onSaveInstanceState not saving images on rotation - android

I want to save two images, so that they are still displaying in the imageviews after screen rotation. Somehow it does not work and I don't know why. onRestoreInstanceState dind't work either. Can someone help me?
Here is my code:
private byte[] mapImage;
private byte[] photo;
private Bitmap imageBitmap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_entry);
if(savedInstanceState !=null) {
photo = savedInstanceState.getByteArray(STATE_PHOTO);
viewPhoto.setImageBitmap(DbBitmapUtility.getImage(photo));
mapImage = savedInstanceState.getByteArray(STATE_MAP);
viewGPS.setImageBitmap(DbBitmapUtility.getImage(mapImage));
}
#Override
protected void onSaveInstanceState(Bundle outState) {
if(imageBitmap!=null) {
photo = DbBitmapUtility.getBytes(imageBitmap);
outState.putByteArray(STATE_PHOTO, photo);
}
if(mapImage !=null) {
outState.putByteArray(STATE_MAP, mapImage);
}
super.onSaveInstanceState(outState);
}
private void showMapView() {
Bundle getImageOfMap = getIntent().getExtras();
if (getImageOfMap != null) {
mapImage = (byte[]) getImageOfMap.get("map_image");
Bitmap showMapImage = DbBitmapUtility.getImage(mapImage);
viewGPS.setVisibility(View.VISIBLE);
viewGPS.setImageBitmap(showMapImage);
}
}
EDIT:
I put super.onSaveInstanceState(outState); on top of the onSaveInstanceState(Bundle outState) method and now mapImage is being shown after rotation but imageBitmap still isn't. I found out that the imageBitmap is saved but in onCreate it is null.
Does someone know a solution for this?
EDIT: mapImage is now somehow saved without savedInstanceState, because when I delete the mapImage part it is still there after rotation. It didn't use to be like this when I posted this question first. I give up...
I think I'll just use the not so pretty solution of the configChanges in the manifest. Thanks to everyone who tried to help!
Thanks!

IMO it is a threarding issue. Maybe your image size is more. Try saving the bytearray in some other thread. Better use an AsyncTask to save the byre array.

You have a typo. I think you should be checking for imageBitmap != null instead of photo != null:
protected void onSaveInstanceState(Bundle outState) {
if(imageBitmap!=null) {
photo = DbBitmapUtility.getBytes(imageBitmap);
outState.putByteArray(STATE_PHOTO, photo);
}
if(mapImage !=null) {
outState.putByteArray(STATE_MAP, mapImage);
}
super.onSaveInstanceState(outState);
}
EDIT:
Also, make sure that onCreate(), you initialize your viewPhoto (and other variables)
viewPhoto = (ImageView) findViewById(R.id.viewPhoto);
before access it inside the if block.

Unless your app is running on API21+ version of Android, your
public void onSaveInstanceState (Bundle outState, PersistableBundle outPersistentState);
will NOT be called as it simply does not exist on earlier versions of the platform than 21. To support pre API21 devices you must, instead of the above, override the following method:
public void onSaveInstanceState (Bundle outState);
This will work on API21+ as well, so you do not need to override both methods, of course (unless you know you need to deal with PersistableBundle the new one offers).
Can you once confirm about onSaveInstanceState calling means it's called or not?

Related

Instance state serializable loses data

