onSavedInstanceState vs. SharedPreferences - android

I have 7 activites all with back and forth navigation buttons between the rest; activites consist of editTexts, Spinners, textViews, TimePickers, DatePickers, and checkboxes.
I want all UI to be present and saved through navigation of an application instance; however on application termination everything needs to default.
My 8th activity collects all UI and places into an email . . .fyi
I have read alot about both onSavedInstanceState & SharedPreferences way of saving the data as activities go back and forth . . .
Which would be better for me?

It will depend on how you want to manage the data. Both options (and more) are feasible:
If you want to fill once and keep the data even if the app gets killed, use SharedPreferences.
If it's volatile data that will have to be reentered differently some other time (i.e., days later), then use onSavedInstanceState.
If you want to keep multiple datasets on the same device, then use a SQLiteDatabase

SharedPreferences
Use for things that should always be remembered, no matter if the phone is turned off (eg for settings chosen in the settings screen of your app
onSavedInstanceState
Use this for remembering things about the current state of your activity such as the currently selected tab on the screen. This allows you to recreate the same state after a rotation or if the app was killed due to low memory.
The things saved in onSaveInstanceState will be forgotten after reboot, and when starting a new instance of an activity they will not be passed, so they are only for remembering the state of the activity
onRetainNonConfigurationInstance
Use this for storing objects which take a long time to load so that you don't have to load them again when the phone is rotated.

Related

Can I trust my global variables to always be preserved during screen rotation? (Android / Kotlin)

Is it bad practice to use Global variables instead of bundles to preserve state between screen rotations?
I really like just using global variables to keep state. But since everything suggests using bundles, should I do that? I don't know what pitfalls I could be thinking of.
Here is a snippet of code that demonstrates global variables dont change.
I have to check that the LEVEL_DEFS list is 0 before adding to it, otherwise every screen rotation adds duplicate levels (where I originally assumed I would have to re-add the levels after rotation)
`
val LEVEL_FILES = arrayOf("levels/lv1.txt", "levels/lv2.txt", "levels/lv3.txt")
val LEVEL_DEFS = mutableListOf<LevelDefinition>()
fun getAllLevels(context: Context): MutableList<Level> {
if (LEVEL_DEFS.size == 0) {
for (filename in LEVEL_FILES) {
LEVEL_DEFS.add(LevelDefinition(getTextFileString(context, filename)))
}
}
}
`
When I google screen rotation, all the results suggest that you have to use a bundle to save state.
I also asked chat gpt-3, which insisted that I must use a bundle to save the state during rotation.
Yet, it is clear to me that global variables do not change during rotation. I have used many simulated and real devices and they all behave the same way.
Your global variables will live as long as their scope is in memory, so it depends where you're storing them - if they're at the top level (e.g. defined in the top level of a file, or in an Application component) that state will exist until the process is destroyed.
If your state is stored in an Activity, then that Activity will be destroyed on rotation, so typically you'd use the Bundle passed into onSaveInstanceState to store any data you need, and then restore it when appropriate.
If your app is in the background, its process can be destroyed at any time by the system - in that case, you'd lose your global variable state. For an Activity, those save state lifecycle methods would be called, so you'd still store in the Bundle and receive that Bundle when the app is opened again. The process will have been destroyed, but if you're set up to store and restore that state, you'll handle it just like with a rotation. (This is a different situation to the user actually closing the app, which considers the next time it's opened to be a "fresh start" and doesn't provide any stored state.)
You can also use View Models to store state, which survive rotation but require using SavedStateHandle to behave like the Bundle and survive process death (but again, not actual restarts). More info about your options for storing this transient state here.
So what this means is it depends exactly what you're storing, how long it needs to stick around, and if it's allowed to be destroyed when the app is in the background. That last one is usually a "no" which makes global variables a bad fit in most cases.
You'll need some way to persist that data (e.g. SharedPreferences) and at that point you may as well use that as your state, rather than storing it there and in a variable. Up to you though! Persisting data is also how you'd handle state surviving fresh start app restarts too, e.g. storing a high score that shouldn't disappear when the user closes the app.
(Also I'm not sure what asking a chatbot for advice is worth, no matter how much it "insists"! But that's a whole other topic)
Global variables persist as long as your application is running - however when it is in the background you have no guarantee that the Android OS won't stop and re-start your application (which would lose any global or static variables). Once your application goes to the background, you can no longer be certain they will stick around.
What you have posted looks like it would be fine though if you only access it through the getAllLevels method (since it re-creates it when needed). However, if you are adding levels somewhere else, those could be lost.
If you want to be able to add levels, one possible solution would be to load the data from SharedPreferences inside getAllLevels when the list is empty - and update the SharedPreferences any time levels are added.

Variable values reset after minimizing app

I'm making this android app that acts as a calculator for a game. The calculator will tell you the cost of everything you selected and also show you a total price.
For example, if you select AR on the spinner, it will say it costs 1200. If you select WS, it shows the cost as 2400.
However, if this app is minimized for a great deal of team (30 minutes or more), the price values get reset to default (0). The spinner is still be on AR, but the price says 0. I need to click on the spinner again before it recalculates the value.
Is there a way for me to refresh it or prevent the loss of values?
It is due to that View is refreshed because when you hit the home button it will call the onPause() method then if you open another app it will then grant some memory in the others app and instead of going back to the onResume(), it will go to onCreate() due to the memory management of android.
solution:
you need to save all your data in the saveInstanceState of protected void onSaveInstanceState(Bundle outState) {
method then in Oncreate get all the saved data
You need to save your data in saveInstanceState method and then restore it in onRestoreInstanceState. After that you need to fill fields as it was in view.
Example
This is due to the nature of mobile devices having relatively limited resources.
You should save your data somewhere more durable. You might find this article on general Data Storage to be useful. This question should be relevant too: How do I save an Android application's state?
onPause in Activity - save your values in SharedPreferences or in Application (not Activity).
onResume in Activity - update select items of your spinners from this values.
You can save your data to Shared Preferences and in onResume() method you can set that value back to the respective field.
Changed text at the runtime in textview but after relaunching application retrieving default data in textview in android

Save state for particular duration- android

I have an android requirement where i have created a form like structure have radio buttons, edit texts and so on. When the user make half entries to the form and say moves away from the page either by back button or battery off. When he returns to the form, the same state of half filled form should re appear. Please suggest if android has internal functionality to save the form state and restore when the user revisits. Also, the stored contents should be saved only for particular duration say one hour after which the fresh form should appear. Please help with possible methods applicable for me to start with.
I recommend reading the Android Activity lifecycle. From the webpage:
"In addition, the method onSaveInstanceState(Bundle) is called before placing the activity in such a background state, allowing you to save away any dynamic instance state in your activity into the given Bundle, to be later received in onCreate(Bundle) if the activity needs to be re-created. See the Process Lifecycle section for more information on how the lifecycle of a process is tied to the activities it is hosting. Note that it is important to save persistent data in onPause() instead of onSaveInstanceState(Bundle) because the latter is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation."

How to deal with retained data after Activity comes to foreground when using more than one Activity?

At the moment I'm a little bit confused about the lifecycle management in Android. There are at least 4 possibilities to resume retained data after an Activity comes back to the foreground:
Android handling: If there is enough memory, Android stores and resumes the important data (checked radio buttons, text of EditText,-... and so on) after Activity restart, the user has the same state as before as the Activity went into background.
onPause, onResume: Overriding onPause and save the important data to a database or text file and resume it when onResume is executed next time.
onSavedInstance(Bundle), onRestoreInstance(Bundle): I can save the data as key-value-pair into bundles and restore them after onRestoreInstance is executed.
onRetainNonConfigurationInstance(), getLastNonConfigurationInstance(): I handle all my storage issues in one big object and read getLastNonConfigurationInstance() out when onCreate is executed.
Although it is confusing which approach is best, I guess it relies on development experience to know when to use which possibility. If you have some good examples for each I would be glad, but this is not my question. I wonder how to deal with all that when I have different Activities and one Activity will be killed by Android when it pauses in background:
In my case I have a MainActivity and a MessageActivity. The MessageActivity consists of a ViewSwitcher which consists of two states. State one is a radio button choice list. State two is an EditText with two buttons (send and abort). When I monkey test each state, hit the Android home button, and restart the application, the right Activity with the right state and the old data comes into foreground, when I leave the handling to Android. So that works.
But what happens when Android destroys the MessageActivity in background:
If I use the Android way, the data is lost and I guess MainActivity (instead of MessageActivity->state(1 or 2)) will start next time after I relaunch the application (is that correct?). So when I'd like to keep the data of MessageActivity, I have to use one of the other three possibilities.
How to do that neatly, when the application entry point (so the MainActivity) differs from the last active Activity. The problem is that I have to resume a special Activity with a special state of ViewSwitcher. I could start MessageActivity out of MainActivity with startActivity(Intent) in onStart() or onResume() method (because MainActivity is probably the entry point) but then I run into a lot of logical problems in Lifecycle management. Due to this fact I don't think that this is the right way to do that.
But, what's the right and best way to do that?
I guess MainActivity (instead of MessageActivity->state(1 or 2)) will start next time after I relaunch the application (is that correct?)
No, I don't believe this is correct, depending on what your code does in onCreate(). It certainly doesn't need to be correct if you go about things the right way. A simple way to test this is to rotate your screen, which recreates the running activities, unless you have overridden the default configuration change behaviour.
I recommend reading this section in the android docs carefully:
http://developer.android.com/guide/topics/fundamentals/activities.html#SavingActivityState
In particular:
even if you do nothing and do not implement onSaveInstanceState(), some of the activity state is restored by the Activity class's default implementation of onSaveInstanceState(). Specifically, the default implementation calls onSaveInstanceState() for every View in the layout, which allows each view to provide information about itself that should be saved. Almost every widget in the Android framework implements this method as appropriate, such that any visible changes to the UI are automatically saved and restored when your activity is recreated. For example, the EditText widget saves any text entered by the user and the CheckBox widget saves whether it's checked or not. The only work required by you is to provide a unique ID (with the android:id attribute) for each widget you want to save its state. If a widget does not have an ID, then it cannot save its state.
What this means is, that so long as you don't force any UI state in any onCreate() calls, your activity stack and UI state will be restored.
Personally, my preferred approach is to keep as little state as possible in member variables of my activities, saving and restoring it with onSave/RestoreInstanceState(), and relying on the default implementations to save the rest of the UI state (text box contents, etc). Data that should persist between sessions I commit straight to my DB or preferences as soon as it's changed (e.g. in the on-click handler). This means I don't need to worry about the activity lifecycle for that. As much as possible, my UI just presents a view of the data in my DB (using CursorAdapter etc.).
Edit:
Regarding restoration of the whole activity stack:
When the user leaves a task by pressing the HOME key, ... The system retains the state of every activity in the task. If the user later resumes the task by selecting the launcher icon that began the task, the task comes to the foreground and resumes the activity at the top of the stack.
(See http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html)
It's not my attempt for a best answer, but it's too long to get in the comments section.
First I will suggest not to rely on the "Android way" - this will result in inconsistent application behavior depending on the free memory of the device - bad practice.
My suggestion is to save your state-dependent data in key-value pairs in SharedPreferences, every time you go into onPause() in your MessageActivity. Store a flag in SharedPreferences, which indicates which was the Activity that was last opened (if you only have two Activities you can easily go 0/1 or true/false flags).
When you re-launch your application, it's normal to start the Activity marked in your AndroidManifest.xml as "entry point". So naturally you'll check the flag in onResume() in your MainActivity and start the other Activity if needed. In MessageActivity's onResume() check the values in SharedPreferences and fill in what's necessary...
If your application is "resumed" to the last Activity in the ActivityStack this will call onResume() in the last Activity in the ActivityStack.
The way I have handled an issue like this in the past, is to have a service running in the background, which handles the flow of information from different activities via either Intents and listeners (preferable, since they are the most easily decoupled solution), or if you are extremely careful, and the only viable solution for some reason is to store the data through direct property access or method calls, you can use static properties/methods on the service class as well. However, I would strongly recommend using the Intent/listener method as it is generally more flexible, thread safe, and decoupled. Additionally, it is wise to make sure that not much is happening at any point in time (in other words, only use this service for Intent handling) when it's not needed, otherwise the Service will tend to hog CPU time as well as RAM, when it's not really needed.
Some resources to look at when it comes to this approach would be IntentService and its related classes, including the superclass, Service. IntentService, however, it is worth noting handles a few more things about async Intent processing, etc that Service does not automatically come with.Hope this helps you!
login.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String name=username.getText().toString();
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor editor = settings.edit();
editor.putString("username", name);
if(name.equals("xxx")) {
Intent intent=new Intent(currentactivity.this,nextactivity.class);
intent.putExtras(bundle);
startActivityForResult(intent,0);
}
}
});

