If the user e.g. changes device's orientation during the REST (or any other long running async operation), the fragment is detached from the activity.
So if this fragment uses getActivity() anywhere in the code handling the REST response, it will raise a null pointer.
I can protect all the calls of getActivity() with null checks - but there's still the possibility the activity becomes null between the check-line and the use-line. And also there's lot of places where this is done, so the code will become a mess.
setRetainInstance(true) is not usable if I want to change the layout on orientation change. + There's are also some strange effects with that like: Multiple fragments, setRetainInstance(true) and screen rotation
So this leads me to think maybe its generally bad practice to handle rest calls in fragments?
I have seen some practices where the activity contains a non visual fragment to handle the responses. But I can't put this in the fragments, I guess. So I have to use the activity as a mediator and reach the results to the current fragment?
I just thought, it's cleaner to put everything in the fragment. Since I don't need to modify the code elsewhere. And it's then a self contained entity, which I can put somewhere else without problems. But what do I do with these unreliable references to the context? I mean if the fragment is recreated, I really don't care about the detached fragment - it just has to finish whatever it's doing silently and don't disturb my new workflow with exceptions. And of course I don't want to surround everything with try catch(Exception) since I care about the exceptions thrown in other situations.
I'm usually use the fragment as the display aspect and the activity as the logic aspect. just like MVC- the fragment is the view and the activity is the controller.
So all my fragments has this code:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
listener = (FragmentListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement FragmentListener");
}
}
and every activity that uses this fragment implement the FragmentListener interface.
Once I got a response from the server I'm just populate (or just calling the right method) the fragment, you can do some null checking here, but It will be rare...
You certainly can do that. You probably want to look at AsyncTaskLoader and the LoaderManager (they specifically cope with the lifecycle issues you are talking about). Also be aware that task loader IDs have to be unique to the activity, so if you have multiple fragments running loaders hosted within the same activity, the loader id's cant all be the same ;-)
on orientation change, the activity will be re-created. To protect this use the below code
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" in manifest file ( i.e in activity tag )
and ovrride the method in your MainActivity
#Override
public void onConfigurationChanged(Configuration newConfig) {
// TODO Auto-generated method stub
super.onConfigurationChanged(newConfig);
}
Related
As mentioned here:
"be careful to call getActivity() only when the fragment is attached to an activity. When the fragment is not yet attached, or was detached during the end of its lifecycle, getActivity() will return null."
I have a couple of questions regarding calling getActivity() inside DialogFragment.
What are different scenarios under which a DialogFragment can unexpectedly get detached from its parent Activity or didn't attach at the first place? The thing is I am calling getActivity() inside onPositiveButtonClick's listener and have received a couple of crash reports (Null pointer exception) for it. I am unable to reproduce the crash, screen orientation doesn't seem to do the trick.
What are some recommended guidelines on how to use getActivity() with minimal damage? I have read some other stackoverflow posts which suggest
a) Override onAttach() method.
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
I would prefer this less as it keeps an instance of the Activity. Also, how can I be sure local instance of the activity is never set to null. Would like to know the pros and cons before using it.
b) Will delegating the onClick() implementations to the calling activity using an interface help? If yes, how?
If all of this is unavoidable, I don't see a better alternative than letting the app crash. I can't show a toast since getActivity() is null and would avoid letting the onClick operation fail silently.
Any pointers would be much appreciated. Thank you!
I can't for the life of me figure out why sometimes my Fragments become uncontrollable after my app has been launched, paused, and then resumed.
I hold references to my Fragments like this:
public class MainActivity ... {
public static AccountFragment accountFragment;
....
#Override
protected void onCreate( ... {
accountFragment = new AccountFragment(this);
...
}
I have a custom toolbar with a Spinner which theoretically should allow the user to cause an AsyncTask in the AccountFragment to run. This is implemented like so:
if (accountFragment.getView() != null) {
accountFragment.load()
}
I then create a ViewPager, a FragmentPagerAdapter, etc.
This works completely fine most of the time. The user can select a new item in the Spinner and the AccountFragment will update accordingly. However, sometimes after the app has been stopped and then later resumed, the AccountFragment will not respond at all to anything. Even other views in the app which should affect the AccountFragment are useless. It makes no sense to me because the AccountFragment's view cannot be null, and the AsyncTask which is executed by accountFragment.load() makes some very simple changes to the UI in its onPreExecute which should be very apparent (e.g. Everything disappears and a ProgressBar appears) which is simply not happening.
Any ideas?
There's not enough code to know exactly what's going wrong here, but there are also a bunch of stuff you're doing wrong.
1) Don't store fragment reference in a public static field. Make it just private, or protected at most. Read basic Java manuals for explanation.
2) Don't overload Fragment's constructor, it's discouraged by design. If you need to pass some values to it, do it by using arguments.
3) I guess you're not performing a check during onCreate() method in your Activity if it's being recreated or freshly created. You could end up with two instances of AccountFragment where one of them is in some weird detached state.
...
You should probably spend more time researching basics about Fragment/Activity relationship and how to avoid situations like this.
What you need is override these methods:
onSaveInstanceState(){}
onCreate (Bundle savedInstanceState){}
or onCreateView()
See http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle) . You put all the necessary parameters to bundle in onSaveInstanceState() and restore them in onCreate() or onCreateView().
I am using the v4 Support library in our app, and I have a step-based process in the app that is working fine under normal circumstances. However, we have a requirement that everything needs to work and not crash under memory pressure situations. So I'm using the SetAlwaysFinish library to help with this (https://github.com/bricolsoftconsulting/SetAlwaysFinish). It's been extremely helpful in pinpointing areas the need to detect and handle these kinds of situations, but I've run into one that has me stumped. Keep in mind this works fine under normal circumstances, but I'm explicitly testing with the "setAlwaysFinish" = ON.
Here's my setup: I have a "ProcessActivity" class that hosts the layout for a ViewPager. The ViewPager has an Adapter set, which contains my list of Fragments that will encompass the process. This is in the onCreate() method:
ProcessActivity:
createSteps(); // this creates/populates the ArrayList "processSteps" with Fragments
theAdapter = new ProcessAdapter(this, processSteps); // the ProcessAdapter class extends FragmentStatePagerAdapter, with an additional list of Fragment steps
theViewPager.setAdapter(theAdapter);
There are other pieces, but that is the core of how it's setup. During one of the Fragment steps, I actually need to "break" from the step process and push out to an Activity temporarily to do some logic (and then subsequently return to the step process). I do this as follows, with a ForResult, since I need to be able to handle an OK/Cancel action in the activity when it finishes:
Step4Fragment:
Intent intent = new Intent(getActivity(), ThePushActivity.class);
intent.putExtra(blah..);
startActivityForResult(intent, THE_REQCODE);
Once it pushed onto this view, sometimes the onDestroy() method of both the ProcessActivity and Step4Fragment are called (due to the alwaysFinish). Sometimes it seems to work fine and it calls back into the Step-Fragment process. But usually, what it does is that it will call the ProcessActivity's onDestroy() method, and then it will re-call the onCreate() method with the bundle saved-instance-state populated. This will call the step-creation code above, and it puts the app in a funky state where the last step the user was on is showing, but behind the scenes it's actually on the first step (fragments are, at that point, out of whack and disconnected), and a crash inevitably occurs. It seems at this point, the Step4Fragment is completely disjointed and will crash somewhere if you try to do anything, even though it seems as if it got re-instantiated correctly
Any thoughts on best ways to address this? I think it's fine if I find a way to even just reset things so that it kicks the user back to the first step of the process if a memory issue is occurring. However, my whole concern is the crash, of course.
Let me know if I need to provide more specifics. Thanks in advance for any help!
UPDATE #1:
Just a quick update that I've noticed that the Fragments are re-instantiated and initialized, and it properly falls into the "current" Fragment's onActivityResult() method and does what it needs to do properly. It seems where the disconnect lies is in the base ProcessActivity class, following one of these scenarios. The ViewPager layout shows properly, but everything outside of the ViewPager is not correct (i.e. it is indicating that it's on the 1st step of the process, when it should indicate that it's on the 4th, and the navigation buttons are showing for the 1st step rather than the 4th).
So I'm guessing I need to somehow properly set those elements manually. In digging deeper into this, I may be leaving out pieces of code that are needed for someone to actively help with this. If I could somehow access the state of a ViewPager field, which contains the ability to get the "currently shown fragment", before this whole onDestroy()/onCreate() was called, possibly from the savedInstanceState bundle? That would probably solve my issue. But from inspecting this bundle upon debugging, I pretty much only see the Fragments themselves, and their respective states. I'll keep digging, though.
Regardless, please let me know if anyone has any ideas on how this kind of thing should properly be done (at a high level, of course).
UPDATE #2
I'm seeing that even tho the "current" Fragment seems to be initiated correctly, and the view shows correctly, everything is detached. I can't call any methods on getResources() or getActivity(), etc. I actually was able to get the ProcessActivity 'working' properly based on saving off the step index (int) into the savedInstanceState bundle, and then reloading the UI elements around it. However, I still have this blocker with the current Fragment being detached from the Activity, even though it's seemingly re-instantiated properly.
UPDATE #3
When I follow the direction of this post: ViewPager and fragments — what's the right way to store fragment's state?, I end up with an immediate exception when I try to do the first putFragment(). The exception is: "IllegalStateException: Fragment Step4Fragment is not currently in the FragmentManager". I'm thinking this may have to do with the fact that I'm only keeping one Fragment to the left and one fragment to the right 'active' at any one time (i.e. offScreenPageLimit).
Are you sure that your Fragment isn't being re-instantiated?
All subclasses of Fragment must include a public empty constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the empty constructor is not available, a runtime exception will occur in some cases during state restore.
Turns out that the solution to this was similar to the solution presented in this post: ViewPager and fragments — what's the right way to store fragment's state?
I had a couple things going on that needed to change. First off, I needed to move the Fragments to fields rather than temp variables. Secondly, I needed to avoid instantiating new Fragments each time in the onCreate(). Instead, it should try to pull them from the savedInstanceState as such:
fragment1 = (Fragment1Step) getSupportFragmentManager().getFragment(savedInstanceState, Fragment1Step.class.getName());
fragment2 = (Fragment2Step) getSupportFragmentManager().getFragment(savedInstanceState, Fragment2Step.class.getName());
etc...
and then right after this, I have subsequent checks for each of them; if they are null (i.e. coming in fresh and not from a saved instance), then they will be instantiated new:
if (fragment1 == null) {
fragment1 = new Fragment1Step();
}
if (fragment2 == null) {
fragment2 = new Fragment2Step();
}
In order for this to work, of course, these should also be saved off in the onSaveInstanceState() method:
try {
getSupportFragmentManager().putFragment(outState, Fragment1Step.class.getName(), fragment1);
} catch (Exception e) {
// do nothing
}
try {
getSupportFragmentManager().putFragment(outState, Fragment2Step.class.getName(), fragment2);
} catch (Exception e) {
// do nothing
}
etc...
The reason why I have these in try/catch blocks is because our ViewPager only has an offScreenPageLimit of 1, so some of these will throw exceptions upon the "putFragment()" call if they are not currently in the 'active' stack of Fragments being shown.
It's not extremely pretty, and there may be a better way to handle this.. but this seems to work fine with this now in place.
My code has an Activity, that has a FragmentPagerAdapter that creates 'n' fragments as necessary.
Activity has a loader, and each fragment has its own loader. All loaders have unique ID.
(Activity's loader in fact determines the number of Pages in the adapter)
I keep getting this warning here and there and can't put my finger on what's causing it.
It doesn't seem to be critical, also looking at the LoaderManger's code throwing this warning, but still - warnings are usually signs for bugs..
Had originally used FragmentStatePagerAdapter and then moved to FragmentPagerAdapter, thinking that could somehow be the issue - but obviously it's not.
Posting code would really complicate this and add very little.
Any thoughts?
in your fragment move your initLoader method inside the onActivityCreated method.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
LoaderManager lm = getLoaderManager();
lm.initLoader(LOADER_ID, null, this);
}
I've just finished a few hours long debugging session with the support library.
TL;DR: DO NOT CALL getLoaderManager in Fragment.onCreate, use onActivityCreated!
(this implies that you can't do initLoader before onActivityCreated)
Fragment.getLoaderManager() will lazily get a LoaderManager instance for you from the activity. However for this to be a valid one the fragment already has to be activated (FragmentManager.makeActive), which implies two things relevant here:
the Fragment has been added
(FragmentManager.addFragment)
the Fragment has an internal identifier (mWho)
(makeActive calls Fragment.setIndex)
The latter is really important because when you call Fragment.getLoaderManager() which in turn asks FragmentActivity.getLoaderManager(who, ...) for a real manager. When called from Fragment.onCreate() the makeActive call didn't happen yet so you'll get back a LoaderManagerImpl with mWho == null which is bad, because the framework will reassign the LoaderManager instance to every fragment which has similar lifecycle.
Because of this reassignment the LoaderManager has already been started by one fragment, but another one will try to start it as well, because the activity didn't know which fragment is asking, neither of them had their identity (mWho) yet.
My setup is as follows.
I have a FragmentPagerAdapter called from my Activity which loads two fragments. This is setup within onCreate.
In onResume I call an ASyncTask which loads data from a database, and then calls a callback in my activity onLoadComplete via a load data listener.
#Override
public void onLoadComplete(JSONArray data) {
// TODO Auto-generated method stub
LocalFragment fragmentB = (LocalFragment)getSupportFragmentManager().findFragmentByTag(ListTag);
fragmentB.setList(data);
LMapFragment fragmentA = (LMapFragment)getSupportFragmentManager().findFragmentByTag(MapTag);
GoogleMap our_map = fragmentA.getMap();
fragmentA.plotP(myLocation,data);
}
The fragments are initialized by the Pager, and within each fragments code I set the respective tag e.g in LocalFragment
#Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
String myTag = getTag();
((PagerTest) activity).setListTag(myTag);
Log.d("what",myTag);
}
This allows me to access the fragment, call a function within it which populates a list or populates a map. It works.
What I am now trying to do is account for screen orientation changes.. If while the ASyncTask is running the orientation is changed, the app crashes.
As suggested here: Hidden Fragments I have been trying to implement a hidden fragment which saves the state of my ASyncTask. So what I have done is set it up so in onResume of my Activity i call a function
static LoadDataFromURL the_data = null;
static JSONArray pub_data = null;
private static final String TAG = "RetainFragment";
public RetainFragment() {}
public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new RetainFragment();
fm.beginTransaction().add(fragment, TAG).commit(); // add this
}
return fragment;
}
which essentially saves my data.
Basically what this means is that if i rotate my screen i dont call my ASyncTask again.. the screen just rotates.. it works perfectly.
If however I go back to the main menu and then click on the activity again the screen returns blank (but does not crash). My understanding is that the data is retained as an object in the fragment, but on reloading the activity afresh the data needs to be set again.. I.E onLoadComplete needs to be called to populate the list/map..
So i concluded that if initially after the ASyncTask completes i save the returned data in my hidden fragment onRetainInstance, then i could simply call onLoadComplete and pass it..
The problem is, in this situation seemingly the fragment has not been called yet, as such the tags are null, and calling the callbacks within onLoadComplete crashes the app.
I have been banging my head over this for ages.
My ASyncTask is in a seperate class: LoadDataFromURL
What i want to achieve is as follows - a fragmentviewpager whereby on screen rotate the ASyncTask is retained on rotate/attached to the new activity, and if it has completed before it shouldn't be run again..
Could anyone advise.
Many Thanks
EDIT
Having changed the variables in my secret fragment to public variables, everything has seemingly come together.. BUT because im not 100% sure how/when things are called, I dont fully understand WHY it works..
So.. I call findOrCreateRetainFragment and it either creates a new 'secret' fragment or returns the current instance.
If it is returning a current instance, i dont call my async task again. If it is not, I call my asynctask and load the data.
With this setup, when i load the activity and rotate the screen, it rotates as expected woop.
Now, when i go back to the main menu and then click the activity again, it calls the async task.
My understanding is that on rotate the async task is not called again, and the viewpager is somehow saving the fragments.
On the other hand, when i go back my activity is destroyed, as is my secret fragment, and as such when i click on it again it loads the data. THis is essentially what i want..
Have i understood this correctly?
Thanks
There are a few issues here that you're experiencing (I think).
First of all, the reason your callbacks crash is because they're attached to an old Activity that no longer "exists" after a screen orientation and/or Activity push. If you use onAttach() to attach a callback to your fragment, you must use onDetach() to detach that callback when the Fragment is removed from the Activity. Then, whenever you call the callback, check for a null so you don't send data to a dead object.
Basically, the logic you're trying to use here is:
Start Activity.
Check if your Fragment exists. If it does, grab it. Else, create it.
Retrieve the data if it exists. If not, wait for the callback.
Because of the nature of callbacks (depending on your implementation), you will not receive data until the event fires. However, if the Activity is gone and the event has already fired, the callback won't execute. Thus, you have to retrieve the data manually. When using setRetainInstance(), it's helpful to think of it as this entity detatched from your Activity. It will exist as long as you don't pop the current Activity or push a new Activity. However, your current Activity will be destroyed upon screen orientation changes while the Fragment won't. As such, the Fragment shouldn't rely on the existence of the Activity.
A much more elegant solution to the problem that you may want to look in to is implementing the Android Loader API. Loaders are handy tools that are handled by the system that work is roughly the same way but are more in-tune with asynchronously retrieving data. They work effectively the same way. You simply start your loader and the system with either create one if it doesn't exist or re-use one that already exists. It will remain in the system by the LoaderManager upon configuration changes.
EDIT:
To answer your edit, I guess I'll explain what's happening. It's convoluted, so just tell me if anything needs clarification.
Fragments aren't technically speaking part of your currently running Activity. When you create an instance of the Fragment, you have to call beginTransation() and commit() on the FragmentManager. The FragmentManager is a singleton that exists within the realm of your application. The FragmentManager holds on to the instance of the Fragment for you. The FragmentManager then attaches the Fragment to your Activity (see onAttach()). The Fragment then exists within the FragmentManager which is why you never really have to hold a reference to it within your application. You can just call findFragmentByTag/Id() to retrieve it.
Under normal circumstances, when your Activity is being destroyed, the FragmentManager will detach the instance of your Fragment (see onDetach()) and just let it go. The Java garbage collect will detect that no reference to your Fragment exists and will clean it up.
When you call setRetainInstace(), you're telling the FragmentManager to hold on to it. Thus, when your Activity is being destroyed on a configuration change, the FragmentManager will hold on to the reference of your Fragment. Thus when your Activity is rebuilt, you can call findFragmentByTag/Id() to retrieve the last instance. So long as it didn't keep any context of the last Activity, there shouldn't be any problems.
Traditionally, one would use it to keep references to long standing data (as you are) or to keep connection sockets open so a phone flip doesn't delete it.
Your ViewPager has nothing to do with this. How it retrieves the Fragments is completely dependent on how you implement that Adapter that it's attached to. Usually, retained Fragments don't have Views themselves because Views hold Context data of the Activity they were created in. You would just basically want to make it a data bucket to hold on to the data for the Views to pull from when they're being inflated.