I was looking at the bottom sheet behaviors and how to make it's state persists through destruction.
I found this class SavedState https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetBehavior.SavedState
but I could not find any example on how to use that.
Moreover, handling state persistence can just be handled through the bundle
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(Key, BottomSheetBehavior.from(yourscrollview).state)
}
private fun loadSavedInstanceState(savedInstanceState: Bundle) {
BottomSheetBehavior.from(yourscrollview).state = savedInstanceState.getInt(Key)
}
So what is that SavedState's purpose.
Whenever there is a configuration change such as screen rotation the activity is recreated.
In this recreation, the application gets restarted and may lose data in the views if not handled.
For do this there are two methods that are triggered at different stages of the lifecycle:
onSaveInstanceState
onRestoreInstanceState
They are used to save and retrieve values. The values are stored in the form of a key-value pair.
In your case:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(key, state);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
int state = savedInstanceState.getInt(key);
}
Related
onSaveInstanceState and onRestoreInstanceState are not working. whenever i press back button or come out of the app the data of custom recyclerView is not visible until upload the next file. when i upload all the data comes back as I have stored it in shared preference.
Oncreate:
if (savedInstanceState != null) {
// Restore value of members from saved state
savedInstanceState.get(String.valueOf(savedInstanceState));
}
else
{
//initialize members with default values for a new instance
setContentView(R.layout.activity_resource);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
public void onRestoreInstanceState(#Nullable Bundle savedInstanceState, #Nullable PersistableBundle persistentState) {
super.onRestoreInstanceState(savedInstanceState, persistentState);
}
onSaveInstanceState stores view state automatically if all views inside layout has id.
For data restoring inside ListAdapter I would suggest sroring list inside ViewModel or at least Presenter depend on architecture you are using.
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.
My app needs to be able to dynamically create TextViews, so I need to be able to restore them if my app is ever GCed or switches orientations. The problem is I can't figure out how to restore the state of my TextViews. Saving their states with TextView.onSaveInstanceState() seems to work just fine, but when the Parcelable is passed to onRestoreInstanceState(), nothing happens and the resulting view is just blank. This is a short example which doesn't work:
public class MainActivity extends AppCompatActivity {
TextView v;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
ViewGroup main = (ViewGroup) findViewById(R.id.main);
v = new TextView(this);
main.addView(v);
if (state == null) {
v.setText("A simple message.");
} else {
v.onRestoreInstanceState(state.getParcelable("message"));
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("message", v.onSaveInstanceState());
}
}
I have checked the value of state.getParcelable("message") in the debugger and it definitely has the required information. onRestoreInstanceState() just isn't using it. Any help at all would be appreciated.
EDIT: I screwed up with the debugger. The information was actually never written to the bundle. TextView.onSaveInstanceState() had returned an empty Parcelable. That was the real problem.
I figured it out. It the problem was with TextView.onSaveInstanceState()—not TextView.onRestoreInstanceState(). TextView.onSaveInstaceState() only saves the state if you explicitly ask it to by calling TextView.setFreezesText(true) first. SURPRISE! Thank you, Google. Also note that onRestoreInstanceState() doesn't restore the FreezesText property. Oh, Google. You're just so zany. What'll we do with you?
Instead of restoring the state during onCreate() you may choose to implement onRestoreInstanceState(), which the system calls after the onStart() method. The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null:
Save Your Activity State:
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
Restore Your Activity State:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
} else {
// Probably initialize members with default values for a new instance
}
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
I have 3 custom views.
The first one works great. It contains an EditText when I launch an intent and come back whatever the user entered is restored.
The 2nd contains a TextView and the 3rd a Spinner. They do not save when I launch my intent and return.
I think know how to preserve the data using onSaveInstanceState and onRestoreInstanceState in my custom views, However when the activity containing the custom views is not killed (meaning it is only paused), and I return onRestoreInstanceState is not called.
This is what I'm calling in my custom views when I need to save them.
#Override
public Parcelable onSaveInstanceState() {
textValue = editText.getText().toString();
Bundle bundle = new Bundle();
bundle.putParcelable("instanceState", super.onSaveInstanceState());
bundle.putString(TEXT_VALUE_KEY, this.textValue);
return bundle;
}
#Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
textValue = bundle.getString(TEXT_VALUE_KEY);
editText.setText(textValue);
super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
return;
}
super.onRestoreInstanceState(state);
}
I'm unsure I what I should do since onRestoreInstanceState is not called. I think the EditText customView works because default android behavior saved them temporarily, but it doesn't save spinners or TextViews.
You should change your onCreate() method in order to check if the Activity has already called onSavedInstanceState like this:
#Override
public void onCreate(Bundle savedInstanceState) {
if(savedInstanceState == null) {
// The activity haven't called onSavedInstanceState(), which means you should initialize your objects and UI here
}
else {
// Whatever states you saved onSavedInstanceState() are stored in savedInstaceState, so use them to reconstruct your customViews
}
}
Am I missing something or do Fragments not have a onRestoreInstanceState() method? If not, how do I go about attaining something similar?
Fragments do not have an onRestoreInstanceState method.
You can achieve the same result in onActivityCreated, which receives a bundle with the saved instance state (or null).
Check the source code here.
I know, that you have accepted answer, but you should read the official documentation about fragments, and it says (paragraph "Handling the Fragment Lifecycle"):
You can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated()
So, you can use that suits you best: onCreate(), onCreateView(), or onActivityCreated()
In Fragments guide's ListFragment example you can find:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
Which you can use like this:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
}
onActivityCreated() is invoked after the fragment returns back from the stack.
onViewStateRestored of Fragment is the equivalent of onRestoreInstanceState of Activity. But it is called after onActivityCreated(Bundle) and before onStart().
onActivityCreated is deprecated. and i found it just confusing in terms of the fragment lifecycle. just do this:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
// and then:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
savedIInistanceState?.let{
//restore the data here
}
}