I'm writing my first Android app. Essentially it shows and controls the score of a straight pool match. I've read articles about the lifecycle of an activity but I still have a problem I don't understand:
When I switch back to my app with a running game (so a score different from the initial 0:0 is shown) the activity sometimes loses its state and shows 0:0 instead of the score when I left the app. I overloaded the methods onSaveInstanceState and onRestoreInstanceState. The former get's called when I press the home button of my device. The latter never gets called. I've read that the method only gets called when onCreate is called. The onCreate method doesn't get called although the app needs longer than usual to reload after switching to some other apps in between. So I think the activity does get rebuild but obviously not by onCreate and the saved score is not loaded by onRestoreInstanceState.
Can you explain me what's happening and how to achieve the desired behaviour? Thank you very much!
edit: I was asked to post my onCreate() and onSaveInstanceState() methods. I tried to shorten them in a useful way. Please tell me If there is anything unclear or missing.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final GameSetting gameSetting = getGameSettings();
final GameData gameData = new GameData(gameSetting.getLeague());
game = new GameLogic(gameData);
scoreViews.put(PlayerId.PLAYER_A, (TextView) findViewById(R.id.playerAScore));
}
#Override
protected void onSaveInstanceState(#NonNull final Bundle outState) {
GameDataInstanceSupplier.saveInstance(game.getGameData(), outState);
// inside the method the data gets saved like
// outState.putInt(STATE_SCORE_A, data.getScore(PlayerId.PLAYER_A));
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(#NonNull final Bundle inState) {
super.onRestoreInstanceState(inState);
GameDataInstanceSupplier.restoreInstance(game.getGameData(), inState);
// inside the method the data gets loaded like
// data.setScore(PlayerId.PLAYER_A, inState.getInt(STATE_SCORE_A));
}
I put a "debug output" inside the onCreate() method and it did not appear in the debug log. The same was true for onRestoreInstanceState(). A message was printed inside onSaveInstanceState() when I pushed the home button.
I think your app is not the problem, you see, I came to find this page because I have the same problem switching aplications in my android phone, it's specially noticiable when usig 2 factor authentication and have to switch to text message, mail or other app to get the sent code, when switch back to the code input screen on the browser or app (teams for example) it is no longer there anymore and it has return to the begining, the login screen for example. This is very annoying because I have disable every thing that has to do with energy savings but still have the issue. I discovered that if I open first the message app and then do the authentication while switching very quickly just to see the code in 1 or 2 seconds and then return sometimes it works. It could be memory issue, or may be an unfulfilled politic on the energy savings. I think is probably the last because other explicitly stated properties like setting the screen brightness automatic adjust off and set to maximun works for a while and then returns to a default. It may even be a planned obsolescence. My phone is a Huawei P smart 2019 (POT-LX3) with android 10. I'll may be try a factory reset. Any suggestions are welcome.
Related
I'm making a manga application but I stuck with save current scroll position when user rotate their device (landscape to portrait or portrait to landscape). I'm using RecyclerView and GridLayoutManager. Here is two block code that I have researched on the internet:
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
rvItems.getLayoutManager().scrollToPosition(savedInstanceState.getInt("position"));
}
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("position", ((GridLayoutManager)rvItems.getLayoutManager()).findFirstCompletelyVisibleItemPosition());
super.onSaveInstanceState(outState);
}
I'm using it and either in onResume() or onPause(), none of them are working. So I'm asking for any idea or suggestion that solve my problem? Any comment will be approriate.
P/S: One more problems, Is there any solution for stop loading all resource/data in current activity while user rotates their screen? I mean in my application, there might be numerous data and resource either text and images, as I know, every time user rotate their screen, the function onCreate() and onDestroy() was called which every data in onCreate() function will be loaded again. I need the resource/data only load one time since it's opened, then it won't load resource when user rotates their screen.
My writing might be very bad but now I'm sticking with many problems without any solution I can think or search in internet.
Thanks for reading!
add this to your activity tag in manifest
android:configChanges="orientation"
I am developing the detail view of a product which gives suggestions of similar products (waking the same activity with another product who also has suggestions) more or less like Google Play app detail activity. The problem comes when accessing to multiple related products which causes a OutOfMemoryException (since we are keeping all the previous instances of that ProductDetailActivity on the BackStack).
Is there a way of provide proper products back history without having to keep all the previous activities consuming memory?
I've checked Google play app and I'm not able to see how but it seems that it keeps all the previous activities and it's not causing any OutOfMemory while navigating related apps over related apps.
I had a similar issue. In my case I was using fragments, but I will share the technique so you can use it with your activity.
You can think of the multiple activities like a stack. When the user selects a product suggestion, the current activity is pushed onto the stack and a new activity is started. Conversely, when the back button is pressed, the activity finishes and the previous activity is popped off the stack and made current.
This can consume a lot of memory depending on your application.
The insight that I had was that I could save a lot on memory by managing that stack myself, and only saving the minimum amount of data required to recreate that UI.
So let's start by creating the stack:
private ArrayList<Bundle> mProductStack;
You will need to persist this stack in onSaveInstanceState() and recover it in onCreate(). Let's get those out of the way:
#Override
public void onSaveInstanceState(Bundle outState) {
// ... all your other state saving code
outState.putParcelableArrayList("productStack", mProductStack);
super.onSaveInstanceState(outState);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... all your other activity create logic
if (savedInstanceState != null) {
mProductStack = savedInstanceState.getParcelableArrayList("productStack");
} else {
mProductStack = new ArrayList<>();
}
}
There's a reason for using a list rather than a stack. I had a lot of problems with persisting/unpersisting a Stack<Bundle> around casting and so forth, so I decided to go with the simple ArrayList<Bundle>, which is supported directly by Bundle (putParcelableArrayList(), etc).
Now when the user navigates to a new product, you need to save the current state in the stack, and load your new product into the current activity. Your state might even be as simple as a product ID:
Bundle entry = new Bundle();
entry.putInt("productId", mProductId);
mProductStack.add(entry); // push
// ... load the data for the next product
You may need to persist some actual UI state, for example I had this:
int scrollPos = mNestedScrollView.getScrollY(); // remember where we were in the list
entry.putInt("scrollPos", scrollPos);
Now you just have to handle the back button press:
#Override
public void onBackPressed() {
if (mProductStack.isEmpty()) {
super.onBackPressed();
return;
}
Bundle entry = mProductStack.remove(mProductStack.size() - 1); // pop
mProductId= entry.getInt("productId");
// ... load the data for the previous product
}
I don't know how complex your state is, but keep in mind that all the data types you want to save will need to implement Parcelable. There's a lot of content online (including SO) on this topic.
The thing that seems to have the most impact on memory is images. Memory management for bitmaps is a whole nasty topic unto itself. You may want to implement some type of LRU cache for images so you don't have to re-fetch images for the previous product — unless memory constraints force you to do so.
I have an application where whenever I exit the application via the home hardware button, it should return to the last state the application is in. However, when I launch the application again, the application shows a white screen with only my header bar. And when I click on the header bar's button, the application crashes with the IllegalStateException where the application cannot find the method for the button clicked.
I am currently implementing with Sherlocks Fragment, where the header bar is an action bar. I'm also using HTC Rhyme, Version 2.3 (Gingerbread). The following is the codes for the addition of fragments into my main app.
Codes to add the fragments within the onCreate method in the activity:
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
Bundle bMain = getIntent().getExtras();
String statusCheck = "";
if (bMain != null) {
statusCheck = bMain.getString("statusCheck");
}
if (statusCheck.equals("web")) {
MyWebViewFragment webfrag = new MyWebViewFragment();
trans.add(R.id.container,webfrag, "WebViewFragment");
} else if(statusCheck.equals("traveloguelist")) {
MyTravelogueListFragment travelfrag = new MyTravelogueListFragment();
trans.add(R.id.container,travelfrag, "TravelogueListFragment");
}
trans.commit();
This is the codes when I change a fragment:
MyTravelogueListFragment travelfrag = new MyTravelogueListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.container, travelfrag).addToBackStack(null).commit();
[Edit]
I realized after much reading and running that the main issuei have is that upon resuming the application, the activity is actually created again. Thus, some of the parameters i passed in does not get registered, resulting in the wrong display. I THINK this is the error that is causing that to happen:
Previously focused view reported id "myresID" during save, but can't be found during restore.
However, I don't know how you force the application to remember the previous state of the fragment? Or is there any other way around this problem?
I'm still very stuck with this problem. Will really appreciate it if someone can help me!
After much trial and error and many readings, I finally found a way to sort of solve my problem.
From what I understand, this problem will occur due to the Activity's life cycle. The comment by Tseng in this forum was quite comprehensive:
http://forum.unity3d.com/threads/127794-Android-Apps-crashing-on-resume
It seems that during the time when other applications are invoked when a certain activity is onPause/onStop, Android might free up some of its memory the activity is currently holding on to if there is insufficient memory required. In this case, all the current objects or variable the paused activity is having will be destroyed. Thus, when the activity is back on focus, the onCreate is actually invoked again. Thus, the activity will have no idea which fragment I am currently require.
However, I realized that it will always call the saveInstanceState which is essentially a bundle object. So I did the following:
onSaveInstanceState method
#Override
public void onSaveInstanceState(Bundle bundle) {
//activityFrag is a string object that tells me which fragment i am in currently
bundle.putString("statusCheck", activityFrag);
}
onCreate method
if (savedInstanceState != null) {
getSupportFragmentManager().popBackStack(null, getSupportFragmentManager().POP_BACK_STACK_INCLUSIVE);
//return;
statusCheck = savedInstanceState.getString("statusCheck");
} else {
statusCheck = b.getString("statusCheck");
}
What I have done is to remove all the fragments I have stacked thus far to remove any issues where there is missing information needed. So this is like starting anew again. The status check just determine which fragment the user has last visited.
After much testing, it seems like it does solve my problem. though I wouldn't say it is perfect. One of the main downfall I have is that whenever I change my fragment, I have to update and change my statusCheck to make sure the correct fragment will be called. However, I have to admit this way is a little unorthodox and might not be very correct.
If any of you have any better ideas, please feel free to share!
You can try to implement following:
Use putFragment to save all fragments, currently located in FragmentManager, into bundle in onSaveInstanceState;
And then you can use getFragment to get all previously stored fragments back from bundle in onRestoreInstanceState.
Also... you'll probably need some HashMap that will help to determine the hierarchy of the fragments (in case you have containers and contained fragments) to be saved into bundle as well.
Also... when restoring from bundle you'll need to know keys for all fragment you've put there earlier. Probably, the easiest way is simply to organize an array of keys and put them into bundle when saving the fragment into instance.
This way your saving and restoring will be complete and centralized.
I recently converted my app from an activity based app to a fragment based app. It's a score keeping app, and I was easily able to save and restore score when it was an activity. However, that doesn't seem to be working as a fragment. Here's my code:
#Override
public void onSaveInstanceState(Bundle savedInstanceState){
super.onSaveInstanceState(savedInstanceState);
if(t!=null&&flag==1){
savedInstanceState.putInt("time", t.getTimeLeft());
} else {
savedInstanceState.putInt("time", 0);
}
savedInstanceState.putIntArray("score_array", points);
savedInstanceState.putIntArray("position_array", spinnerPosition);
savedInstanceState.putBooleanArray("checked_array", shouldBeChecked);
flag = 0;
Log.d("MyApp", "savingState");
}
#Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
Log.d("MyApp", "onActivityCreated");
try {
int timeLeft = savedInstanceState.getInt("time");
points = savedInstanceState.getIntArray("score_array").clone();
spinnerPosition = savedInstanceState.getIntArray("position_array").clone();
shouldBeChecked = savedInstanceState.getBooleanArray("checked_array").clone();
((BaseAdapter) ((ListView)getView().findViewById(R.id.missionList)).getAdapter()).notifyDataSetChanged();
if(timeLeft!=0){
flag=1;
this.getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
t = new TimerClass(timeLeft*1000, 1000);
t.setViews((TextView)getView().findViewById(R.id.minuteView), (TextView)getView().findViewById(R.id.tenSecondView), (TextView)getView().findViewById(R.id.secondView), (Button)getView().findViewById(R.id.start_button));
((Button)getView().findViewById(R.id.start_button)).setText(R.string.stop);
t.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
I used this exact same code successfully on an activity in onRestoreInstanceState rather than onActivityCreated and without the try/catch. The problem is I'm getting null pointer errors an time I try and pull something from the bundle. I can see saving state in the log, and then onActivityCreated, but onActivityCreated doesn't seem to be getting the stuff put in the bundle by onSavedInstanceState. I just get a null pointer on every line that calls savedInstanceState.getAnything(). However, from my reading, onCreate, onCreateView, and onActivityCreated all use the same bundle. I have tried moving the code to the other two with no luck.
For anyone who needs a better answer:
Always test for if(savedInstanceState!=null) before accessing it, to avoid null pointer errors.
Also, be aware that onSaveInstanceState() won't be able to save your data in all cases when your fragment is destroyed.
Particularly if your Fragment is pushed onto the back stack, and the device is rotated. In this case, only onDestoryView() gets called when your Fragment is pushed onto the back stack, so when onSaveInstanceState() runs during device rotation, it won't be able to save any data from your Views because they were all destroyed earlier.
See the following thread for a work-around. I think it's useful for everybody developing with Fragments.
Especially DroidT's answer:
https://stackoverflow.com/a/19744402/2517350
I ended up coming up with a fairly hacky work around. Not thrilled with it, since it's not actually solving the core problem. If anyone can help me find that, I'd happy accept that answer over this.
That said, I simply used the activity's onSaveInstanceState and onRestoreInstanceState to save and restore the fragment data.
I have seen the following links before posting this question
http://www.devx.com/wireless/Article/40792/1954
Saving Android Activity state using Save Instance State
http://www.gitshah.com/2011/03/how-to-handle-screen-orientation_28.html
How to save state during orientation change in Android if the state is made of my classes?
I am not getting how should i override the following function :
#Override
public Object onRetainNonConfigurationInstance() {
return someExpensiveObject;
}
In my application i have layout with one editext visible and other editext get visible when the data of first editext validates to true.I have set the visbility of all other editextes and textviews to false and make them visible after validating.
So in my activity if the screen orientation is changed then all the items having android:visibility="false" get invisible.
I have also came to know that when our activities screen orientation changes it calls onStop() followed by onDestroy() and then again starts a fresh activity by calling onCreate()
This is the cause .. But i am not getting how to resolve it ..
Here You can see the screenshots of my application :
in this image all fields are loaded
and in another image when the screen orientation is changed to landscape they are all gone
Any link to tutorial or piece of code will be highly appreciable.
And also my application crashes when a progress dialog is shown up and i try to change screen orientation.How to handle this ??
Thanks
Well if you have the same layout for both screens then there is no need to do so just add below line in your manifest in Activity node
android:configChanges="keyboardHidden|orientation"
for Android 3.2 (API level 13) and newer:
android:configChanges="keyboardHidden|orientation|screenSize"
because the "screen size" also changes when the device switches between portrait and landscape orientation.
From documentation here: http://developer.android.com/guide/topics/manifest/activity-element.html
There is another possibility using which you can keep the state as it is even on Orientation change using the onConfigurationChanged(Configuration newConfig).
Called by the system when the device configuration changes while your activity is running. Note that this will only be called if you have selected configurations you would like to handle with the configChanges attribute in your manifest. If any configuration change occurs that is not selected to be reported by that attribute, then instead of reporting it the system will stop and restart the activity (to have it launched with the new configuration).
At the time that this function has been called, your Resources object will have been updated to return resource values matching the new configuration.
There are 2 ways of doing this, the first one is in the AndroidManifest.xml file. You can add this to your activity's tag. This documentation will give you an in depth explanation, but put simply it uses these values and tells the activity not to restart when one of these values changes.
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout"
And the second one is: overriding onSaveInstanceState and onRestoreInstanceState. This method requires some more effort, but arguably is better. onSaveInstanceState saves the values set (manually by the developer) from the activity before it's killed, and onRestoreInstanceState restores that information after onStart() Refer to the official documentation for a more in depth look. You don't have to implement onRestoreInstanceState, but that would involve sticking that code in onCreate().
In my sample code below, I am saving 2 int values, the current position of the spinner as well as a radio button.
#Override
public void onSaveInstanceState(#NonNull Bundle savedInstanceState) {
spinPosition = options.getSelectedItemPosition();
savedInstanceState.putInt(Constants.KEY, spinPosition);
savedInstanceState.putInt(Constants.KEY_RADIO, radioPosition);
super.onSaveInstanceState(savedInstanceState);
}
// And we restore those values with `getInt`, then we can pass those stored values into the spinner and radio button group, for example, to select the same values that we saved earlier.
#Override
public void onRestoreInstanceState(#NotNull Bundle savedInstanceState) {
spinPosition = savedInstanceState.getInt(Constants.KEY);
radioPosition = savedInstanceState.getInt(Constants.KEY_RADIO);
options.setSelection(spinPosition, true);
type.check(radioPosition);
super.onRestoreInstanceState(savedInstanceState);
}