I'm developing an android tablet application and i need to implement screen orientation (landscape and portrait). when screen orientation changes the fragment asyntactask restarting again. (sometimes application crashing) I need to stop restarting the asynctask and request data again. want to load the existing json data in to the new screen.
any help appreciate.
approch: android:configChanges="keyboardHidden|orientation|screenSize" is not working because of im using a fragment class.
You may find some hack (including muting the configuration changes or retaining the instance) to somehow get it work, but really AsyncTasks are evil and you should stay as far as you can from it most of the time.
If your current AsyncTask's job is to load some data from the network before showing it inside your Fragment, I'd recommend to use an AsyncTaskLoader. The examples in the javadoc should give you a good idea of how to implement it.
This will allow the network request to keep going while you rotate, to notify the UI (i.e. the LoaderManager.LoaderCallbacks) with the result only when it is ready to process it (so not while rotating for example), and will also cache the result so that you don't re-issue the network request each time you need the data.
There are a bunch of 3rd-party libraries trying to address this very common problem too, and Loaders also have their intricacies, but if I understand your use case correctly it should be just what you need.
Don't forget that whatever solution you choose it will have to account for the rotation (activity destroy / recreate) happening before, during and after the load of the data.
It seems that this approach is the less recommended. If you're initializing and executing your AsyncTask within your onCreate() method, I'd suggest using this approach. Basically it consists on saving the data you don't need to restart on a runtime configuration change, so you can handle them afterwards in the onCreate() method and decide which information you want to keep from the previous landscape and which should be restarted.
You can easily avoid the "re-creation" of a fragment, just using the setRetainInstance method on it.
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change). This can only be
used with fragments not in the back stack. If set, the fragment
lifecycle will be slightly different when an activity is recreated:
•
onDestroy() will not be called (but onDetach() still will be, because
the fragment is being detached from its current activity).
•
onCreate(Bundle) will not be called since the fragment is not being
re-created.
• onAttach(Activity) and onActivityCreated(Bundle) will
still be called.
Reference link
You can use this for example in you onCreateView method, like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
setRetainInstance(true);
// ... more of your code
}
While the Activity that contains the fragment still re-creating, the fragment instance will be the same, and will be reattached to the new activity.
Keep in mind that even with setRetainInstance the onCreateView method will be called always (in orientation change) because the fragment need to inflate the view according to the new space/dimensions, so if you are executing your asynctask from that method, you should consider moving it to the onCreate method (which will only be executed once) to avoid the "re-launching" every orientation change.
Hope it helps.
Related
I have started studying Android programming recently after taking a very long break (half a decade) from programming altogether. It's been going well so far.
I have noticed that Seek Bar's progress survives onDestroy event after a rotation (configuration change) happens and this happened on a fragmented activity. Then I created an empty activity and added the widget in the layout and the result is the same (as I expected but still tried due to some code). I also tried overriding the onCreate method and passing a null SavedBundleInstance into super.onCreate() and the result is the same. So I don't know where the Seek Bar's progress location is saved before onDestroy and then passed back unless I am missing some static variable in the SeekBar class or its super classes. (I just thought about that now, I should check it)
Can someone explain why this happens?
Usually, Android Views save their state upon rotation. The same happens for example with EditText. If you input something and then rotate the device, the text will be kept.
It's done inside the specific View class.
But how is this state persisted through orientation changes?
In Activities, for example, you have onSaveInstanceState(Bundle bundle). That method is called by the system before a configuration change occurs. What you do, to persist the state of the Activity, is to store inside bundle the values you want to save. Then, when the Activity is created again, this bundle is passed back to you, for example in the onCreate method. This way you can restore your state.
But what about Views? They use a similar mechanism. If we want the details we have to look at the source code. Let's look at the source code of ProgressBar. It turns out that the View has a onSaveInstanceState, too (this line). You can see there, that the progress is being saved.
I hope this clarifies the mechanism.
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 that is hosted inside of an activity. when user prsses the back button i need to save the model data and have it available the next time user opens the fragment/activity. But just while in the app, it does not need to be persisted to disk. So for example if user destroyed the process, then there is no need to keep the model data, it can be fetched from network again.
what i have tried:
icePick and onSavedInstance calls but these dont seem to kick in when user presses the back button on the fragment. tell me if im wrong.
here is what i have implemented in my fragment:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("myModel", Parcels.wrap(myModel));
}
i am using the parceler library if that makes any difference. I can also convert the code to kotlin if required. when i hit the back button the fragment gets popped off the stack and the activity contain it calls onDestroy but im not getting any call back in onSaveInstanceState. Also when i check in onCreate() savedInstanceState is null. I have not overrided onSavedInstance in the activity, just in the fragment. What am i doing wrong ?
i had a though to use a database to do this, but i just need it while in memory and there should be a way to do this without a DB.
from what i learned if user hits the back button onSaveInstance is not called by the system:
If an activity is in the foreground, and the user taps the Back button, the activity transitions through the onPause(), onStop(), and onDestroy() callbacks. In addition to being destroyed, the activity is also removed from the back stack.
It is important to note that, by default, the onSaveInstanceState()
callback does not fire in this case.
source: here
#onSaveInstanceState of fragment is strictly coupled to activity lifecycle
According to doc
Called to retrieve per-instance state from an activity before being
killed
You operates only with fragments and activity is left untouched,
so this method is definitely can't be used in your case and shouldn't.
My suggestion is to use some kind of persistent storage though interface. It could be in memory storage (any type of singleton, like suggested in comments. It may be scoped to app or activity or to custom case (you have to control manually cache lifecycle) and injected with dagger, for example), shared-preferences based storage, database storage. It is easy to test if you follow dependency injection patterns & use structural pattern like MVP (but it is not a point of this question)
So store the data in the repository on change or in the onPause method (because it is the last guaranteed to call when screen is being gone). And restore it in onCreate
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.
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.