Android - opening series of activities after restart

there is my situation. I have same activities which goes one by another, no matter what they are doing. Lets name them from the start of alphabet. When users uses my application, he goes through activities and makes his own path between them, so he could with back button go back in respecitve back order.
He starts with act. A - D - F and with back button he goes back as from F to D and A. Ok. Now, when Android system resolves the application is no longer in use or needs lot of RAM in some particular time, system kills it. My goal is to find, how to restore application to its former state including order of opened activities?
It might not be clear, so here is the example:
User has open activities A (login) - D - F - G, minimize it, after some time, app is killed. When he start this application again, he needs to login at activity A and than he has to see activity G (= he was there last time), and when he push back button, he will go do activity F, then activity D and so on... Is like revieving an row of activites. I know I have to persist all the information stored in my activities (D, F, G), but is it acutally possible to persist app state like that?
Thanks for any comment on this
Solution:
I am tracking flag, which identifies the state my application is in. If it is s 0, it means I am opening new activity normally. On start of each activity I put into shared preferences string, which contains all my activity history. Each activity has it's own id (again sharedPref). In another shared pref I am saving as a String formular data (or data with GUI), when onPause occurs. I set flag as a 1. When app starts and flag is 1, I revive application stack from sharedPref. Set data for each of them from another Shared Pref. And that's it, application state is revived :-)
You can persist anything you need to, it just a matter of how and what is going to be beneficial. There are multiple techniques that have been used to persist state over the years. Nearly all of them are available to you, but will require careful management on your part. Depending on what your application does, there may be special tricks available to you, as well.
Step 1
Determine what each Activity needs in order to run effectively. Determine what you can recalculate and what you absolutely should not recalculate. For instance, if one of your Activities is a Cursor Adapter of some kind and works according to a key to a table, you don't need to persist the entire Activity, you simply need to hold onto whichever _id relates to that particular Activity run.
Step 2
Since you are wanting to track Activity history, you will need some representation of that history. What you are proposing is a stack model, so you will want to write your own stack object and find an easy way to identify each activity in that stack. Do not try and save the actual Activity references as this will invariably lead to leaks. You can then save this stack to a database, shared preferences, file or even parcel it to a bundle. Integer constants that identify each Activity might be one way to accomplish this.
Step 3
Decide on your method of save, and build the appropriate save and load methods for your stack and each Activity.
Step 4
Override the Back button to retrieve the top Activity identifier and its appropriate data on the stack. (As a note: your stack might be better placed in an extended Application) Then start the next Activity with its required data.
Step 5
When your "login" Activity (or Application) starts, load the stack. When authentication completes, reload the top Activity on the stack, passing its required data through Intent Extras. You don't have to open ALL of the Activities at once, just the ones that the user is on.
Step 6
In your onCreate or onWindowAttached for each Activity, have it add itself to the stack. In your onDestroy for each Activity, have it remove itself from the stack. Since you are persisting your data, you can easily finish() to indicate that the Activity is complete.
Step 7
In your onPause for each Activity have it save the state that you feel is important. You can even save the scroll position and just have it rescroll when the Activity restarts. In your onCreate have it regain its state via the extras that you supplied.
It is really as simple as all of that. If you need some samples, I can gladly provide.
FuzzicalLogic
Assuming all you need to do is reconstruct the path of activities from A to Z (or whatever), you don't need to make things too complicated. if you want to do it the right way, do the suggestion by Fuzzical Logic. if you want to get it running quickly and complicate things after that, you can start with this simple method.
Basically, you map each activity to a code, and maintain a simple text file. Each time an activity is invoked, it should append it's code to the text file. So you're really just writing to a file exactly what you explained in your question. In your example, you'd have "ADFG" in a text file.
When you exit an activity and go back, just read the file, chop off the last letter, and write it out. In your example, if you had "ADFG", pressed back, the file would now contain "ADF".
When your app starts, simply read the file and for each character, create the associated activity as you would normally. Read the file once and pass the string to each activity as it is created. So the root activity would read "A" and start that activity (passing the string "DFG" in the bundle), then that activity would read the next character and start the D activity (passing "FG"), and so on until the last activity sees that there's no characters left in the string.
Once that's all working, you can worry about how to store state information for each activity. Fuzzy's solution is by far the most elegant, but elegant and ASAP don't usually mix, so it's your call. I'd separate the stack data from the state data for each item in the stack. It's just easier that way IMO.
Hope that helps!

Categories

Resources