I have an Activity that creates a retained Fragment in order to persist a long running network operation in the event that the Activity goes through any configuration changes during the long running operation. So basically I'm trying to use the retained Fragment like a Singleton that lives until Activity's onDestroy() is called.
My goal is to have Activity's onCreate() trigger the long running network operation because I only want it to run when the activity is created not every time it starts again (otherwise I'd put it in onStart()).
To do this I first create a retained fragment in Activity's onCreate, then use FragmentManager to add the retained fragment then I kick off the network call in Activity's onCreate method and pass the networking object to the retained Fragment to hold onto.
This works, however I'm concerned because if I log what's going on I can see that the Activity first sets data on the retained Fragment and then the retained Fragment's onCreate() method is called. This looks wrong and seems out of order, however it works.
Is it bad practice to utilize the retained Fragment instance before the fragment has run its onCreate() method?
EDIT
After reading the responses and thinking a bit more about this, I'm realizing that initiating the network call from Activity onCreate() although convenient, is risky to do. As noted in the responses there's a chance that the long running operation returns very quickly and attempts to manipulate the Activity's view which may not have been initialized yet. So for my specific case I am resorting to initiating the long running operation from Activity's onStart() method and then using the retainedFragment to cache the response. This way even if onStart() is called multiple times and attempts to kick off the long running operation again, the result from the first attempt will be cached and can be returned.
I will admit that's unusual, but I can't think of a reason off the top of my head why it would be bad. The advantage of a retained Fragment is the reference to the Fragment is not destroyed so the references it holds on to are also retained. onCreate() and onDestroy() are called once throughout the lifetime of the Fragment (when the Fragment is added and removed respectively).
The danger might be that the asynchronous operation finishes before onCreate() is called. Likewise, the operation could finish after onDestroy() is called and when you expected the Fragment to be running. There are some methods like Fragment#setArguments() that can not be called during some parts of the Fragment's lifecycle. If you were to call these methods when you expected the Fragment to be running, then you will get an Exception thrown. So you end up having to put in a bunch of checks like if(isAttached()) { /* do this */ }. Putting the operation in onCreate() will ensure that it at least started before the operation finished.
But if you are not actually relying on any of the Fragment's functionality, then it should be fine. The lifecycles are only meant to tell you what's going on with it.
As long as you are not relying on Activity/Fragment views being available while Fragment's onCreate, you will be fine:
Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point.
(https://developer.android.com/reference/android/app/Fragment.html#onCreate(android.os.Bundle)
Still, you need to secure possible edge case that long-running operation may be finished before Activity and Fragment are fully created (if you depend on their views, this might be an issue).
Therefore, think about what you will do when long-running operation has finished and the results need to be presented somewhere.
Related
I have done ample research on this, and there is not one clear solution on the problem.
In the life-cycle, particularly in the Fragment life-cycle, following Exception comes any moment after onPause().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
My logic says, that to continue with the current fragment, after it reaches this state, I have to restart the activity and again point back to the intended fragment using Intent.
I want to be clear on what is happening and what should be real solution to deal with it.
I need to know the pros and cons of this mechanism; its importance in Fragment or Activity life-cycle.
Also, if I am changing the Windows Feature in onCreate to not to go to sleep, unless if the user has manually pressed the home button, will still the activity will go to this state?
This exception happens when you're trying to add/remove/replace/interact in any other way with a Fragment inside the Activity when it's paused.
Which means Activity will not be able to restore it's state (restore the state of a Fragment which has been changed) if it will be destroyed right away.
Best solution here, is to check that Activity is NOT paused during the interaction with a Fragment.
Another option is to use commitAllowingStateLoss() to interact with Fragment transaction, with a risk of losing it's state.
See:
https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()
In a perfect world you should analyze each crash carefully and add checks to verify that you interact with fragments only when Activity is up and running.
A better explanation is presented in a new Android developer reference and guide documents for using JetPack Life Cycle Listener.
https://developer.android.com/topic/libraries/architecture/lifecycle#kotlin
The library makes the components Activity Life Cycle aware. That means you do not require an abstract baseActivity class which overrides every life cycle callback, and record that state in a boolean variable. LifeCycle listener will do it for you.
All you have to do is stop introducing a new fragment or stop any Loader that updates the UI when its response returns. The right time to do this is before onStop or onSavedInstance state is called, and your components will be made aware of it.
It clearly states that after the onSavedInstancState or onStop is called the UI becomes immutable till the onStart of the Activity is called again. Sometimes you have to call restart the same activity using NEW TASK and CLEAR TASK flags using intent, when this state occurs and there is no chance that otherwise onStart is going to be called.
Happy Coding :-)
I have a Fragment in an Activity. When the Activity gets created, it triggers an asynchroneous load of the application state from a file. When this finishes, the Activity starts creating some paged Fragments, depending on the configuration state.
The problem is that when the OS kills the app, so that onCreate(bundle) has a stored state in the bundle because the OS took care of storing it prior to killing the app, the Fragments are also recreated immediately, but the application state is still getting read from the file, so they don't have access to the data they should be showing, which they need in their onCreateView(). This is because this time the Fragments are created by the OS, and not by the callback of the AsyncTask in the Activity's callback.
I'm hesitant towards making onCreateView() block/wait for the async result (even if it is avaliable in a fraction of second), so I'm thinking about moving the code which depends on the application state out of the Fragment's onCreateView(), and have some callback create it when the data is avaliable.
Ideally I'd work with deferreds/promises/futures, where I can "subscribe" to the async load result in the onCreateView() , but i'm too new to Java in order to know how to do this. I don't know which libraries exists or are suited for this.
Which suggestions do you have for me so I can deal in the best possible way with this issue?
Kind regards.
I feel like this should have been answered by the documentation in the Activity class, but I'm still not positive -- when is it actually safe to update the UI of an Activity (or a Fragment)? Only when the activity is resumed, or any point between being started and stopped?
For example, the Activity docs state:
The visible lifetime of an activity happens between a call to onStart() until a corresponding call to onStop(). During this time the user can see the activity on-screen, though it may not be in the foreground and interacting with the user. Between these two methods you can maintain resources that are needed to show the activity to the user. For example, you can register a BroadcastReceiver in onStart() to monitor for changes that impact your UI, and unregister it in onStop() when the user no longer sees what you are displaying. The onStart() and onStop() methods can be called multiple times, as the activity becomes visible and hidden to the user.
From that reading, I would assume that even if a foreground dialog is displaying, I can safely update UI elements behind it.
EDIT
To clarify: the reason I ask is having been bitten by errors when firing off an AsyncTask, and attempting to update the UI in the onPostExecute method. Even though it runs on the UI thread, the user has navigated away from that view and I would receive an exception. I'm starting a new project now and trying to establish some guidelines around better AsyncTask idioms.
I guess this comes down to what you mean by "safe" to update the UI. For elements on the Activity screen, they can be updated whenever, even if your Activity is not in the foreground (but make sure to update from the UI Thread).
However, the issue that will trip you up is saving state: onSaveInstanceState.
As you may know, the Activity that is in the background may be destroyed by the OS to free up memory. It will then be re-created when you come back to it. During this process, the onSaveInstanceState method will be called. If the OS does destroy the Activity, any changes you made to the UI State after the call to onSaveInstanceState will not be persisted.
For Fragments, you will actually get an IllegalStateException if you try to commit a FragmentTransaction after onSaveInstanceState. More info on that.
To summarize, you can update the UI of your activity at any point and try to gracefully handle the Fragment issues, but you may lose these updates when the Activity is restored.
So, you could say that it is only truly safe to update the Activity while it is in the foreground, or rather, before the call to onSaveInstanceState.
Edit in regards to Async Task onPostExectue
This is likely related to the issue I am referring to above with Fragment State Loss. From the blog post I linked to:
Avoid performing transactions inside asynchronous callback methods. This includes commonly used methods such as AsyncTask#onPostExecute() and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with performing transactions in these methods is that they have no knowledge of the current state of the Activity lifecycle when they are called. For example, consider the following sequence of events:
An activity executes an AsyncTask.
The user presses the "Home" key, causing the activity's onSaveInstanceState() and onStop() methods to be called.
The AsyncTask completes and onPostExecute() is called, unaware that the Activity has since been stopped.
A FragmentTransaction is committed inside the onPostExecute() method, causing an exception to be thrown.
I have an app which makes rest calls and represents the data in a GridView.
The main activity uses two fragements, a UI Fragment and a retained worker fragment. The worker fragment contains an inner AsyncTask that performs the REST calls.
Everything was working great I had no crashes etc, until I tried to do an update on a regular interval.
In order to perform the interval I added a handler. The handler is a member of the worker fragment. Within the worker fragment I have the LoadAPI method which calls the asynctask.
public void loadAPI(){
final String myURL = "http://x.com/"
handler.post(new Runnable() {
public void run(){
new APITask().execute(myURL);
handler.postDelayed(this,10000);
}
});
}
The problem is when there is a config change, my activity is destroyed, and onPostExecute crashes when it references the main activities listener. But I have implemented onAttach in my worker fragment. onCancel seems an ugly option, as multiple asynctasks can get called, and I don't have a named instance. I suppose I could keep a list of asynctasks in the worker fragment and cancel them onDestroy (It's ok to lose the latest update) but I think I am doing something wrong here. What is frustrating is the worker frag and asynctask were working fine until I did a continuous polling and I can't figure out why the introduction of the handler is causing this behavior.
My api tasks will take anywhere from 50 milisecond to 5 seconds.
Retained fragments will not get recreated during config changes like rotations, but they will still get destroyed and recreated when system will kill your app because it is in background for example.
so to be safe you should at least:
Never put your async task inside fragment as inner class, if you want to have it inside your fragment class body, then make it static. Otherwise AsyncTask will keep internal reference to your fragment and will prevent it from being garbage collected, and whats more bad is that in onPostExecute you will access your destroyed fragment.
When creating your asynctask, pass a reference to fragment to it, and store this reference inside WeakReference<>, ex:
private WeakReference<DataFragment> fragmentRef;
then in onPostExecute, before using fragment check if fragmentRef.get() returns non-null.
If you need continuous data updates, then consider using IntentService, or even WakefulIntentService. It will be slightly more difficult to report data updates progress from service to activity - but it can be managed with broadcasts. Also, if you want to do data updates from background then you will have to use service, together with alarms - then WakeFullIntentService (or regular service) is the way to go: https://github.com/commonsguy/cwac-wakeful for further reading.
Whats the recommaned approach to notifying the hosting activity of a fragment that performs some background processing, that its done. Assuming that the fragments are running some threads that are performing work outside of the main looper.
A simple callback won't do since:
The Activity could be detached due to screen rotation which would lead into a NullPointerException.
Polling from within the activity is just dumb
Only calling the activity once if attached and let the activity check after each onCreate call (i.e. due to screen rotation).
What I currently do but it seems wrong: Whenever the Fragment gets attached, it will check if the work is done and notify the activity via callback. If the fragment finishes the work it will also callback the activity (if attached).
Why I think is is wrong? Because I have some really ugly methods that check if the fragment is attached and also if the work is done in order to maybe call the callback. This becomes very stupid when an exception is raised during performing some work in the fragment and the activity is detached. If android decides to call onSaveInstance in the same moment I will have to put the Exception into the Bundle and deliver it later when the Activity and fragment is recreated from the saved state. Additionally I can run into a situation where a activity will received the same callback twice (once from checking the fragment and the second time when the fragments gets attached; this could happen when the application got saved and restored)
This generates so much code that, in my optinion, could be much more clear if activites won't get detached. That is why I hope I'm doing something wrong and hope someone will provide me with a better solution.
I would say you should use a service for your background processing but if you've chosen a fragment for a specific reason you can stick with that. The way you should notify the activity from either a fragment or a service that might have a different lifecycle from the activity would be through a BroadcastReceiver. With this method the activity can register and unregister a BroadcastReceiver during its own lifecycle callback. From the fragment or service you just send the broadcast and forget about it. If an activity is listening for the broadcast it will receive it, if not nothing will happen.