Currently, when I
Launch a new Activity by clicking on AutoCompleteTextView's drop down
Close the launched Activity
AutoCompleteTextView's drop down is hidden.
I would like to preserve AutoCompleteTextView's drop-down state which includes
Drop down should not be hidden when gets back from launched Activity
Drop down's scroll position should be preserved.
I'm not exactly sure the reason why AutoCompleteTextView's dropdown will be hidden when I back from launched Activity. Hence, I had tried 2 things
Change windowSoftInputMode of launched Activity from stateAlwaysHidden to stateUnchanged.
In onActivityResult, when the launched Activity is closed, perform mSearchSrcTextView.showDropDown(); explicitly.
However, I am still facing the issue. The previous scroll position of AutoCompleteTextView's dropdown is not preserved. It is reset back to top of the list.
Here's the screen-shot to better illustrate the problem I am facing.
(Current AutoCompleteTextView's dropdown is scrolled to the end. I click on the last item and launch a new Activity)
(New Activity is launched. Now, I click on the BACK soft key twice, to close the keyboard and then close the Activity)
(Due to the explicit call of mSearchSrcTextView.showDropDown(); in onActivityResult, the drop down is shown again. However, its previous scrolled position is not being preserved. Start of list is being shown instead of end of list)
I was wondering, is there any way to preserved the AutoCompleteTextView's DropDown state, when closing a previous launched Activity?
For AutoCompleteTextView, it has a method called dismissDropDown(). I believe when back from newly launched activity, this function is being triggered. So we workaround this problem by extending AutoCompleteTextView & override it's dismissDropDown().
We add a boolean flag temporaryIgnoreDismissDropDown, to indicate whether to temporarily ignore dismissDropDown.
public class MyAutoCompleteTextView extends AutoCompleteTextView {
private boolean temporaryIgnoreDismissDropDown = false;
.....
#Override
public void dismissDropDown() {
if (this.temporaryIgnoreDismissDropDown) {
this.temporaryIgnoreDismissDropDown = false;
return;
}
super.dismissDropDown();
}
public void setTemporaryIgnoreDismissDropDown(boolean flag) {
this.temporaryIgnoreDismissDropDown = flag;
}
}
Before launching new Activity, we set dismissDropDown to true. After coming back from launched activity, dismissDropDown is called. The override method checks if temporaryIgnoreDismissDropDown is true, just set it to false & do nothing. So the real dismissDropDown is skipped.
// myAutoCompleteTextView is instance of MyAutoCompleteTextView
myAutoCompleteTextView.setTemporaryIgnoreDismissDropDown(true);
// launch new Activity
startActivity(....);
Hope this help, good luck!
After an hour of coding, much trying and a lot of googling around, I've put together a solution that does just what you want. It uses reflection to access the ListView within the Dropdown menu and to access the dropdown state when you leave the activity.
The code for this is kinda long, so I'll walk you through all the parts. Firstly, I have some variables we will need:
boolean wasDropdownOpen;
int oldDropdownY;
Handler handler;
The handler will be neccessary for later, as we have to do a little trick in the onResume() method. Initialize it as usual in your onCreate() method:
handler = new Handler(getMainLooper());
Now, let's get to the tricky part.
You need to call the following method before you start any activity. It can't be done in onPause() since the Dropdown menu is already closed when this method is called. In my test code I've overridden the startActivity() and startActivityForResult() method, and called it there, but you can do this however you like.
private void processBeforeStart() {
ListPopupWindow window = getWindow(textView);
if(window == null) return;
wasDropdownOpen = window.isShowing();
ListView lv = getListView(window);
if(lv == null) return;
View view = lv.getChildAt(0);
oldDropdownY = -view.getTop() + lv.getFirstVisiblePosition() * view.getHeight();
}
This will save your dropdown ListView's state for later. Now, we will load it. This is the onResume() method we will need for this:
#Override
protected void onResume() {
super.onResume();
if (wasDropdownOpen)
textView.showDropDown();
handler.postDelayed(new Runnable() {
#Override
public void run() {
ListView lv = getListView(getWindow(textView));
if (lv != null)
scrollToY(lv, oldDropdownY);
}
}, 150);
}
First of all, let me explain this method. We saved the state if the dropdown was open, so we reopen the menu if it was. Simple. The next part is the scrolling. We need to do this in a Handler because the UI is not yet fully loaded when onResume() is called and therefore the ListView is still inaccessible.
The scrollToY() method you see there is a modified version of the code from this post, as Android's ListView does not have an inbuilt method to set the scroll position as precisely as we want it here.
The implementation of this method is as follows:
private void scrollToY(ListView lv, int position) {
int itemHeight = lv.getChildAt(0).getHeight();
int item = (int) Math.floor(position / itemHeight);
int scroll = (item * itemHeight) - position;
lv.setSelectionFromTop(item, scroll);// Important
}
Now, you've probably seen the getWindow() and getListView() methods I've used above. These are the reflection methods, which we have to use because Android does not expose a public API to access the ListView within the ListPopupWindow of the AutoCompleteTextView. Additionally, the DropDownListView, a subclass of ListView that is actually used within this object, is not visible to the oudside as well, so we have to use Reflection once again.
Here is the implementation of my two helper methods:
private ListView getListView(ListPopupWindow window) {
for (Field field : window.getClass().getDeclaredFields()) {
if (field.getType().getName().equals("android.widget.DropDownListView")) {
field.setAccessible(true);
try {
return (ListView) field.get(window);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
private ListPopupWindow getWindow(AutoCompleteTextView tv) {
Class realClass = tv.getClass().getName().contains("support") ? tv.getClass().getSuperclass() : tv.getClass();
for (Field field : realClass.getDeclaredFields()) {
if (field.getType().getName().equals(ListPopupWindow.class.getName())) {
field.setAccessible(true);
try {
return (ListPopupWindow) field.get(tv);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
I've tested this on Android O (API level 26) and it works just as you described you want it to work.
I hope that the effort I put into this answer gets me a chance on the Bounty ;-)
It sounds like you've already figured out how to show the drop-down on demand (via showDropDown()), so I'll only address how to restore the scroll position of the dropdown.
You can access the first visible position of the dropdown like this:
autocomplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int firstVisiblePosition = parent.getFirstVisiblePosition();
// save this value somehow
}
});
Save the value of this int however you'd like (in memory, via onSaveInstanceState(), pass it through to the started activity so that it can pass it back via onActivityResult(), etc). Then, wherever you re-show the dropdown, do this:
autocomplete.showDropDown();
autocomplete.setListSelection(firstVisiblePosition);
The shortcoming of this technique is that it makes the item at firstVisiblePosition completely visible, so if it was halfway scrolled out of view, the list position won't be restored perfectly. Unfortunately, I don't believe there's any way to save/restore this partial-view offset.
I have a fragement with various Spinners in it. To have these spinners display an initial (non selectable) value I'm using a custom arrayAdapter (SpinnerHintAdapter). The only important thing this class does is override the getCount() so the last item of the selection-array isn't displayed, this is where the initial value is stored.
This all works fine untill you rotate the device, then the spinners are set on the last normal value of the list for some reason, even though the Fragment class still thinks it's set on the initial value.
Anyone any clue why this happens and / or how to solve this problem?
Code samples:
fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_free_top_up, container, false);
Spinner pet = (Spinner) rootView.findViewById(R.id.pet);
SpinnerHintAdapter<CharSequence> petAdapter = SpinnerHintAdapter.createFromResource(getActivity(),
R.array.pet_array, android.R.layout.simple_spinner_item);
petAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
pet.setAdapter(petAdapter);
pet.setSelection(pet.getCount());
return rootView;
}
SpinnerHintAdapter:
#Override
public int getCount() {
int count = super.getCount();
// The last item will be the hint.
return count > 0 ? count - 1 : count;
}
example string-array
<string-array name="pet_array">
<item>Yes</item>
<item>No</item>
<item>(initial value)</item>
</string-array>
The activity is re-created when you rotate the device. You need to override onSavedInstanceState function, save your data in a Bundle and then use that data in onCreate again.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//save your data
}
and then use that in your onCreate to restore your spinner.
Haven't found an answer to this specific problem but used another method to display an initial value which can be found here: https://stackoverflow.com/a/12221309/3453217
Any clarification as to what went wrong with my first attempt is still welcome.
I have a question about Android's RecyclerView.State.
I am using a RecyclerView, how could I use and bind it with RecyclerView.State?
My purpose is to save the RecyclerView's scroll position.
Update
Starting from recyclerview:1.2.0-alpha02 release StateRestorationPolicy has been introduced. It could be a better approach to the given problem.
This topic has been covered on android developers medium article.
Also, #rubén-viguera shared more details in the answer below. https://stackoverflow.com/a/61609823/892500
Old answer
If you are using LinearLayoutManager, it comes with pre-built save api linearLayoutManagerInstance.onSaveInstanceState() and restore api linearLayoutManagerInstance.onRestoreInstanceState(...)
With that, you can save the returned parcelable to your outState. e.g.,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
, and restore restore position with the state you saved. e.g,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
To wrap all up, your final code will look something like
private static final String BUNDLE_RECYCLER_LAYOUT = "classname.recycler.layout";
/**
* This is a method for Fragment.
* You can do the same in onCreate or onRestoreInstanceState
*/
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if(savedInstanceState != null)
{
Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, recyclerView.getLayoutManager().onSaveInstanceState());
}
Edit: You can also use the same apis with the GridLayoutManager, as it is a subclass of LinearLayoutManager. Thanks #wegsehen for the suggestion.
Edit: Remember, if you are also loading data in a background thread, you will need to a call to onRestoreInstanceState within your onPostExecute/onLoadFinished method for the position to be restored upon orientation change, e.g.
#Override
protected void onPostExecute(ArrayList<Movie> movies) {
mLoadingIndicator.setVisibility(View.INVISIBLE);
if (movies != null) {
showMoviePosterDataView();
mDataAdapter.setMovies(movies);
mRecyclerView.getLayoutManager().onRestoreInstanceState(mSavedRecyclerLayoutState);
} else {
showErrorMessage();
}
}
Store
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
Restore
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
and if that doesn't work, try
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
Put store in onPause()
and restore in onResume()
How do you plan to save last saved position with RecyclerView.State?
You can always rely on ol' good save state. Extend RecyclerView and override onSaveInstanceState() and onRestoreInstanceState():
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
mScrollPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
SavedState newState = new SavedState(superState);
newState.mScrollPosition = mScrollPosition;
return newState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
if(state != null && state instanceof SavedState){
mScrollPosition = ((SavedState) state).mScrollPosition;
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null){
int count = layoutManager.getItemCount();
if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
layoutManager.scrollToPosition(mScrollPosition);
}
}
}
}
static class SavedState extends android.view.View.BaseSavedState {
public int mScrollPosition;
SavedState(Parcel in) {
super(in);
mScrollPosition = in.readInt();
}
SavedState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mScrollPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
I wanted to save Recycler View's scroll position when navigating away from my list activity and then clicking the back button to navigate back. Many of the solutions provided for this problem were either much more complicated than needed or didn't work for my configuration, so I thought I'd share my solution.
First save your instance state in onPause as many have shown. I think it's worth emphasizing here that this version of onSaveInstanceState is a method from the RecyclerView.LayoutManager class.
private LinearLayoutManager mLayoutManager;
Parcelable state;
#Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
The key to getting this to work properly is to make sure you call onRestoreInstanceState after you attach your adapter, as some have indicated in other threads. However the actual method call is much simpler than many have indicated.
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
Update: Since version 1.2.0-alpha02 there's a new API to control when state restoration (including scroll position) happens.
RecyclerView.Adapter lazy state restoration:
Added a new API to the RecyclerView.Adapter class which allows Adapter to control when the layout state should be restored.
For example, you can call:
myAdapter.setStateRestorationStrategy(StateRestorationStrategy.WHEN_NOT_EMPTY);
to make RecyclerView wait until Adapter is not empty before restoring the scroll position.
See also:
RecyclerView.Adapter#setStateRestorationPolicy
RecyclerView.Adapter.StateRestorationPolicy
All layout managers bundled in the support library already know how to save and restore scroll position.
The RecyclerView/ScrollView/whatever needs to have an android:id for its state to be saved.
By default, scroll position is restored on the first layout pass which happens when the following conditions are met:
a layout manager is attached to the RecyclerView
an adapter is attached to the RecyclerView
Typically you set the layout manager in XML so all you have to do now is
Load the adapter with data
Then attach the adapter to the RecyclerView
You can do this at any time, it's not constrained to Fragment.onCreateView or Activity.onCreate.
Example: Ensure the adapter is attached every time the data is updated.
viewModel.liveData.observe(this) {
// Load adapter.
adapter.data = it
if (list.adapter != adapter) {
// Only set the adapter if we didn't do it already.
list.adapter = adapter
}
}
The saved scroll position is calculated from the following data:
Adapter position of the first item on the screen
Pixel offset of the top of the item from the top of the list (for vertical layout)
Therefore you can expect a slight mismatch e.g. if your items have different dimensions in portrait and landscape orientation.
I Set variables in onCreate(), save scroll position in onPause() and set scroll position in onResume()
public static int index = -1;
public static int top = -1;
LinearLayoutManager mLayoutManager;
#Override
public void onCreate(Bundle savedInstanceState)
{
//Set Variables
super.onCreate(savedInstanceState);
cRecyclerView = ( RecyclerView )findViewById(R.id.conv_recycler);
mLayoutManager = new LinearLayoutManager(this);
cRecyclerView.setHasFixedSize(true);
cRecyclerView.setLayoutManager(mLayoutManager);
}
#Override
public void onPause()
{
super.onPause();
//read current recyclerview position
index = mLayoutManager.findFirstVisibleItemPosition();
View v = cRecyclerView.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - cRecyclerView.getPaddingTop());
}
#Override
public void onResume()
{
super.onResume();
//set recyclerview position
if(index != -1)
{
mLayoutManager.scrollToPositionWithOffset( index, top);
}
}
You don't have to save and restore the state by yourself anymore. If you set unique ID in xml and recyclerView.setSaveEnabled(true) (true by default) system will automatically do it.
Here is more about this: http://trickyandroid.com/saving-android-view-state-correctly/
Beginning from version 1.2.0-alpha02 of androidx recyclerView library, it is now automatically managed. Just add it with:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
And use:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
The StateRestorationPolicy enum has 3 options:
ALLOW — the default state, that restores the RecyclerView state immediately, in the next layout pass
PREVENT_WHEN_EMPTY — restores the RecyclerView state only when the adapter is not empty (adapter.getItemCount() > 0). If your data is loaded async, the RecyclerView waits until data is loaded and only then the state is restored. If you have default items, like headers or load progress indicators as part of your Adapter, then you should use the PREVENT option, unless the default items are added using MergeAdapter. MergeAdapter waits for all of its adapters to be ready and only then it restores the state.
PREVENT — all state restoration is deferred until you set ALLOW or PREVENT_WHEN_EMPTY.
Note that at the time of this answer, recyclerView library is still in alpha03, but alpha phase is not suitable for production purposes.
Since I find that most options are too long or complicated, here is my short Kotlin option with viewBinding and viewModel for achieving this:
If you want to have your RecyclerView state lifecycle aware, put this code in your ViewModel, otherwise you can use a basic Repository and work from there:
private lateinit var state: Parcelable
fun saveRecyclerViewState(parcelable: Parcelable) { state = parcelable }
fun restoreRecyclerViewState() : Parcelable = state
fun stateInitialized() : Boolean = ::state.isInitialized
and this inside your onPause()
binding.recyclerView.layoutManager?.onSaveInstanceState()?.let { viewModel.saveRecyclerViewState(it) }
and finally this in your onResume()
if (viewModel.stateInitialized()) {
binding.recyclerView.layoutManager?.onRestoreInstanceState(
viewModel.restoreRecyclerViewState()
)
}
Enjoy :)
I've had the same requirement, but the solutions here didn't quite get me across the line, due to the source of data for the recyclerView.
I was extracting the RecyclerViews' LinearLayoutManager state in onPause()
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state is saved in onSaveInstanceState(Bundle outState), and extracted again in onCreate(Bundle savedInstanceState), when savedInstanceState != null.
However, the recyclerView adapter is populated and updated by a ViewModel LiveData object returned by a DAO call to a Room database.
mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
#Override
public void onChanged(#Nullable final List<String> displayStrings) {
mAdapter.swapData(displayStrings);
mLinearLayoutManager.onRestoreInstanceState(state);
}
});
I eventually found that restoring the instance state directly after the data is set in the adapter would keep the scroll-state across rotations.
I suspect this either is because the LinearLayoutManager state that I'd restored was being overwritten when the data was returned by the database call, or the restored state was meaningless against an 'empty' LinearLayoutManager.
If the adapter data is available directly (ie not contigent on a database call), then restoring the instance state on the LinearLayoutManager can be done after the adapter is set on the recyclerView.
The distinction between the two scenarios held me up for ages.
Here is my Approach to save them and maintain the state of recyclerview in a fragment.
First, add config changes in your parent activity as below code.
android:configChanges="orientation|screenSize"
Then in your Fragment declare these instance method's
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
Add below code in your #onPause method
#Override
public void onPause() {
super.onPause();
//This used to store the state of recycler view
mBundleRecyclerViewState = new Bundle();
mListState =mTrailersRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(getResources().getString(R.string.recycler_scroll_position_key), mListState);
}
Add onConfigartionChanged Method like below.
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//When orientation is changed then grid column count is also changed so get every time
Log.e(TAG, "onConfigurationChanged: " );
if (mBundleRecyclerViewState != null) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mListState = mBundleRecyclerViewState.getParcelable(getResources().getString(R.string.recycler_scroll_position_key));
mTrailersRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
}
}, 50);
}
mTrailersRecyclerView.setLayoutManager(mLinearLayoutManager);
}
This approch will reslove your problem.
if You have diffrent layout manager then just replace upper layout manager.
On Android API Level 28, I simply ensure that I set up my LinearLayoutManager and RecyclerView.Adapter in my Fragment#onCreateView method, and everything Just Worked™️. I didn't need to do any onSaveInstanceState or onRestoreInstanceState work.
Eugen Pechanec's answer explains why this works.
This is how I restore RecyclerView position with GridLayoutManager after rotation when you need to reload data from internet with AsyncTaskLoader.
Make a global variable of Parcelable and GridLayoutManager and a static final string:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
Save state of gridLayoutManager in onSaveInstance()
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
Restore in onRestoreInstanceState
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//restore recycler view at same position
if (savedInstanceState != null) {
savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
}
}
Then when loader fetches data from internet you restore recyclerview position in onLoadFinished()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
..of course you have to instantiate gridLayoutManager inside onCreate.
Cheers
For me, the problem was that I set up a new layoutmanager every time I changed my adapter, loosing que scroll position on recyclerView.
You can either use adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY which is introduced in recyclerview:1.2.0-alpha02
https://medium.com/androiddevelopers/restore-recyclerview-scroll-position-a8fbdc9a9334
but it has some issues such as not working with inner RecyclerView, and some other issues you can check out in medium post's comment section.
Or you can use ViewModel with SavedStateHandle which works for inner RecyclerViews, screen rotation and process death.
Create a ViewModel with saveStateHandle
val scrollState=
savedStateHandle.getLiveData<Parcelable?>(KEY_LAYOUT_MANAGER_STATE)
use Parcelable scrollState to save and restore state as answered in other posts or by adding a scroll listener to RecyclerView and
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
scrollState.value = mLayoutManager.onSaveInstanceState()
}
}
I would just like to share the recent predicament I encounter with the RecyclerView. I hope that anyone experiencing the same problem will benefit.
My Project Requirement:
So I have a RecyclerView that list some clickable items in my Main Activity (Activity-A). When the Item is clicked a new Activity is shown with the Item Details (Activity-B).
I implemented in the Manifest file the that the Activity-A is the parent of Activity-B, that way, I have a back or home button on the ActionBar of the Activity-B
Problem:
Every time I pressed the Back or Home button in the Activity-B ActionBar, the Activity-A goes into the full Activity Life Cycle starting from onCreate()
Even though I implemented an onSaveInstanceState() saving the List of the RecyclerView's Adapter, when Activity-A starts it's lifecycle, the saveInstanceState is always null in the onCreate() method.
Further digging in the internet, I came across the same problem but the person noticed that the Back or Home button below the Anroid device (Default Back/Home button), the Activity-A does not goes into the Activity Life-Cycle.
Solution:
I removed the Tag for Parent Activity in the manifest for Activity-B
I enabled the home or back button on Activity-B
Under onCreate() method add this line supportActionBar?.setDisplayHomeAsUpEnabled(true)
In the overide fun onOptionsItemSelected() method, I checked for the item.ItemId on which item is clicked based on the id. The Id for the back button is
android.R.id.home
Then implement a finish() function call inside the android.R.id.home
This will end the Activity-B and bring Acitivy-A without going through the entire life-cycle.
For my requirement this is the best solution so far.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_show_project)
supportActionBar?.title = projectName
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId){
android.R.id.home -> {
finish()
}
}
return super.onOptionsItemSelected(item)
}
I had the same problem with a minor difference, I was using Databinding, and I was setting recyclerView adapter and the layoutManager in the binding methods.
The problem was that data-binding was happening after onResume so it was resetting my layoutManager after onResume and that means it was scrolling back to original place every time.
So when I stopped setting layoutManager in Databinding adaptor methods, it works fine again.
Alternative way watch this,
First of all, define variables,
private LinearLayoutManager mLayoutManager;
int lastPosition;
public static final String MyPREFERENCES__DISPLAY = "MyPrefs_display" ;
SharedPreferences sharedpreferences_display;
SharedPreferences.Editor editor_display;
then,
mLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.scrollToPosition(sharedpreferences_display.getInt("last_saved_position", 0));
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
lastPosition = mLayoutManager.findFirstVisibleItemPosition();
}
});
if you want to save position by button,
btn.setOnClickListener(v -> {
editor_display.putInt("last_saved_position", lastPosition).apply();
Toast.makeText(Arama_Fav_Activity.this, "Saved", Toast.LENGTH_SHORT).show();
});
In my case I was setting the RecyclerView's layoutManager both in XML and in onViewCreated. Removing the assignment in onViewCreated fixed it.
with(_binding.list) {
// layoutManager = LinearLayoutManager(context)
adapter = MyAdapter().apply {
listViewModel.data.observe(viewLifecycleOwner,
Observer {
it?.let { setItems(it) }
})
}
}
Activity.java:
public RecyclerView.LayoutManager mLayoutManager;
Parcelable state;
mLayoutManager = new LinearLayoutManager(this);
// Inside `onCreate()` lifecycle method, put the below code :
if(state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
#Override
protected void onResume() {
super.onResume();
if (state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
}
#Override
protected void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
Why I'm using OnSaveInstanceState() in onPause() means, While switch to another activity onPause would be called.It will save that scroll position and restore the position when we coming back from another activity.
I have created an DialogFragment which is building and returning AlertDialog from onCreateDialog method. The AlertDialog contains two EditText views.
I'm setting the initial values of these two edit texts in the onCreateDialog method which works great until I rotate the phone and all changes get lost/restored to initial values because the onCreateDialog is recalled.
So my question is where should I place the initial values so they are only set the very first time you open the dialog and if you have done changes and you rotate your phone, the last state i retained and retached?
Below I have pasted simplified version of my code. One solution could be initializing the class attributes at newInstance() method, but then I need to make them static. Other solution could be passing the values through the Bundle, but no put-methods take Calendar as parameter type.
What is best practice?
public class MyDialogFragment extends DialogFragment implements OnClickListener, OnDateSetListener, OnQuantitySetListener
{
private EditText editText1, editText2
private MyObject myObject;
public static MyDialogFragment newInstance()
{
return new MyDialogFragment ();
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater factory = LayoutInflater.from(getActivity());
final View v = factory.inflate(R.layout.my_layout, null);
editText1 = (EditText) v.findViewById(R.id.text1);
editText2 = (EditText) v.findViewById(R.id.text2);
myObject = <get the object from database>;
editText1.setText(myObject.attribute1);
editText2.setText(myObject.attribute2);
bindDataToViews();
return new AlertDialog.Builder(getActivity())
.setIconAttribute(R.drawable.add)
.setTitle("Title of the dialog")
.setView(v)).create();
}
... other methods using getting the values from EditText and putting them back to MyObject
}
Calendar is Serializable so you can put it as that in the Bundle.
You can store your data in onSaveInstanceState(Bundle outState) method, and read them again in onRestoreInstanceState() method. onSaveInstanceState will be called before screen rotation, and onRestoreInstanceState() after change. This is a good place to store data between orientation changes.
Or you can also add in Manifest file
android:configChanges="orientation"
Add this value to activitiy, which contains your alertDialog.