In one of Activities I have HashSet<Integer> mSelectedPositions. I want to save state of this set on screen rotation.
#Override
protected void onSaveInstanceState(#NotNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(SELECTED_TYPES_POSITIONS, mSelectedPositions);
}
And restore it
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!= null && savedInstanceState.containsKey(SELECTED_TYPES_POSITIONS)){
mSelectedPositions = (HashSet<Integer>) savedInstanceState.getSerializable(SELECTED_TYPES_POSITIONS);
}
...
}
The problem is, getSerializable(..) returns an empty HashSet, even then it wasn't empty in putSerializable(..).
What's even weirder, I have almost the same code (with other keys) in other Fragments, and it works fine.
Don't know if matters, but activity in question is a child of MainActivity.
Upd
Part of the problem is in selection flow. On destroy of activity action mode is finished.
#Override
protected void onDestroy() {
if(mActionMode != null){
mActionMode.finish();
}
super.onDestroy();
}
Which triggers
#Override
public void onDestroyActionMode(ActionMode mode) {
mAdapter.clearSelections();
mActivity.nullifyActionMode();
}
in SelectionCallback.
I think, next thing happens:
1. I put mSelectedPositions in outState Bundle, it stores reference
2. Activity is destroyed
3. SelectionCallback clears mSelectedPositions
4. Actual serialization happens with empty HashSet.
So I made some changes — new HashSet with copy of mSelectedPositions data
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(SELECTED_TYPES_POSITIONS, new HashSet<>(mSelectedPositions));
}
And it works like it should.
Upd2
In Fragments I call mActionMode.finish() in onDetach(), which is not called on screen rotation, so mSelectedPositions there remains intact.
Try putting a Json instead of raw HashMap
#Override
protected void onSaveInstanceState(#NotNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SELECTED_TYPES_POSITIONS,new Gson().toJson(mSelectedPositions));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!= null && savedInstanceState.containsKey(SELECTED_TYPES_POSITIONS)){
String data = savedInstanceState.getString(SELECTED_TYPES_POSITIONS);
if(data != null){
mSelectedPositions = new Gson().fromJson(str, new TypeToken<HashSet<Integer>>() { }.getType())
}
}
...
}
The funny thing is that serialization of outState happens after onDestroy() call. And I was erasing data passed to outState in onDestroy.
So, there are two ways to fix that:
• pass copy of data to outState
• don't erase data in onDestroy
Opted for second option. Erasing might be required in some cases in onDetach() of Fragments, but it is not necessary with Activity.

Correct way to handle camera app from fragment without ever crashing

