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.
Related
I need to understand how to use MvxCachingFragmentCompatActivity. I have asked this question before previous question, but I got a piece of example code, which is helpful but not what I needed. What I need is an understanding of how to use it.
First of all I have one activity and all my views are fragments.
My big assumption here is that using MvxCachingFragmentCompatActivity will enable me to restore my application navigation hierarchy if my activity is torn down and needs to be restored. Can someone confirm if this is correct.
If this is correct how do I use it. For example
Do I need to implement Save and Restore state in the view models? Is
there anything else the developer needs to do?
What does the MvxFragmentAttribute parameter IsCacheableFragment
actually do as regards caching fragments?
What performs the action of recreating my fragment hierarchy when an
activity is restored?
It would be great if there was some documentation around this.
I need to know this as my Activity is being torn down and then restored after I use another Activity for a camera feature. When the Activity restores itself the ViewModel for my fragments are null.Also I am finding Close(this) does not work in my view model. I'm sure I am not doing everything I need to do to make this work, but I need guidance on how it is supposed to be used.
Any help would be appreciated, maybe someone from the MvvmCross team. I'm really stuck here. I would prefer a description of the behaviour rather than point to a sample, but both would be great.
[Updated]
So I built a debug version of the V4 and V7 MvvmCross libraries and set about debugging. As far as I can tell as long as you add the following attributes to your fragment class this should set about caching your fragments.
[MvxFragment(typeof(MainActivityViewModel), Resource.Id.contentFrame, AddToBackStack = true, IsCacheableFragment = true)]
[Register("com.dummynamespace.MyFragment")]
Note the lowercase namespace is important, your class name can be mixed case.
However I am still seeing problems after my activity is destroyed and re-created. In my case I am actually seeing my activity destroyed and recreated more than once in quick succession. One example is that I cannot close the view after the activity destroyed and recreated. This seems to be due to the fact that the code in GetFragmentInfoByTag (MvxCachingFragmentCompatActivity class) is returning the wrong information needed to close the view. The close functionality needs the ContentId from the returned IMvxCachedFragmentInfo, however this is returning it as 0. Also the AddToBackStack property is set to false. Below I have listed what is returned in the fragment info
AddToBackStack = false
CacheFragment = true
CachedFragment = null
ContentId = 0
FragmentType = This is set to the correct fragment type
Tag = This is set to the corresponding view model for the fragment
Before the activity is destroyed and recreated the the fragment info is correct.
I am using MvvmCross 4.2.3. Has anyone else experience this?
Update 02/03/2017
I found out that my activity was being destroyed and recreated not due to memory but due to the camera orientation. We found it only failed when we held the camera in landscape mode.
The issue regarding the ContentId being set to 0 was caused by my app not being able to resolve the IMvxJsonConverter implemenation. This occurs when the MvvmCross Json plugin is not installed. Also you have to add the following to your App.cs file so it can be registered
Mvx.RegisterType<IMvxJsonConverter, MvxJsonConverter>();
If this is not done, then the Try.Resolve fails and the code that uses it is skipped over. Sometimes it is done silently other times it outputs a log. IT would seem to me that this should probably be fatal if you expect your app to survive the activoty being torn down and reconstructed.
Also one the MvvmCross Json plugin is installed you have to implement the Save and Restore state pattern in your view models save-Restore
Update new problem 08/03/2017
I am testing the restore of every view in my app. I am doing this by allowing the orientation to be changed which destroys my MvxCachingFragmentCompatActivity and then re-creates it.
When the activity is destroyed my fragment is also destroyed. At this point I tidy up my view model to ensure it will be free'd up and will not cause a memory leak.
However I have hit a problem where when OnCreate is called. It seems to do two things
Get a view model from the MvxFragmentExtensions OnCreate method by
calling into the view model cache
Then calls RestoreViewModelsFromBundle
The problem is that the call to MvxFragmentExtensions OnCreate (1) calls into the view model cache and returns a view model which has not been Started e.g Start() called on it, but this is used to set the DataContext.
After RestoreViewModelsFromBundle (2) is called the DataContext is not set again event though it has gone through the Constructor->Init->RestoreState->Start set up. So I now have a view model which is not setup properly and so my view does not work.
When I took out my code to tidy the view models, I got a bit further as the cached view model set by (1) now had the correct data. But I am hitting other problems because it is attempting to create a new view model due to the call to RestoreViewModelsFromBundle (2). As a short term fix is there anyway I can force the view model created as part of the restore process to be set as the ViewModel
Can someone from the MvvmCross team please help out with some information as to what is happening here and why?
I'm using the viewpagerindicator library (http://viewpagerindicator.com/) to create some sort of wizard for my android app. It works fine and does exactly what I want.
I would like to "extend" the functionality a bit by having "previous"/"next" buttons in my ActionBar - pretty much as in Android's "Done Bar" tutorial - to step through the wizard. Works like a charm, too.
HOWEVER:
I would like to display information about the "next" & "previous" fragment in the ActionBar's buttons. Information I pass to the fragments that live in the ViewPager at the time of their "creation" (actually at the time of their object instantiation - using the classical "newInstance(...)" approach to create the instance of my fragment, store the parameters in a Bundle and extract them in the fragment's "onCreate" method). The same way the template does it, when you create a new fragment for your project.
So, this information is the thing I actually want to display in my wizards button to know what fragment is next and which was last.
The type of this information is not important for my problem. It could be a String or an icon or an int or ... anything else you want.
However, wherever I've tried to access my fragments data, the fragment has not yet been fully initialized (meaning its "onCreate" method has not been called yet).
I've tried it in the host fragment's "onViewCreated" method, because I thought that's where all its subviews should be initialized already (at least their "onCreate" method should have been called, I thought), but it seems that this is handled differently for ViewPager to retain only the number of fragments in memory that was set by setOffscreenPageLimit.
So, what I'm looking for (and probably just missing) is the correct callback method here. One that is called when the ViewPager's next Fragments have been loaded and initialized. If such a callback exists, I could place my little piece of code there to update the text in my "previous"/"next" buttons within the ActionBar.
Any help, comments, ideas are highly appreciated. If needed, I can also try to attach some code sample to better explain my setup, but I think it should be easy enough to understand what my problem is.
Thanks in advance!
P.S.: I also tried to do this by using EventBus to send "onFragmentInitialized" messages from my fragments within in the ViewPager and the hosting fragment. It actually worked, but it does not seems the proper way to do this.
When a Fragment's onCreate Method is called, its already preparing to be displayed, and practically its past the point where its considered a Next or Previous fragment instead its considered current.
A fragment's onCreateViews method is called after committing a transaction in the FragmentManager. which takes less than 1 sec to bring it in front of the user (depending on the device and runtime environment)
But in your case, your data should be initalized outside the Fragment that uses it, and displayed where ever you want by passing the data itself then displaying whatever you want form it.
decouple your data from android objects (Fragment, Activity ...) and you should be able to load, maintain, access it cleanly and without worrying about their callbacks.
The Fragment's arguments can be read and loaded in its onAttach callback rather than onCreate, the Activity will then (after onAttach is complete) get a onAttachFragment callback with the Fragment as a parameter. However, I doubt onAttachFragment will be called when switching between already loaded pages in the view pager.
If not, you could have the fragment notify the activity (through an interface) that it is now active during its onActivityCreated, onViewCreated or similar method.
But it sounds more like the activity should register as a page changed listener to the ViewPager itself, and update its state depending on the page rather than which fragment is active.
As a side note, ViewPagerIndicator is quite old now (hasn't been updated in 3 years), a more modern approach is the SlidingTabs example from Google, which has been built into a library available here: https://github.com/nispok/slidingtabs
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.
So basically I have updated my application from using just activities in a tabBar to using SherlockFragments in a supportActionBar. Not a big deal you would think, and so did I. Everything worked perfectly through all of my tests. However when I released the new version my users started complaining and reporting numerous crashes where fragments throw a NPE when calling getSherlockActivity().
I have read about this problem and it's a problem with restoring instance states and fragments that are no longer attached to any activity but still run for some reason. I haven't found a specific solution for my problem (since I'm using the actionBar to display my tabs instead of a ViewPager).
Now my question is if I can simply discard these fragments that return null when calling getSherlockActivity()? Because they aren't attached to any activity, are they also not visible?
If not, how could I solve this problem? I am already checking the FragmentManager for already existing fragments before creating them, but this isn't sufficient.
Please help me!
You don't need to discard anything. All necessary checks are needed to be done in a Fragment.
You should try to avoid using getSherlockActivity() before onAttach() is called (since it is called before onCreate(), should be no problems with this, but still) and after onDetach() is called.
if (isAdded() && !isDetached())
or
final SherlockActivity a = getSherlockActivity();
if (a != null)
Might work okay.
I'm having a problem instantiating Fragments in my program using the Support Library implementation. Here's a brief description of the task I'm attempting to perform and some of my attempts which haven't yet borne fruit:
The UI of my application is subject to change to meet user preferences. In order to do this, I'm using a Fragment for each different layout and replacing the active Fragment in the UI at a given time as per the user's instructions. Here are some ways I've tried (and failed) to do this:
I've tried adding the Fragments as non-static inner classes in my Activity. This approach worked so long as the user did not rotate the device. As soon as the user rotated the device, the application crashed (this is true for Portrait -> Landscape rotation and for Landscape -> Portrait rotation). Upon checking the issue using the emulator, I was getting an InstantiationException. I checked SO for some help, which led me to:
Implement the Fragment as a static inner class. When the Fragment initiates, it will expand its layout, and then from later in the control flow of the Activity, I can do stuff to the Fragment's subviews (in particular, add listeners to the buttons). Unfortunately this didn't work because I couldn't refer to the Fragment's subviews using [frag_name].getView().findViewById(). Something about referencing static objects in a non-static context. Once again, I checked SO, which led me to:
Implement the Fragment as a separate class altogether from the Activity. This seems to be what the Dev docs on developer.android.com recommend. Upon doing this, everything seems to compile fine, but when I try to refer to the Fragment's subviews (once again, using [frag_name].getView().findViewById()), I get a NullPointerException. When I add System.out.println() statements across my code to find out exactly what is happening, I find that the print statement inside onCreateView in the fragment is never getting fired, which implies that onCreateView is never getting triggered.
So now, I'm stuck. What am I doing wrong? The precise implementation of this isn't as important as learning something from the experience so I can get better at Android development, so if seperate classes are better than static classes or vice-versa, I don't really care which I use.
Thanks.
Figured it out. Turns out that in order to do what I wanted, I had to register the Activity as a Listener to each of the Fragments and pass "ready to enable buttons" messages back and forth between the two. To anyone using this question for further research, the guide on how to do that is located on the Android Developer guide, here: http://developer.android.com/training/basics/fragments/communicating.html