I am new to the android development. Currently i am working with the architecture components. When I inflate my fragment for the first time the Observer onChanged() executed several times so the data in my ui got repeated. But it is working fine when I detach and attach the fragment.
MyFragment.java
private MyViewModel myViewModel;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup
container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.my_fragment, container, false);
return rootView;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
myViewModel = ViewModelProviders.of(this).get(MealViewModel.class);
myViewModel.getBreakfast(randomNum).observe(getViewLifecycleOwner(), new Observer<List<BreakFast>>() {
#Override
public void onChanged(#Nullable final List<BreakFast> breakFasts) {
for (int i = 0; i < breakFasts.size(); i++) {
String food = breakFasts.get(i).getFood();
if (food.contains("||")) {
food = food.replace("||", "\n");
}
builder.append( food + "\n");
}
resultTextView.setText(builder.toString());
}
});
}
In the above code, the onChanged method gets executed many times when the view inflates for the first time.
LiveData is a type of observable. Its job is to publish changes when data hosted in it gets updated (either manually by calling postValue on it or automatically when new data is added)
If you want to get data only once, then LiveData is not an option for you. You can simply use Single to achieve this.
Another thing that you can do is - simply ignore changes when they are pushed to you second time onward through your onChanged() method. What I mean by this is, do not update your text view second time onward. However, you should really have a separation logic that's clean enough to distinguish in between the callback that you will use and other callbacks that you won't use.
One last thing that I can think of is remove observer - your current lifecycle owner (activity or fragment) once you have observed the changes once. By calling removeObserver() method on LiveData.
PS: All of these are just workarounds. You should really rethink about using LiveData if live changes to the data is not something that you wanted to begin with.
Related
I'm new to SO and fairly new to coding, so please accept my apologies in advance if I break rules or expectations here.
I have an unusual setup involving two recyclerViews, which I'll explain here and also paste a simplified version of the code below (as there is so much not relevant to this question).
In what I'll call verticalRecyclerViewActivity, a verticalRecyclerViewAdapter is called, with data it fetches from Firebase and loads into arrayLists.
If the user clicks on an item in the vertical recyclerview, a new dialog fragment which I'll call horizontalRecyclerViewDialogFragment is inflated, and that loads what I'll call horizontalRecyclerView (which has similar items to the vertical one, in more detail, with options to click on them to review them).
If the user clicks on an item in the horizontalRecyclerView, a new activity which I'll call reviewItem is started (through an Intent). When the user submits their review, it finishes and returns (through the backstack) to the horizontal RecyclerView. That can also happen if they press the back button without actually submitting a review. That all works fine, but I need the horizontalRecyclerView to show that they have (or haven't) reviewed the item and state the score they gave it in a review.
Calling notifyDataSetChanged won't work for this because of how information comes through two recyclerViews and Firebase calls (or, at least, it would be very inefficient).
I've tried using startActivityForResult (I know it's deprecated, but if I could get that to work I could try using the newer equivalent which I don't yet understand) but the problem is that the result is returned to the original (VerticalRecylcerView) activity, which is two recyclerView adapters and one fragment beneath what needs to be updated, and I don't know how to pass that data to the horizontal Recyclerview.
I've also tried using interfaces but was unable to pass it through the Intent (tried using Parcelable and Serializable, but it seems neither can work in this situation?).
Since the review is updated on Firebase, I could have the horizontal Recyclerview listen for a change, but that seems very inefficient?
So I've found a solution using localBroadcast (which I know is also deprecated). The Intent (with the review score) is transmitted when it is reviewed and received in the horizontal recyclerView adapter. But when and how should I unregister the adapter? Ideally the receiver would be turned on when the user goes to the Review activity and turned off once the user returns from that activity and the (horizontal) recyclerView holder is updated, whether the review is successfully submitted or whether the user just presses the back button and never submits a review.
My question is similar to this one: How to unregister and register BroadcastReceiver from another class?
That is noted as a duplicate of this one: How to unregister and register BroadcastReceiver from another class?
There's a lot in those questions I don't understand, but the important difference I think between their and my cases is that I would just like the receiver to know when a review is submitted, and ideally be unregistered then, or possibly when the viewHolder is recycled, which I tried but also didn't work since it's not connected to the viewHolder (should it be?).
Any help would be much appreciated, thank you!
public class verticalRecyclerViewActivity extends AppCompatActivity {
// Loads an XML file and assembles an array from Firebase.
mVerticalRecyclerView = findViewById(R.id.verticalRecyclerView);
verticalRecyclerViewAdaptor mVerticalRecyclerViewAdaptor = new verticalRecyclerViewAdaptor (this); // also pass other information it needs
mVerticalRecyclerView .setAdapter(mVerticalRecyclerViewAdaptor);
}
public class verticalRecyclerViewAdaptor extends RecyclerView.Adapter<verticalRecyclerViewAdaptor.singleHolder> {
// Usual recyclerView content
holder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
horizontalRecyclerViewFragment mHorizontalRecyclerViewFragment = new horizontalRecyclerViewFragment();
// lots of arguments passed it needs.
FragmentManager fragmentManager = ((FragmentActivity) view.getContext()).getSupportFragmentManager();
mHorizontalRecyclerViewFragment.show(fragmentManager, null);
}
public class mHorizontalRecyclerViewFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity().getApplicationContext(); // Not sure why I need this, but it works.
View horizontalRecyclerViewView = getActivity().getLayoutInflater().inflate(R.layout.horizontal_recyclerview_holder, new CardView(getActivity()), false);
Dialog horizontalRecyclerViewDialog = new Dialog(getActivity());
horizontalRecyclerViewDialog.setContentView(horizontalRecyclerViewView);
mHorizontalRecyclerView = horizontalRecyclerViewView.getRootView().findViewById(R.id.horizontalRecyclerView);
mHorizontalRecyclerViewAdapter = new horizontalRecyclerViewAdapter (mContext)
// Other arguments passed
mHorizontalRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(),
LinearLayoutManager.HORIZONTAL, false));
mHorizontalRecyclerView.setAdapter(mHorizontalRecyclerViewAdapter);
}
public class horizontalRecyclerViewAdapter extends RecyclerView.Adapter<horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder> {
public horizontalRecyclerViewAdapter(){}
// Blank constructor and also one with lots of arguments for it to work.
public horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.horizontal_recyclerview_adaptor_holder, parent, false);
return new horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder(view);
}
public void onBindViewHolder(#NonNull horizontalRecyclerViewHolder holder, int position) {
// Connect up various views.
holder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
LocalBroadcastManager.getInstance(mContext).registerReceiver(reviewSubmittedListener, new IntentFilter("reviewSubmitted"));
Intent reviewNow = new Intent(view.getContext(), ReviewActivity.class);
// Put extra details with the intent
view.getContext().startActivity(reviewNow);
}
BroadcastReceiver reviewSubmittedListener = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent reviewFinishedIntent) {
int reviewScore = reviewFinishedIntent.getExtras().getInt("reviewScore");
// Update the horizontal RecyclerView with the information received from the review Activity.
}
};
}
public class ReviewActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_review_item);
// Set up the review, using Firebase and data passed through the intent.
}
public void submitReview() {
// Check that the review is complete/valid and submit it through Firebase
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(ReviewItemActivity.this);
Intent reviewFinishedIntent = new Intent("reviewSubmitted");
reviewFinishedIntent.putExtra("reviewScore", overallScore);
LocalBroadcastManager.getInstance(this).sendBroadcast(reviewFinishedIntent);
finish();
}
If you are using RxJava you can use the RxBus else you can use one of many EventBus implementation for this.
If that is not the path you want to take then you can have a shared view model object that can be used only for communication between fragments see this article.
this is my first question on stack (great community, thanks!)
So, I have this problem:
I'm developing an android application with a tab layout. Whenever I navigate to the "Status" tab, I'd like to show a circular ProgressBar that is updated according to the value of a variable. Let's call this variable prog. Moreover, I'd like to display in the center of the ProgressBar a TextView that displays the same value of prog.
I have came up with this part of code:
public class StatusFragment extends Fragment
{
private ProgressBar torqueRefProgressBar;
private TextView torqueRefTextView;
RunningThreadExample r;
private Thread t1;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//Inflate the layout for this fragment
return ...;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
// Initializations and other parts not relevant for the question...
this.torqueRefProgressBar = getView().findViewById(R.id.torqueRefProgressBar);
this.torqueRefTextView = getView().findViewById(R.id.torqueRefTextView);
t1 = new Thread( this.torqueRefProgressBar, this.torqueRefTextView)); // passing arguments by value but values are references to the objects, therefore I'm passing by ref
t1.start();
}
}
In this Fragment I create the ProgressBar and the TextView and then pass them to the Runnable as you can see below
public class RunningThreadExample implements Runnable
{
ProgressBar torqueRefProgBar_thread;
TextView torqueRefTextView_thread;
public RunningThreadExample(ProgressBar progressBar, TextView textView)
{
this.torqueRefProgBar_thread = progressBar;
this.torqueRefTextView_thread = textView;
this.endScale = this.torqueRefProgBar_thread.getMax();
}
#Override
public void run()
{
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
float i = 0;
double temp = 0;
while(Running)
{
try
{
Thread.sleep(1000/60); // update freq 60Hz
}
catch (InterruptedException e)
{
e.printStackTrace();
}
temp = getCurrentVariableValue(); // Not shown here (returns a double)
this.torqueRefProgBar_thread.setProgress((int) temp);
this.torqueRefTextView_thread.setText(String.valueOf(temp));
}
Log.i(INFO_TAG,"Finishing thread!!!!!");
}
}
I'd like this update to run all the time the app is working (for this reason I've neglected AsyncTask). After a while, it happens that the ProgressBar stops updating, while the TextView continues to work without any problem.
I've been reading that a possible cause could be that the calling setProgress(temp) might stuck the UI. However I can't understand while the TextView is still working.
Can you suggest a better way to make the progressBar update?
Thank you!
See this thread:
Android basics: running code in the UI thread
So to summarize, Android requires certain types and sections of code to run in a specific context. Many tools such as Handlers are available to help with these tasks but it was difficult to learn to begin with.
You cannot affect View from any Thread other than Main Thread. Do the following in run() method
runOnUiThread(new Runnable() {
//
run(){
this.torqueRefProgBar_thread.setProgress((int) temp);
} this.torqueRefTextView_thread.setText(String.valueOf(temp));
);
I have a fragment that starts a count and changes an icon status. After opening the app there is a count - 00:00 and a button that says START.
After clicking START the counts starts and the button changes to STOP.
After clicking STOP the count stops and the button changes to START. Pretty basic.
The thing is that after clicking START and minimalizing the app and opening it back (putting app in background and back) the count and button is always reverted back to START and 00:00.
So the question is: How can I keep the fragment alive after minimalizing the app?
Code:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View recordView = inflater.inflate(R.layout.fragment_record, container, false);
ButterKnife.bind(this, recordView);
return recordView;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
btnPause.setVisibility(View.GONE);
recordBtn.setColorPressed(getResources().getColor(R.color.colorPrimary));
}
#OnClick(R.id.btnRecord)
public void recordAudio(){
onRecord(mStartRecording);
mStartRecording = !mStartRecording;
}
private void onRecord(boolean start) {
Intent intent = new Intent(getActivity(), RecordingService.class);
if(start){
recordBtn.setImageResource(R.drawable.ic_media_stop);
//Toast.makeText(getContext(), "Started recording", Toast.LENGTH_LONG).show();
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
getActivity().startService(intent);
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
recordingStatusTxt.setText("Recording now...");
} else {
recordBtn.setImageResource(R.drawable.ic_mic_white);
chronometer.stop();
chronometer.setBase(SystemClock.elapsedRealtime());
timeWhenPaused = 0;
getActivity().stopService(intent);
recordingStatusTxt.setText("Click the button to start recording");
}
}
You cannot keep your fragment alive.
System handles it itself. It can kill it anytime it wants to.
The thing is that after clicking START and minimalizing the app and
opening it back (putting app in background and back) the count and
button is always reverted back to START and 00:00.
The reason behind it actually pretty simple - your fragment basically recreates. And this is an expected behaviour. To continue you should learn Activity and Fragment lifecycle
And as I understand this is a Recorder app so you should get time elapsed from that service.
For example, you could Override onResume() method of your Fragment with setting time and button status using information gained from your service.
Good luck!
If you minimize the app while running, the android operating system can terminate your app and restart it when the user returns to it. You cannot guarantee it will remain in memory. If there is any data that needs to be maintained, you are responsible for saving it in the state handlers.
If you wish to have some process continue in the background, you'll need to start an Android Service and your fragment will need to communicate with it. The Fragment is just the UI piece of the app.
I'm using EventBus in my Android application. In my mainActivity, I have this handler method which sends live data to the EventBus as follows:
private final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TGDevice.MSG_STATE_CHANGE:
EventBus.getDefault().postSticky(msg.arg1);
...
I'm using Fragments class and I need to receive the message from the handler.
I have registered the Fragment class in the onCreateView method as follows:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_data_log, container, false);
EventBus.getDefault().register(this);
tv = (TextView) view.findViewById(R.id.tv);
}
public void onEvent(Message message){
tv.setText("Signal" + message);
}
And I have the onEvent method which is suppose to be called when there is an Event. Unfortunately, this method is never called. I thought it might be method to be overridden but it doesn't seem to be.
What do i need to do to read from messages from the EventBus?
Also, in debugging modes, where can I see the number of threads being created? (I'm using Android Studio)
Unfortunately, this method is never called
That is because your onEvent() takes a Message, and (presumably) you are not posting a Message. You are posting whatever arg1 is.
Also, in debugging modes, where can I see the number of threads being created? (I'm using Android Studio)
Go into the Android Debug Monitor (Tools > Android > Android Debug Monitor from the main menu), and there's a threads view in DDMS inside of there.
I am having a ViewPager to allow the user to slide between 5 different views where each "view" extends Fragment.
I have my own adapter which extend FragmentPagerAdapter and which implement getItem() as
#Override public Fragment getItem(int position) {
switch(position) {
case 0:
return new TextDescriptionFragment();
// Handle the 4 other cases the same way
}
}
This works fine and the user can swipe between the 5 different views. But here comes the problem: Each of the first 4 views contains Views such as Button and EditText which the user can interact with.
And I then want the last page(Page number 5) to show all the user input values from all the views from the 4 previous pages(fragments). How do I do that?
I can't find any way to read the user input values from the previous fragments. The views may not even exist anymore(But will be recreated if the user goes back).
And I can't seem to get the existing fragments.
I would consider having a custom object that keeps the data each fragment fills. Something like:
public class FillerData implements Parcelable {
private String page0$data0;
private String page0$data1;
private String page0$data2;
// getters and setters if you wish
// implement Parcelable interface as this object will be managed by host activity
}
You'll have only one such object managed by parent activity and the parent activity will implement an interface for exposing this object:
public static interface FillerDataExposer {
public FillerData exposeFiller();
}
public class MyFragmentHostActivity extends FragmentActivity implements FillerDataExposer {
private static final String FILLER_KEY = "FILLER_KEY";
private FillerData myFillerData;
protected void onCreate(Bundle savedInstance) {
.......
if(savedInstance != null) {
myFillerData = (FillerData) savedInstance.getParcelable(FILLER_KEY);
} else {
myFillerData = new FillerData();
}
}
protected void onSaveInstanceState(Bundle savedInstance) {
super.onSaveInstanceState();
savedInstance.putExtra(FILLER_KEY, myFillerData);
}
public FillerData exposeFiller() {
return this.myFillerData;
}
}
Now, each of your fragments will have access to that centralized data filler object through parent activity. To reduce the weight of your code, all your fragments could extend from a base fragment class that provides access to FillerDataExposer implementation (actually, the parent activity):
public abstract class AbstractFillerFragment extends Fragment {
protected FillerDataExposer dataExposer;
public void onAttach(Activity act) {
super.onAttach(act);
// make sure no ClassCastExceptions
this.dataExposer = (FillerDataExposer) act;
}
}
Fragments that should only record the data filled could look like this:
public class Page1Fragment extends AbstractFillerFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = // inflate XML resource ...
yourEditText = (EditText) view.findViewById(...);
// other relevant code ....
}
public void onViewCreated(View view, Bundle savedInstanceState) {
yourEditText.setText(dataExposer.exposeFiller.setPageX$DataY());
// some code for EditText.addTextChangedListener(new TextWatcher() could look like:
yourEditText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
dataExposer.exposeFiller().setPage1$Data0(s.toString());
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
}
While the fragment that needs to have access to all data stored could look like this:
public class FinalFragment extends AbstractFillerFragment {
public void collectAllData() {
DataFiller allDataCollectedObject = dataExposer.exposeFiller();
// by calling get...() you should have access to collected data.
}
}
This only a sketch, but you'll get the picture. The idea is to keep a single object in your activity managed across activity restarts and to make it accessible through interfaces so you will respect the fragment to activity patterns.
Hope it makes sense ...
2 solutions come to my mind.
The first one is to save user input data when your first 4 fragment's onpause() methods are called. You may save the data to a preference and then retrieve it from your 5th fragment.
The second approach is to persist your fragments while swiping.This way the swiping will be faster and cleaner and they want be recreated everytime a swipe happens:
yourcustomviewpager.setOffscreenPageLimit(5);
About setOffscreenPageLimit from the android doc:
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number of pages you will need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting can have benefits in perceived smoothness of paging animations and interaction. If you have a small number of pages (3-4) that you can keep active all at once, less time will be spent in layout for newly created view subtrees as the user pages back and forth.
You should keep this limit low, especially if your pages have complex layouts. This setting defaults to 1.