Here is a re-occurring problem that I haven't found a good solution for in the past.
My application is based on a single activity that has multiple child fragments.
What i want to do:
In some of my fragments, I want to take a picture with the phones own camera-app and both show the image for the user and then upload it to my server.
What i do now
Now, i am calling StartActivityForResult with my camera intent which works fine. Then i receive what i need from onActivityResult and are able to show the taken image in an image view and also send it to my server.
The problem
Some times when my onActivityResult is called. My fragment has been uninitiated or just flushed from memory by the OS (As i understand it).
This means that variables now has null-references.
What i have read from similar issues is that OnCreateView() is supposedly to be called before OnActivityResult().
So what I am trying to do here is to save the fragments state to its Arguments in my onDestroyView() and onSaveInstanceState() and then try to restore variables such as the temporary Camera Image FilePath. Here however, the fragment seems to initiate the Fragment with a new Bundle and not the one i've created for it, and causes my app to crash due to my camera file is null.
This is also hard to test as this just happens some times at random.
Code
saveState() is called from onDestroyView() and onSaveInstanceState()
#Override
protected Bundle saveState() {
Bundle state = new Bundle();
state.putSerializable("tempCameraFile", tempCameraFile);
return state;
}
restoreStates() is called in the end by onCreateView()
private void restoreStates(){
tempCameraFile = (File)savedState.getSerializable("tempCameraFile");
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 7777 && resultCode == Activity.RESULT_OK) {
setPendingImage(tempCameraFile);
}
}
private void setPendingImage(File imageFile){
try {
Bitmap bitmap = PhotoUtils.decodeFile(imageFile.getPath(), Utils.convertDpToPixel(40, mActivity));
if(bitmap != null) {
buttonImageChooser.setImageBitmap(bitmap);
}
} catch(NullPointerException npe){
npe.printStackTrace();
Log.d(getClass().getSimpleName(), "imageFile NULLPOINTER!!!! WHYYYY!?");
}
}
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
saveStateToArguments();
}
#Override
public void onDestroyView() {
super.onDestroyView();
saveStateToArguments();
}
private void saveStateToArguments() {
if (getView() != null)
savedState = saveState();
if (savedState != null) {
Bundle b = getArguments();
b.putBundle("savedState", savedState);
}
}
I really hope there is an obvious thing I am doing wrong when using fragments and that someone are able to help me out.
This has been a reoccurring problem that I have solved with a really ugly implementation of destroying and re-creating fragments from my Activity, but I now want to do this the right way.
The problem is you are not saving your state at all.
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
saveStateToArguments();
}
The bundle outState contains all values which will be saved, but your method
saveStateToArguments(); saves the values in another bundle.
The outState and your bundle are not related, so nothing will be saved.
Besides there is no need to call the saveStateToArguments(); in the onDestroyView 'cause the onSaveInstanceState will be called.
So simply change your code to the following:
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putSerializable("tempCameraFile", tempCameraFile);
}
And restore the state in the method onRestoreInstanceState
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
tempCameraFile = (File) savedInstanceState.getSerializable("tempCameraFile");
}
}
Because the lifecycle is the following:
onCreate
onStart
onRestoreInstanceState
onActivityResult
onResume
See State of Activity while in onActivityResult question
Use activity to start the camera app and use activity's onActivityResult to make sure if the fragment exists. If it doesn't reinitialise the fragment.
getActivity().startActivityForResult(intent);
The problem is the system kill background activities on low memory.
Solution :
Save state of Activity or Fragment
/**
* Handled take camera photo on low memory maybe activity on background killed, need to save state.
*/
private Uri cameraMediaOutputUri;
#Override
public Uri getCameraMediaOutputUri() {
return cameraMediaOutputUri;
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putParcelable("cameraMediaOutputUri", cameraMediaOutputUri);
super.onSaveInstanceState(outState);
}
Restore the state and here is the trick as restoring state on Activity is different from Fragment.
Restoring Activity State
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState !=null && savedInstanceState.containsKey("cameraMediaOutputUri"))
cameraMediaOutputUri = savedInstanceState.getParcelable("cameraMediaOutputUri");
}
Restoring Fragment State Fragment Life Cycle
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState !=null && savedInstanceState.containsKey("cameraMediaOutputUri"))
cameraMediaOutputUri = savedInstanceState.getParcelable("cameraMediaOutputUri");
}

Where is a Bundle variable saved in Activity?

public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putInt("x", x);
}
When I save a Bundle, Where is the Bundle variable exactly saved? I can't find it anywhere
In addition to android reference, you can read the source code to understand what has happened here.
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
In the source code, it's clear to see the state will be managed by Application. When the Activity is destroyed, the Application can help save relevant states. But, if you ever met this situation that Application was killed, you would find all states were lost. So, I think all states are kept in memory, not file like preference.
Well, I don't think you will find it and I don't expect to be referenced directly somewhere.
However its content will be available in onCreate(savedInstanceState) when the activity is recreated. Taken from its documentation: savedInstanceState: If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Note: Otherwise it is null
Another place to look for its content is onRestoreInstanceState(savedInstanceState)
i am not sure I think you must set the int,string or whatever you want
so as to save it into int
Let me show you an
EXAMPLE
public void onSaveInstanceState(Bundle state){
super.onSaveInstanceState(state);
Int i = 1;
state.putInt("s",i);
}

NoSuchMethodError Bundle.getString()

