I have a viewpager with at least 3 fragments. If the user inputs data into A, then B, then C and goes back to A that data is lost from A.
I think I read somewhere that this has to do with memory management and since fragments only connect to or store adjacent fragments it's killing A when I get too far away.
However I wish to retain the life of each fragment even were I to have >3.
How can I tell the viewpager to never kill a fragment unless explicitly directed?
use
setOffscreenPageLimit(int limit)
on the ViewPager object. "It sets the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state."
Related
When using FragmentStatePagerAdapter is an Android version of "This is better for pagination through a collection of objects for the number of pages is indeterminate. Destroy fragments as the user navigates to other pages, minimizing memory usage "
The operation of this widget to make the creation of a page before the actual position, in my case I consume different services each time I scroll over the tabs and obviously the data can be updated, since paging loads a position towards Forward when I return to the previous position does not perform service request because FragmentStatePagerAdapter does not detect this, please someone knows how to resolve this FragmentStatePagerAdapter
The length of my tabs is dynamic, that's why I'm using 2 Fragment, one for the tabs and another for the pager
https://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter.html
I try to understand your problem, let me guess your previous or next Fragment was not destroy and when you swipe the page back to previous page or next page it does trigger Network request.
Yes, if it the case then Fragment does not remove from activity which mean not lifecycle trigger to the fragment. However if you need to trigger the network then in your FragmentStatePagerAdapter override this method setPrimaryItem then send the callback to your Fragment (The object pass in should be your live instance fragment). Set primary will provide the one which being visible to the user which is actually the current item of ViewPager.
PROBLEM:
I have an Android application that allows a user to browse to a user's profile ViewProfileFragment. Inside ViewProfileFragment a user can click on an image that will take him to StoryViewFragment where various users' photos show up. It is possible to click on a user profile photo that will take them to another instance of ViewProfileFragment with the new user's profile. If a user repeatedly clicks on user's profiles, clicks an image that takes them to the gallery then clicks on another profile the Fragments stack up in memory quickly causing the dreaded OutOfMemoryError. Here is a diagram flow of what I am describing:
UserA clicks on Bob's profile. Inside Bob's profile UserA clicks on ImageA taking him to a gallery of photos of various users (including Bob's). UserA clicks on profile of Sue then on one of her images - process repeats, etc, etc.
UserA -> ViewProfileFragment
StoryViewFragment -> ViewProfileFragment
StoryViewFragment -> ViewProfileFragment
So as you can see from a typical flow there are lots of instances of ViewProfileFragment and StoryViewFragment piling up in the backstack.
RELEVANT CODE
I am loading these in as fragments with the following logic:
//from MainActivity
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
ft.replace(R.id.activity_main_content_fragment, fragment, title);
ft.addToBackStack(title);
WHAT I'VE TRIED
1) I am specifically using FragmentTransaction replace so that the onPause method will be triggered when the replace takes place. Inside onPause I am trying to free up as many resources as I can (such as clearing out data in ListView adapters, "nulling" out variables, etc) so that when the fragment is not the active fragment and pushed onto the backstack there will be more memory freed up. But my efforts to free up resources is only a partial success. According to MAT I still have a lot of memory that is consumed by GalleryFragment and ViewProfileFragment.
2) I've also removed the call to addToBackStack() but obviously that offers a poor user experience because they can't traverse back (the app just closes when the user hits the back button).
3) I have used MAT to find all of the objects that I take up a lot of space and I have dealt with those in various ways inside the onPause (and onResume) methods to free up resources but they are still considerable in size.
4) I also wrote a for loop in both fragments' onPause that sets all of my ImageViews to null using the following logic:
for (int i=shell.getHeaderViewCount(); i<shell.getCount(); i++) {
View h = shell.getChildAt(i);
ImageView v = (ImageView) h.findViewById(R.id.galleryImage);
if (v != null) {
v.setImageBitmap(null);
}
}
myListViewAdapter.clear()
QUESTIONS
1) Am I overlooking a way to allow a Fragment to remain on the backstack but also free up its resources so that the cycle of .replace(fragment) doesn't eat up all of my memory?
2) What are the "best practices" when it is expected that a lot of Fragments could be loaded onto the backstack? How does a developer correctly deal with this scenario? (Or is the logic in my application inherently flawed and I'm just doing it wrong?)
Any help in brainstorming a solution to this would be greatly appreciated.
It's hard to see the whole picture (even tho you have shown us a lot of information), without concrete access to your source code, which I'm sure it would be impractical if not impossible.
That being said, there are a few things to keep in mind when working with Fragments. First a piece of disclaimer.
When Fragments were introduced, they sounded like the best idea of all times. Being able to display more than one activity at the same time, kinda. That was the selling point.
So the whole world slowly started using Fragments. It was the new kid on the block. Everybody was using Fragments. If you were not using Fragments, chances were that "you were doing it wrong".
A few years and apps later, the trend is (thankfully) reverting back to more activity, less fragment. This is enforced by the new APIs (The ability to transition between activities without the user really noticing, as seen in the Transition APIs and such).
So, in summary: I hate fragments. I believe it's one of the worst Android implementations of all time, that only gained popularity because of the lack of Transition Framework (as it exists today) between activities. The lifecycle of a Fragment is, if anything, a ball of random callbacks that are never guaranteed to be called when you expect them.
(Ok, I am exaggerating a little bit, but ask any Android seasoned developer if he had trouble with Fragments at some point and the answer will be a resounding yes).
With all that being said, Fragments work. And so your solution should work.
So let's start looking at who/where can be keeping these hard references.
note: I'm just gonna toss ideas out here of how I would debug this, but I will not likely provide a direct solution. Use it as a reference.
WHAT IS GOING ON?:
You're adding fragments to the Backstack.
The backstack stores a hard reference to the Fragment, not weak or soft. (source)
Now who stores a backstack? FragmentManager and… as you guessed, it uses a hard live reference as well (source).
And finally, each activity contains a hard reference to the FragmentManager.
In short: until your activity dies, all the references to its fragments will exist in memory. Regardless of add/remove operations that happened at Fragment Manager level / backstack.
WHAT CAN YOU DO?
A couple of things come to my mind.
Try using a simple image loader/cache lib like Picasso, if anything to make sure that images are not being leaked. You can later remove it if you want to use your own implementation. For all its flaws, Picasso is really simple to use and has come to a state where it deals with memory "the right way".
After you have removed the "I may be leaking bitmaps" problem out of the picture (no pun intended!), then it's time to revisit your Fragment lifecycle. When you put a fragment in the backstack, it's not destroyed, but… you have a chance to clear resources: Fragment#onDestroyView() is called. And here is where you want to make sure that the fragment nullifies any resources.
You do not mention if your fragments are using setRetainInstance(true), be careful with that, because these do not get destroyed/recreated when the Activity is destroyed/recreated (e.g.: rotation) and all the views may be leaked if not properly handled.
Finally, but this is harder to diagnose, maybe you'd like to revisit your architecture. You're launching the same fragment (viewprofile) multiple times, you may want to consider instead, reusing the same instance and load the "new user" in it. Backstack could be handled by keeping track of a list of users in the order they are loaded, so you could intercept onBackPressed and move "down" the stack, but always loading the new/old data as the user navigates. The same goes for your StoryViewFragment.
All in all, these are all suggestions that came from my experience, but it's really hard to help you unless we can see more in detail.
Hopefully it proves to be a starting point.
Best of luck.
It turns out that fragments share the same lifecycle as their parent activity. According to the Fragment documentation:
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle. For
example, when the activity is paused, so are all fragments in it, and
when the activity is destroyed, so are all fragments. However, while
an activity is running (it is in the resumed lifecycle state), you can
manipulate each fragment independently.
So the step that you took to clean up some resources in onPause() of the fragment wouldn't trigger unless the parent activity pauses. If you have multiple fragments that are being loaded by a parent activity then most likely you are using some kind of mechanism for switching which one is active.
You might be able to solve your issue by not relying on the onPause but by overriding setUserVisibleHint on the fragment. This gives you a good place to determine where to do your setup of resources or clean up of resources when the fragment comes in and out of view (for example when you have a PagerAdapter that switches from FragmentA to FragmentB).
public class MyFragment extends Fragment {
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
//you are visible to user now - so set whatever you need
initResources();
}
else {
//you are no longer visible to the user so cleanup whatever you need
cleanupResources();
}
}
}
As was already mentioned you are stacking items up on a backstack so it's expected that there will be at least a little bit of a memory footprint but you can minimize the footprint by cleaning up resources when the fragment is out of view with the above technique.
The other suggestion is to get really good at understanding the output of the memory analyzer tool (MAT) and memory analysis in general. Here is a good starting point. It is really easy to leak memory in Android so it's a necessity in my opinion to get familiar with the concept and how memory can get away from you. It's possible that your issues are due to you not releasing resources when the fragment goes out of view as well as a memory leak of some kind so if you go the route of using setUserVisibleHint to trigger cleanup of your resources and you still see a high-volume of memory being used then a memory leak could be the culprit so make sure to rule them both out.
I want to save the fragments state, and after referring all over, I came across two ways of doing so,
Retaining the state of fragment using setRetainInstance(true) in onCreate() method of fragment
and overriding Fragment's onSaveInstanceState(Bundle outState) method.
I tried using both and both worked for me.
I was wandering, are there any specific use cases, when to use any of them?
The major difference between overriding Fragment.onSaveInstanceState(…) and
retaining the fragment is how long the preserved data lasts. If it needs to last long enough to survive configuration changes, then retaining the fragment is much less work.
This is especially true when preserving an object, you do not have to worry about whether the object implements Serializable.
However, if you need the data to last longer, retaining the fragment is no help. If an activity is destroyed to reclaim memory after the user has been away for a while, then any retained fragments are destroyed just like they are not retained.
For an app with a small list of say quiz question,having users start over may be an acceptable choice.
But if the quiz had say 100 questions, Users would rightly be irritated at returning to the app and having to start again at the first question. You need the state of the fragment to survive for the lifetime of the activity record. To make this happen, you would use onSaveInstanceState(…).
Hope this clarifies.
The difference is how long the preserved data is retained. setRetainInstance(true) is basically useful for retaining data while a configuration change occurs (like rotation) for a very brief instant.
If the data needs to be retained while the user navigates to another app (or home screen, etc) then you should override setRetainInstance (because Android may reclaim your Activity's memory.)
I have an application that has three fragments: Records, Chart, and PieChart. These fragments are stored in a ViewPager in a single activity and the user can swipe between them. The Records fragment has a table where the user can enter data. When the user enters data, the other two fragments need to be updated because they reference this data. Originally, I wanted the fragments to update as they were swiped to, but since they're swipeable I can't use onPause() and onResume() because onPause() (and thus onResume()) only gets called when a tab is 2+ screens away. So I decided to have the Records fragment send a callback to the Activity to tell the other fragments to update themselves when the Records fragment is updated. I keep all the fragments in memory and this works fine until the app goes into the background. When the app comes out of the background, if the user enters data into the table, the app crashes because all of the variables in the other fragments are null. Why is this happening? I am not using saveinstancestate anywhere in my app. Should I be?
Going by the example from here:
http://developer.android.com/reference/android/app/Fragment.html
You should save all your data that's backing each of your UI components in the Bundle of your onSaveInstance() of your Fragment. Then in the onActivityCreated() callback, restore the data.
Let's say I have a list of homogenous items which is likely to be changed in the lifetime of my Activity by user interaction or OS events. The Activity contains a FragmentPager which shows a number of ListFragments.
These fragments share the previously mentioned data but display it in different ways. E.g. they differ in sorting order or display only a subset of the data. Currently each fragment keeps a separate list containing the respective part of the data in the respective order.
When the data changes, basicly every fragment has to be updated. This means resorting or adding/removing items from some of the fragments. What is the best practice to keep the data in the different fragments consistent?
Currently I have some sort of an observer object, which is notified when something changes and subsequently notifies the connected fragments. But there are a couple of problems:
When the app just started, some of the fragments haven't been created by the FragmentPager, so notifying them is impossible.
When swiping through the fragments some of them get paused. In this state, they can't update their list. Should they disconnect from the observer in this case? This leads to:
When a change happens, while a fragment is disconnected, it basicly misses it.
And so on...
If I understood your ViewPager shows the same data (or it's portion) but in different views. So, I belive ViewPager shouldn't act in any way when data is changed, it's responsibility of Adapter.
About points below you said:
a) creating of fragments inside ViewPager can be managed by you. Just see javadoc of ViewPager::setOffscreenPageLimit(int limit) method.
b) I think you should do nothing with UI when data changed but fragment is in paused state. If you want to update do it in onResume(). Or better to set some field in DB (if you have) to "updates present" state and check it when Activity(Fragment) appears.
c) As in previous option - if fragment disconnected just ignore updates. Or if you really interested in that update use sticky BroadcastReciver (be carefull sticky BR is expensive thing)
You can keep your data in Application class, update only visible fragments when data has changed, and always ask for the new data in Fragment's onResume(), that'll do it
What I would do is have each ListFragment use a Loader to load its data. Then, instead of having the observer notify the Fragment (which might have been killed) to refresh its data, register an observer for each Loader so that it will know when the data source has changed, and will re-query when one has been detected. (If your data source is an SQLite database and you are using a ContentProvider, the CursorLoader will do all of this for you).
This is the implementation I would recommend because
Each Fragments behavior remains self-contained (i.e. each is a re-usable component that is not tied to any specific ViewPager or Activity).
It avoids the complexities of having to deal with potentially destroyed Fragments within your ViewPager.
If you need a quick fix, you could probably get away with forcing the Fragments to remain in memory using ViewPager#setOffScreenPageLimit(int limit) as Ivan suggests... however, this isn't as clean of a solution in my opinion.