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
Related
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
This probably demonstrates the most appalling lack of understanding of the activity life cycle, but please be sympathetic. I am ultimately going to want to invoke Activity B from Activity A a number of times, each time passing a different parameter to Activity B which is then responded to by the user and stores/sets various public variables. As a precursor to this, I just want to get my head round how Activity A sees the change to a public variable that Activity B has changed.
I have three very simple classes: Common.java that holds the public variables, the main activity MainActivity.java and the child activity Child.java. There is only one public variable right now; it's the string mess1 which is initialized to "***". All the code does at the moment is when mainbutton is clicked in MainActivity, it invokes Child. In Child, we immediately set mess1 to "Child here" then set the text in a Child-based TextView to mess1. On clicking the childbtn button in Child, we finish() the child activity (and of course the system returns us to MainActivity.
When this app is run, wee see the three stars displayed in MainActivity. When mainbutton is pressed we go to Child and see "Child here" displayed. When the childbtn is pressed, we return to MainActivity BUT, the three stars are still there although we know for sure that mess1 now holds "Child here".
My questions are:
1. Why, when we know mess1 has been changed, does MainActivity still display "***" on return from the Child activity?
2. What do I need to change in the code to get "Child here" to display?
Relevant code extracts follow. Thanks in advance for your help.
Common.java
public class Common
{
public static String mess1 = "***";
}
MainActivity.java
public class MainActivity extends AppCompatActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mainbutton = (Button)findViewById(R.id.mainbutton);
TextView maintop = (TextView)findViewById(R.id.maintop);
mainbutton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
startActivity(new Intent(MainActivity.this, Child.class));
}
});
maintop.setText(Common.mess1);
}
Child.java
public class Child extends AppCompatActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_child);
TextView childtext = (TextView) findViewById(R.id.childtext);
final Button childbtn = (Button) findViewById(R.id.childbtn);
Common.mess1 = "Child here";
childtext.setText(Common.mess1);
childbtn.setOnClickListener
(new View.OnClickListener()
{public void onClick(View v)
{finish();
}
}
);
}
Likely you are moving back on the back stack history and you are resuming the previous activity that was placed in a paused state and therefore the onCreate isn't being called but the onResume (of the initial activity)..
Using global state this way isn't advised but this should work if you place the appropriate code in the onResume method.
You should set the text in onResume() of MainActivity. When you get back from Child.java onResume() (not onCreate()) is invoked and, since maintop's text is set in onCerate() only, nothing changes it on return.
protected void onResume() {
super.onResume();
maintop.setText(Common.mess1);
}
Reference: Activity Lifecycle and Implementing the lifecycle callbacks
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).
I have this nice method in my ListFragment I call to fill out the details of my other fragment:
private void showClientDetails(int pos) {
myCursor.moveToPosition(pos);
int clientId = myCursor.getInt(0);
if(mIsTablet) {
// Set the list item as checked
getListView().setItemChecked(mCurrentSelectedItemIndex, true);
// Get the fragment instance
ClientDetails details = (ClientDetails) getFragmentManager().findFragmentById(R.id.client_details);
// Is the current visible recipe the same as the clicked? If so, there is no need to update
if (details == null || details.getClientIndex() != mCurrentSelectedItemIndex) {
// Make new fragment instance to show the recipe
details = ClientDetails.newInstance(mCurrentSelectedItemIndex, clientId, mIsTablet);
// Replace the old fragment with the new one
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.client_details, details);
// Use a fade animation. This makes it clear that this is not a new "layer"
// above the current, but a replacement
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}
}
It is called when the user clicks on a client in the ListFragment view:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
mCurrentSelectedItemIndex = position;
showClientDetails(position);
}
This works great, but then another FragmentActivity can change the data that this displays so I thought this would work:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
//update the client list incase a new one is added or the name changes
if(requestCode==1899)
{
myCursor.requery();
theClients.notifyDataSetChanged();
showClientDetails(mCurrentSelectedItemIndex); //now if client was edited update their details in the details fragment
}
}
Now I know the line:
if (details == null || details.getClientIndex() != mCurrentSelectedItemIndex) {
Prevents the block of code being reached when its called in my onActivityResult. So if I remove that if statement, then things freak out and the ft.commit() has a hissy fit and gives me the error:
`07-08 16:53:31.783: ERROR/AndroidRuntime(2048): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
So I guess what I am trying to do isn't as cut and dry as it sounds, it makes no sense to me since I can do that onListItemClicked all day and the fragment displays the details of the newly clicked client constantly very well...
I even tried this in my onActivityResult:
//simulate a click event really fast to refresh the client details
showClientDetails(0);
showClientDetails(mCurrentSelectedItemIndex);
that does nothing, is it im trying to call something from the onActivityResult which isn't a Ui thread or what not?
I do have this in my code of the ListFragment as well
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("currentListIndex", mCurrentSelectedItemIndex);
}
Is this what the error is complaining about?
The other FragmentActivity's action is performing a task which requires the Fragment to save its state via a call to onSaveInstanceState in preparation for a new instance being reconstructed. I've seen this for example when I was firing off an activity from a fragment which filled the entire screen as this resulted in the view being detached from the fragment, state needing to be saved etc.
You basically cannot call commit between onSaveInstanceState and the new instance of the fragment being recreated. See commit.
As for the solution, then either re-think to try and avoid the commit being called when it is or alternatively call commitAllowingStateLoss if you think it's OK for the UI to change unexpectedly on the user.
I think the answer is to not do any fragment transactions in the onActivityResult. I believe what happens is that when the onActivityResult is called the activity it is in hasn't yet resumed and restarted its fragments.
Use a handler to post back the function call to the activity.
handler.post(new Runnable() {
#Override
public void run() {
showClientDetails(mCurrentSelectedItemIndex);
}
});
you can do an other thing which no very good but it works.
finish (you activity)
do an intent of that same class by :
Intent mIntent = new Intent(getApplicationContext(), YouClass.class);
startActivity(mIntent);
I have a tabhost on my application and I'm using an Activity group which handles 3 activities inside.
Example:
ActivityGroup Handles
A -> B -> C
When i start this activities i'm using the flag Intent.FLAG_ACTIVITY_CLEAR_TOP.
My problem is when the user goes from A->B->C and press back button, my B activity shows up, but it does not resume or reload or refresh. It has the same state as before.
For example if the user goes again to C, C is refreshed, but when from C goes back.... B is not.
On B I have implementend methods such as onResume, onStart, onReestart and debugging it the main thread never goes in there...
And i need to refresh B because C can make changes that change the content displayed on B.
I have googleled this for 3 days and I couldn't found a solution..
I had this problem too.
I was using ActivityGroup code based on this blog post.
When I pressed the back button the pervious View would load fine, but the activity associated with it would not fire the onResume().
I was using an extended activity with on overridden and public onResume().
I found this blog post, so tried casting the view as my extended activity and called onResume().
Bingo.
Edit.... here's some more detail...
public class YFIMenuListActivity extends ListActivity {
....
#Override
public void onResume() {
super.onResume();
}
....
}
onResume() is normally protected, but I override it and make it public so that my ActivityGroup can call it.
I only have extended list activities in this activity group (I was just playing around). If you have different activities, each will have to override onResume() and I guess you'd have to look at the type of context you got back from v.getContext() before casting and calling it.
My ActivityGroup looks something like this:
public class BrowseGroup extends ActivityGroup {
.....
#Override
protected void onResume() {
super.onResume();
// call current activity's onResume()
View v = history.get(history.size()-1);
YFIMenuListActivity currentActivity = (YFIMenuListActivity)v.getContext();
currentActivity.onResume();
}
....
}
I've managed to implement an expanded version of cousin_itt's approach.
In both of my activities being used within the activity group I changed onResume from :
protected void onResume()
to
public void onResume()
I then wrote the following onResume function in my ActivityGroup to manually fire off onResumes:
#Override
protected void onResume() {
super.onResume();
View v = history.get(history.size()-1);
MainPeopleView currentActivity = null;
try {
currentActivity = (MainPeopleView)v.getContext();
currentActivity.onResume();
}
catch ( ClassCastException e ) {
Log.e(TAG, e.toString());
}
ProfileView otherActivity = null;
try {
otherActivity = (ProfileView)v.getContext();
otherActivity.onResume();
}
catch ( ClassCastException e ) {
Log.e(TAG, e.toString());
}
}
I have to say, this feels like the worst android hack I've ever written. I'm never using activitygroup again.
((ReportActivity)getLocalActivityManager().getActivity("ReportActivity")).onResume();
ReportActivity is that name you want to back Activity
ps: v.getContext();
only return the ActivityGroup ,it can't invoke child Activity onResume
I have found that onFocusChanged(boolean hasFocus) is great for situations like ActivityGroup. This will fire, even if onResume() does not. I use it for a few of my apps that have TabHosts and ActivityGroups. Here you can force the refresh and insure that it always gets fired when your Activity regains the focus.
I hope you have write your refresh data code in this method onResume().