I get some wierd logs, which obviously only occurs on Android-Devices with Versions below 3(checked with emulator).
When you change the orientation and onCreate() or onRestoreInstanceState() is called with an Bundle that is not null it crashed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_menu);}
if (savedInstanceState != null) {
mSlug = savedInstanceState.getString(KEY_SLUG, null);
}
}#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_SLUG, mSlug);
}#Override
protected void onRestoreInstanceState(Bundle outState) {
super.onRestoreInstanceState(outState);
if (outState != null) {
mSlug = outState.getString(KEY_SLUG, mSlug);
}
}
The LogCat-Log looks like:
http://i.stack.imgur.com/WbivQ.png
Does anybody got clue what is happening here?
Bundle.getString with 2 arguments is only available in API level 12 and later. You have to specify the default value yourself, rather than passing it as an argument.
You can check this yourself in the future by clicking the "Filter by API level" dropdown and selecting the appropriate API level in the upper right hand of the documentation
Is simple to make
mSlug = savedInstanceState.getString(KEY_SLUG, null);
compatible with olders APIs, just replace it with
mSlug = getIntent().hasExtra(KEY_SLUG) ? savedInstanceState.getString(KEY_SLUG) : null;
Just use this function:
public static String getStringFromBundle(Bundle bundle, String key, String defaultValue){
if (Build.VERSION.SDK_INT < 12){
String returns = bundle.getString(key);
if(returns==null) returns = defaultValue;
return returns;
} else
return bundle.getString(key, defaultValue);
}
EDIT: Fixed, see comment below.
For anybody using MvvmCross, there is a bug as of 4.2.3 which is due to this. Just create your own FragmentCacheConfiguration and implement one of the workarounds in this thread. https://github.com/MvvmCross/MvvmCross/issues/1431

Saving string on orientation change FC's app if changed too quickly

I'm having trouble saving a string on orientation changed. I've tried using onSaveInstanceState()/onRestoreInstanceState() and onRetainNonConfigurationInstance()/getLastNonConfigurationInstance() with no luck.
I have:
#Override
public String onRetainNonConfigurationInstance(){
final String savedType = currentType;
return savedType;
}
and in onCreate() I have:
currentType = (String) getLastNonConfigurationInstance();
This hasn't worked for me yet and currentType is always null after an orientation change. Any suggestions?
Revision
So this is what I currently have and it's still not working:
#Override
protected void onSaveInstanceState(Bundle outState){
outState.putString("savedType", currentType);
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState){
currentType = savedInstanceState.getString("savedType");
super.onRestoreInstanceState(savedInstanceState);
}
I've tried putting the super method calls at the beginning and end of each method in every combination I could think of but am still getting a NullPointerException for currentType when the view is created again.
Also
This is happening in a Dialog, I'm not sure if that has anything to do with it but thought that might be a useful bit of info.
Partial Solution/Partial New Question
So I got this working somewhat like I wanted. There was a statement buried in a method that set currentType to null towards the end of the life cycle when the variable wouldn't be needed again. That's now out of the way. The screen will successfully change orientations but if it's changed between the two successively too quickly it will FC. This is what I used to get it working in this state:
Class Field:
private String currentType;
At the end of onCreate I have:
currentType = (String) getLastNonConfigurationInstance();
And then:
#Override
public Object onRetainNonConfigurationInstance() {
final String s = currentType;
return s;
}
It works fine if the orientation doesn't change and then change back again quickly. I'd like to fix this though because department and retail stores are using/will use this and I can see people dropping it, which will cause an FC (dropped it on my couch to test). I'm only storing one string so what I'm storing doesn't take up much memory. Any suggestions for this new situation?
Have you tried putting it in a Bundle?
private static final String CURRENT_TYPE = "currentType";
private static final String DEFAULT_TYPE = "defaultType";
#Override
protected void onSaveInstanceState (Bundle outState) {
outState.putString(CURRENT_TYPE, currentType);
super.onSaveInstanceState(outState);
}
#Override
protected void onCreate (Bundle savedInstanceState) {
...
currentType = (savedInstanceState == null)? null :
savedInstanceState.getString(CURRENT_TYPE, DEFAULT_TYPE);
...
}
So I got this working somewhat like I wanted. There was a statement buried in a method that set currentType to null towards the end of the life cycle when the variable wouldn't be needed again. That's now out of the way. The screen will successfully change orientations but if it's changed between the two successively too quickly it will FC. This is what I used to get it working in this state:
Class Field:
private String currentType;
At the end of onCreate I have:
currentType = (String) getLastNonConfigurationInstance();
And then:
#Override
public Object onRetainNonConfigurationInstance() {
final String s = currentType;
return s;
}
It works fine if the orientation doesn't change and then change back again quickly. Asking new question about this new bug,

Categories

Resources