I am trying to update database on non UI thread, however changeListener registered on main thread (at the same time) sometimes not get called. Each Activity adds that listener when onStart method is called, and unregister on onStop.
Here is the simple application flow:
Activity1 contains a list of already create items. When user wants to add new item, Activity2 is launched. User fills all required fields and press add btn -> created item is added into local database with temporary ID, and job is added to queue to create that item on remote. After these two operations, Activit2 is closed (calling finish()) and user is back on Activity1 with list of all already created items (including the new one). Meanwhile, job for creating new item on remote is finished, and within its onRun() method, tmpID of created object is replaced with new one that was retrieved from server. However, at this point Activity1 do not get notified about change in database.
Activities register listener like this:
public Activity extends AppCompatActivity {
private Realm mRealm;
private RealmChangeListener listener = element -> Log.d("Called");
#Override
protected void onStart() {
super.onStart();
mRealm = Realm.getDefaultInstance();
mRealm.addChangeListener(realmChangeListener);
}
#Override
protected void onStop() {
super.onStop();
mRealm.removeChangeListener(realmChangeListener);
mRealm.close();
}
}
This is method of job, that is run on worker thread
#WorkerThread
public void onRun() {
Response<ItemResponse> response = mAPI.createItem(text).execute();
if(response.isSuccessful){
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(r ->{
Item i = r.where(Item.class).equalTo("id", tmpID).findFirst();
if(i != null){
i.id = response.body().id;
}
});
realm.close();
// At this point onChange() method of realmChangeListener should be fired
}
}
On the other side, if I would stay on Activity2(not calling finish() after item is added into local DB) and wait until job get finished, onChange() method of RealmChangeListener is called properly...
Both of threads runs on the same process.
I am thankful for any suggestions
EDIT
Activity1 has a fragment attached to it, an that fragment contains list of items.
Fragment then registers for listeners according to Best practises - Controlling the lifecycle of Realm instances within onStart and onStop callbacks.
#Override
public void onStart() {
super.onStart();
mRealm = Realm.getDefaultInstance();
mRealm.addChangeListener(realmChangeListener);
}
#Override
public void onStop() {
super.onStop();
mRealm.removeChangeListener(realmChangeListener);
mRealm.close();
}
Lifecycle of Fragment, when Activity2 is:
Launched
onPause
Closed using back button or by calling finish()
onStart
onResume
For some reason, when Activity2 is closed manually, using back button, realmChangeListener is called. However, if I close it using finish(), nothing happens ...
Finally, after a couple of hours I found out where the problem was...
Activity1 has a Fragment attached to it with list of items and that fragment listens for changes on realm database to update UI. When user wanted to add new item, Activity2 was launched and along with it a presenter, that was carrying about loading some stuff from local DB. That presenter was also listening on Realm DB.
And here is the problem:
When user added new item and Activity2 was about to finish, close() method was called on presenter to clear the resources. One of the commands that were called within close() method, was mRealm.removeAllChangeListeners(). That wouldn't be a problem as Fragment from Activity1 registers its listener again in onStart() callback, but close() method on presenter was called AFTER onStart() on Fragment. So, basically it removed newly registered listener from fragment.
Again, thank you guys for your willingness! #beeender, #EpicPandaForce
Related
I have one method(let's call it getData) in my fragment(let's call it List) which I call in onCreateView.
This method load some data from server and put it to some views.
In my fragment I have one button which open another fragment(Let's call it Detail), and when I go back to List onCreateView calling again and data start load again.
I tried to put getData into onCreate... and in fact it must works, and mustn't call getData again...
But getData works with views whick initialize only in onCreateView and another methods after onCreateView and all that methods always recalling when I return to List from Detail.
How do I make don't recall getData when I return to List from Detail.
I'm so sorry for my bad English and grammar mistakes.
Thanks.
first of All override onStop method by Ctrl+O
it will look like this
public class yourFragment extends Fragment{
boolean isReturned = false;
#Override
public void onStop() {
super.onStop();
isReturned = true;
}
public void getData(){
if(!isReturned){
//fetch data
}
}
I have a fragment that adds data to my database. When I close it with dismiss(), it returns to my activity. I want to then update my recyclerView in that activity.
My understanding of the activity lifecycle is that onResume should be called correct? I have a Log in my onResume method and as far as I can tell it is not being called.
What is the better solution, and why is this not being called?
onResume
#Override
public void onResume() {
super.onResume();
Log.e("Resume", "Resuming");
}
The button click listener in my fragment. The Log here works perfectly fine.
//save button for saving workouts
mButton2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String title = mEditText.getText().toString();
String category = mSpinner.getSelectedItem().toString();
String weight = mEditText2.getText().toString();
int icon = buildIcon(category);
//pass all the data into a new object for the Recycler View to store and show also refresh
db.addWorkout(new Workout(title, weight, "8/8/8", icon));
Log.e("Database Build", "Added a new workout: " + title);
dismiss();
}
});
The Activity was never paused when you started dealing with the fragment. onResume won't get called in that scenario and that's expected lifecycle behavior.
You should consider implementing some type of callback to let the Activity know when the Fragment has closed. The android documentation has a really good explanation of how to communicate with fragments. Use the pattern in the documentation and build yourself an OnFragmentClosedListener
I have a BaseFragment which within it's onCreateView method, creates a MyObject class. Both of these are inside a ViewPager.
Two different fragments extends from the BaseFragment - FragmentA, FragmentB.
This means FragmentA and FragmentB both have their own instances of the MyObject object.
Within the BaseFragment, I call myObject.initialise(); on the MyObject object from the onStart(); method and cleanUp(); from the onStop();
#Override
public void onStart()
{
super.onStart();
myObject.initialise();
}
#Override
public void onStop()
{
myObject.cleanUp();
super.onStop();
}
Again - this lives inside the BaseFragment so both FragmentA and FragmentB have this in their lifecycle.
The initialise(); function and cleanUp(); functions look like this:
#Override
public void initialise()
{
BusManager.register(this);
}
#Override
public void cleanUp()
{
BusManager.unregister(this);
}
FragmentA will generally close first and it successfully unregisters. When FragmentB closes however, it crashes because it think this was not registered.
I checked the memory address of this and it appears that it tries to unregister the same thing twice.
Missing event handler for an annotated method. Is class com.example.app.MyObject registered?
Why is it doing this? I have made sure that MyObject is a new instance.
For the comment above, note that onDestroy() is not necessary called:
https://developer.android.com/reference/android/app/Activity.html#onDestroy()
You should not count on that for Otto's register / unregister call.
In regarding to Subby's question: I've had scenarios where onStart() / onStop() being called twice. What I ended up is placing a try-catch block. Definitely not a clean solution, but that's how I do before finding out why the lifecycle is messed up.
I have two activities that each contain fragments. Activity A has a fragment with a ListView. Rows of the ListView are obtained from a SQLiteDatabase.
Activity B contains a fragment that enables entries of the database to be edited. When a user edits a database entry, saves the data, then returns to Activity A via the backbutton or:
actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
How do I notify the ListView in Activity A that there has been a change to the database? I understand how to communicate between an Activity and it's fragments, but how do I implement a notifydatasetchanged when the fragments are in two different activities?
I suspect that going back to Activity A via the back button is simply recalling the view from the stack, but that view has changed when there is a database change.
You could simply call the notifyDataSetChanged in onResume in your Activity A. So when you close or go back from Activity B to A, it will update the data. Don't forget to check if the adapter is different than null, otherwise it could crash the first time you open the App.
You could also make a public function in your Activity A which updates the list and call it from your Activity B.
class ActivityA
public static void updateList() {
if(adapter != null){
adapter.notifyDataSetChanged;
}
}
class ActivityB
ActivityA.updateList();
Hope it helps :)
you could start activity with startActivityForResult(start activityB) and override method onActivityResult in activityA and notifyDataSetChanged when needed.
You will need to read the updated dataset from your SQLite database and pass it adapter again. After you need to notify adapter. For example, like in the code below:
In your Activity A
#Override
protected void onResume() {
super.onResume();
// method to read new dataset
Database ourDB = new Database(this);
ourDB.openDB();
data = ourDB.getData();
ourDB.closeDB();
if(adapter == null) {
// initializes adapter when first launched
adapter = new RecyclerViewAdapter(data, this);
yourListView.setAdapter(adapter);
}
else {
// in subsequent returns to activity we are replacing
// old data with new and reusing existing adapter
adapter.updateData(data);
}
}
In your adapter class include similar method:
public void updateData(ArrayList<String> newData) {
data = newData;
this.notifyDataSetChanged();
}
I have Fragment "A" where I have an ImageButton in place. Upon clicking this button a DialogFragment "B" is called to the foreground where Fragment "A" is partially visible in the background. DialogFragment "B" presents the user with a list of choices. Upon clicking a specific choice DialogFragment "B" is dismissed via Dismiss() and Fragment "A" becomes fully visible again.
During this action I need to update the ImageButton on Fragment "A" to represent the user's choice made on DialogFragment "B" (basically a new image for the ImageButton).
Am I correct in thinking the right place to update the ImageButton on Fragment "A" is during OnResume? Does Fragment "A" go into OnPause while FragmentDialog "B" is being shown? Therefore upon returning from DialogFragment "B", Fragment "A" would trigger its OnResume and that's where I should make the update changes to the ImageButton being presented to the user?
I hope my explanation is clear. Any detailed help on where and how I should be updating the ImageButton would be highly appreciated.
With the addition of ViewModels and LiveData solving this problem just got easier. Create a viewModel which both fragments reference. Put the next line in the OnCreate of the fragments. Can also be in onCreateDialog of the dialogfragment.
myViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
When the dialog is dismissed, call a method on myViewModel, which updates a LiveData variable:
dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
myViewModel.setButtonPressed(PositiveButtonPressed);
}
});
dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
myViewModel.setButtonPressed(NegativeButtonPressed)
}
});
In the viewModel the method sets a MutuableLiveData variable for example to the image to be shown.
void SetButtonPressed(int buttonPressed){
if (buttonPressed==positiveButtonPressed){
imageToBeShown.setValue(image A);
}
else{
imageToBeShown.setValue(image B);
}
}
Set an observer to LiveData variable in onActivityCreated:
myViewModel.imageToBeShown().observe(getViewLifecycleOwner(), new Observer<Image>() {
#Override
public void onChanged(#Nullable Image image) {
button.setBackground(image);
}
}
});
Of course you can implement a getter method and keep the MutuableLiveData variable private. The observer then just obeserves the getter methode.
I had same problem when tried with Interface-Callback method but OnResume of Fragment didn't got triggered when DialogFragment was dismissed since we are not switching to other activity.
So here Event Bus made life easy. Event Bus is the easiest and best way to make communication between activities and fragments with only three step, you can see it here
This is nothing but publish/subscribe event bus mechanism. You will get proper documentation here
Add EventBus dependency to your gradle file -
compile 'org.greenrobot:eventbus:x.x.x'
OR
compile 'org.greenrobot:eventbus:3.1.1' (Specific version)
For the above scenario -
Create one custom POJO class for user events -
public class UserEvent {
public final int userId;
public UserEvent(int userId) {
this.userId = userId;
}
}
Subscribe an event in Fragment A whenever it is posted/published from DialogFragment or from somewhere else -
#Subscribe(threadMode = ThreadMode.MAIN)
public void onUserEvent(UserEvent event) {
// Do something with userId
Toast.makeText(getActivity(), event.userId, Toast.LENGTH_SHORT).show();
}
Register or Unregister your EventBus from your Fragment A's lifecycle especially in onStart and onStop respectively -
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
In the end, on clicking specific item, Publish/Post your event from DialogFragment -
EventBus.getDefault().post(new MessageEvent(user.getId()));
Fragment A won't go into onPause so onResume won't get called
http://developer.android.com/guide/components/fragments.html
Resumed
The fragment is visible in the running activity.
Paused
Another activity is in the foreground and has focus, but the activity
in which this fragment lives is still visible (the foreground activity
is partially transparent or doesn't cover the entire screen).