The guide from Android developers is rather ambiguous.
The default implementation of onSaveInstanceState() saves information about the state of the activity’s view hierarchy, such as the text in an EditText widget or the scroll position of a ListView widget.
These get stored as far as I know:
the Intent that started your activity (found out through testing)
properties of the objects in the Activity's view graph if they have been given an Id, presumably
Fragments that are not in the back stack and which have been set to retain their instance (Fragment#setRetainInstance(boolean))
However, in the API I have not found such contract being described and I am not sure whether this list is exhaustive either. I have not been able to find any documentation which clearly expresses what gets stored.
This is a possible duplicate to this question, however that question doesn't ask for sources or exhaustiveness and the answers there do not provide that. Are developers supposed to just look into the SDK in every single View, Activity, Fragment, AutofillManager etc. they use to see what gets saved and what doesn't? What are the guarantees?
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 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
First some background.
In my edit item activity I have added search functionality to change of one of the item's data fields. (Its manufacturer and make). Since the user can select from quite a large amount of items, we have decided to use search with suggestions to change the manufacturer/model.
Using the normal Android search, from the edit activity I can override the onSearchRequested() method and add the current item's ID as part of the extra data. The actual updating of the data item, is done in the search activity. (I know, not the best idea, but my edit activity doesn't know what the user did in the search activity.)
This works fine for a simple search, but I can't seem to find a place to inject this context data (the item's id) for Search Suggestions.
I have read through the android docs and the closest I have come across is SUGGEST_COLUMN_INTENT_EXTRA_DATA column for the resultant row in my suggestion, but since my search content provider also doesn't have any context of what item I am editing, it doesn't seem like it will solve my problem. The Intent is still launched from the suggestion by the Android system, and I can't seem to get the required context info added to it.
Is this even possible given that search suggestions seems to be geared towards context-less global searches i.e. Android's quick search?
For completeness sake: we are targeting platforms less than Android 3.0, so the functionality of SearchView is not available.
Good news: the Intent that the suggestions launch directly is the same one that the activity sets up in the call to 'onSearchRequested()'. Any context info can be set there. Can anyone find a reference to this in the docs?
I know that Android provides a mechanism to allow developers to save and restore states in an Activity via the following methods:
protected void onSaveInstanceState (Bundle outState)
protected void onRestoreInstanceState (Bundle savedInstanceState)
I've seen numerous examples whereby people show how to save simple state (such as the current index in a listview), and many examples where people say that the listview should just be backed by a data model which will allow the list to be restored. However, when things get more complicated and the user interaction more involved, I've been struggling to find out the correct way to save this state.
I have a listview which contains custom controls for each row. When clicked a row will expand with animation to show additional details to the user... within the additional details view, the user is also able to change certain things. Now I know that if my Activity is destroyed (or my Fragment replaced), the current state reflecting the UI changes and the user's interaction with the list is now lost.
My question is how do I got about correctly saving this detailed state of my listview such that the state of each custom component row is also preserved?
Thanks!
Edit: Thanks for the replies below. I understand that SharedPreferences can be used to store key-value state, however, I guess I am more interested in the view states.... for instance Android will automatically save/restore the viewstate of an EditText field (i.e. its contents) in my activity , so how do I go about achieving the same thing with more complex custom components in a listview? Do I really have to save this state all manually myself (i.e. save exactly which listview rows have been expanded, save all the ui changes the user has made/toggled, etc.)?
I don't like to say this, but it seems that this must all be done yourself. The framework does not offer a pre-made method for "save state of view".
Whether you save the state in a Bundle or SharedPreferences, I think the point is that you need to determine state variables that reflect your current list choices, persist them yourself, and onResume() you need to reintroduce that state into your activity.
... quite a manual process, as you say ...
I am not sure but I assume SharedPreferences is what you might be looking for.
Make use of SharedPreferences in onPause() method.
where you can store all you activities data when it is moving to background.
When ever you want to restore the data fetch from the Shared Preferences.
I read up on how Android handles "configuration changes" - by destroying the active Activity.
I really want to know from Android Team why this is. I would appreciate an explanation on how the reasoning went, because I don't understand it. The fact that it acts in that way puts us all, as I see it, in a world of pain.
Lets assume you have a Activity which presents a number of EditText:s, checkboxes etc. If a User starts to fill that form with text/data and then changes orientation (or get a Phonecall), then all input the User made is gone. I haven't found any way to preserve state. That forces us to make extremely painful coding to not lose all data.
As I see it, you need another "non-Activity" class (or "value-holding" class perhaps) that has one field for each "form element" (EditText, checkbox etc).
For every single "form element" that exists, you then need to attach an Event like "onChanged" (or onTextChanged or something like that) that updates the corresponding field in the "value-holding" class to make sure that for every single character you type (in a EditText for example) is saved at once.
Perhaps you can use some listener (like "onDestroy" or something) and then fill the value-holding class with data.
I have also found this piece of info where they talk about using Bundle, onSaveInstanceState and onRestoreInstanceState, but that also mean that the programmer has to manually save and then later put back the values in the correct place? This approach is a bit less messier than my suggestions above, but still not very nice.
Can someone tell me that I am totally wrong and that this is not how it works and that I totally missed some vital information?
You should read the Application Fundamentals (specifically, Activity lifecycle). Since Activitys must be able to handle being killed at any time due to memory contraints, etc. it's just a cleaner way to handle rotations without adding too much complexity - instead of checking every resource for an alternate resource, re-structuring the layout, etc. you just save your essential data, kill the activity, re-create it, and load the data back in (if you're willing to deal with the extra complexity of managing this yourself, you can use onConfigurationChanged to handle the configuration change yourself.) This also encourages better practices - developers have to be prepared for their Activity to be killed for orientation change, which has the (good) consequence of being prepared for being killed off by memory contraints also.
The contents of an EditText will be saved for you automatically when rotating the screen if you put an android:id attribute on it. Similarly, if you display dialogs using Activity#showDialog, then the dialogs are reshown for you after rotating.
on why part - short answer - because you might have resources that needed to be changed as you've rotated the phone. ( Images, layout might be different, etc )
On save - you can save you stuff to bundle and read it back.
#Override
protected void onSaveInstanceState(Bundle outState) {
String story_id = "123"
outState.putString(ContentUtils.STORYID, story_id);
}
or you can use onRetainNonConfigurationInstance () as described here
http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()
Finally if you don't have anything you want to handle during rotation - you can ignore it
by putting this into your activity in manifest
android:configChanges="keyboardHidden|orientation"
In general, i would read trough article from url above couple of times, until lifecycle is crystal clear.
#Alex's approach above pointed me to a really, really useful solution when using fragments:
Fragments usually get recreated on configuration change. If you don't wish this to happen, use
setRetainInstance(true); in the Fragment's constructor(s)
This will cause fragments to be retained during configuration change.
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)