I am using GridLayoutManager as LayoutManager for RecyclerView inside a fragment , and RecyclerView adapter get populated by loader.
I tried to store and restore state of GridLayoutManager on device rotation but not working and still get back to the initial state "first element in RecyclerView".
onSaveInstanceState method:
#Override
public void onSaveInstanceState(Bundle outState) {
mRecylcerViewParecelable = mGridView.getLayoutManager().onSaveInstanceState();
outState.putParcelable(GRID_LAYOUT_PARCEL_KEY, mRecylcerViewParecelable);
super.onSaveInstanceState(outState);
}
onViewStateRestored method:
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
if(savedInstanceState!=null){
mRecylcerViewParecelable = savedInstanceState.getParcelable(GRID_LAYOUT_PARCEL_KEY);
}
super.onViewStateRestored(savedInstanceState);
}
onLoadFinished method:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mMovieAdapter.swapCursor(data);
mGridView.getLayoutManager().onRestoreInstanceState(mRecylcerViewParecelable);}
Try to wrap onRestoreInstanceState() inside Handler:
new Handler().postDelayed(new Runnable() {
#Override public void run() {
mGridView.getLayoutManager().onRestoreInstanceState(listState);
}
}, 300);
It's a hack to delay the onRestoreInstanceState.
I've experienced the same issue, IMO it looks like the RecyclerView keeps going back to initial state because the data in Adapter still being populated when we call the onRestoreInstanceState.
Related
I have a chat app, which opens a Websocket to a server. The app has 2 modules -
chat sdk: which handles the connection plus back and forth
messaging.
ui: which handles adapters and views
The chat UI is on a fragment hosted by an activity.
Everything worked fine but when i decided to use persistence on device rotation, i have problems.
When i rotate the device, the websocket reconnects (which is normal),the activity is recreated so is the fragment too.
After device rotation and some debugging, i saw that my messageList is properly filled with any added items (ie message texts) but not the UI. When i type a text from the EditText and press send, the item is properly displayed in the UI, BUT when i receive response from server the UI is not updated even if the message is added to the messageList.
I read in similar posts that the RecyclerView adapter is updating the old fragment after the rotation. Is this the case? And if this is the case, why when i send a message from the EditText it is properly displayed but not when from the onMessage of my WebSocket?
I tried "android:configChanges="screenSize|orientation|screenLayout" w/o any results.
I override the onConfigurationChanged and force the adapter to update the views, with no result
#Override
public void onConfigurationChanged(#NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if(messagesAdapter!=null){
messagesAdapter.notifyDataSetChanged();
}
}
I save the fragment to restore it in my activity
public class MessageActivity extends AppCompatActivity {
private MessageFragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
if (savedInstanceState == null) {
fragment = MessageFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, fragment)
.commitNow();
}else{
fragment = (MessageFragment) getSupportFragmentManager().getFragment(savedInstanceState, "messageFragment");
}
}
#Override
public void onBackPressed() {
finishAffinity();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment's instance
getSupportFragmentManager().putFragment(outState, "messageFragment", fragment);
}}
And i also save and retrieve my messageList
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
//saving the state of the chat
outState.putParcelableArrayList("messages", messageArrayList);
try {
Log.d(TAG, "messagelist in save instance " + messageArrayList.size());
}catch (NullPointerException e){
e.printStackTrace();
}
outState.putString("dialogId", messageArrayList.get(0).getDialogId());
}
onMessage from websocket
#Override
public void onMessage(ArrayList<Message> data) {
if (!messageArrayList.isEmpty() && messageArrayList.get(messageArrayList.size() - 1).getServerMessageType() == Constants.TYPE_TYPING_ON) {
messageArrayList.remove(messageArrayList.size() - 1);
}
this.messageArrayList.addAll(data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isFragmentAttached) {
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
if (omiliaClient != null) {
updateUi(messageArrayList, omiliaClient.user2);
}
}
});
}
}
Call setRetainInstance(true) in either of onCreate(), onCreateView() or onActivityCreated() methods.
I'm populating the recyclerView with gridlayoutManager. Now I want to save the scroll position on Screen Rotation.
I've tried to do so using onSaveInstanceState and onRestoreInstanceState() as shown in this post :
How to save RecyclerView's scroll position using RecyclerView.State?
Below is my code:
#Override
protected void onSaveInstanceState(Bundle outState) {
Log.e(TAG, "onSaveInstanceState");
super.onSaveInstanceState(outState);
outState.putParcelable(KEY_INSTANCE_STATE_RV_POSITION,
gridLayoutManager.onSaveInstanceState());
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.e(TAG, "onRestoreInstanceState");
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState!= null){
Parcelable savedState =
savedInstanceState.getParcelable(KEY_INSTANCE_STATE_RV_POSITION);
movieAdapter.addAll(movieItemList);
if (savedState!= null){
gridLayoutManager.onRestoreInstanceState(savedState);
}
}
}
//this is my movieAdapter.addAll() method
public void addAll(List<MovieItem>items){
movieItems = items;
}
//This is the method to get lists of movies from ViewModel Class
private void loadFavMovies() {
FavViewModel favViewModel =
ViewModelProviders.of(MainActivity.this).get(FavViewModel.class);
favViewModel.getFavListLiveData().observe(MainActivity.this, new
Observer<List<FavlistItem>>() {
#Override
public void onChanged(List<FavlistItem> favlistItems) {
if (!favlistItems.isEmpty()) {
loadingIndicator.setVisibility(View.GONE);
movieRecycler.setVisibility(View.GONE);
favRecycler.setVisibility(View.VISIBLE);
favAdapter.setFavlistItems(favlistItems);
Toast.makeText(getApplicationContext(), "Swipe Left Or
Right To remove Item",Toast.LENGTH_SHORT).show();
}else {
loadingIndicator.setVisibility(View.GONE);
Toast.makeText(getApplicationContext(), "No Favorite
Movies",Toast.LENGTH_SHORT).show();
}
}
});
}
This is the link to GitHub for this project https://github.com/harshabhadra/Movies-Mela
Now,It has simple solution.use this dependency
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"
And just set the stateRestorationPolicy like below
myAdapter.stateRestorationPolicy=RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
I know i am late but still if it helps!!
Storing the recycler view position is lot simpler than what other answers have made it look like
Here's how you can do it
First create a member variable
Parcelable state;
Now,
#Override
protected void onPause() {
super.onPause();
state = recyclerView.getLayoutManager().onSaveInstanceState();
}
#Override
protected void onResume() {
super.onResume();
recyclerView.getLayoutManager().onRestoreInstanceState(state);
}
overide on pause and on resume methods with the above code and you are good to go!!
One way to do that would be to stop re-creation of activity on orientation change. You can add that to the activity in the manifest to do so.
android:configChanges="orientation|screenSize"
But that isn't always a good practice, so another alternative would be to save the adapter position before exiting in onSaveInstanceState and then scroll to that position in onCreate using scrollToPosition. So, for example, you'd do something along the following lines.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("position", mRecyclerView.getAdapterPosition()); // get current recycle view position here.
//your other code
super.onSaveInstanceState(savedInstanceState);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//your other code
if(savedInstanceState != null){
// scroll to existing position which exist before rotation.
mRecyclerView.scrollToPosition(savedInstanceState.getInt("position"));
}
}
Scroll position should already be automatically saved if you are using GridLayoutManager, which subclasses LinearLayoutManager. You can take a look at SavedState inside LinearLayoutManager:
public static class SavedState implements Parcelable {
int mAnchorPosition;
int mAnchorOffset;
boolean mAnchorLayoutFromEnd;
That mAnchorPosition is effectively your scrolling position and it's saved during rotations. If that's not working for you, something else is likely wrong : you might be reloading / reapplying data incorrectly for example. That is probably the real question you need to be asking. You might need to cache that data somewhere so that you can automatically reapply it immediately after the configuration change.
I can also confirm for what its worth that on every project I've used LinearLayoutManager and GridLayoutManager on, scroll position is correctly maintained during rotations with no additional work on my part.
You should check out this SO post.
Basically, you want to create a new class extending RecyclerView (remember you will have to use your new class instead of RecyclerView) and override onSaveInstanceState() and onRestoreInstanceState(). In onSaveInstanceState() you will save the index of the first visible element and scroll to that element in onRestoreInstanceState().
The way they did it in the accepted answer of the linked post is different, but used LinearLayoutManager instead so I will tailor the code to use GridLayoutManager:
public class CustomRecyclerView extends RecyclerView {
private int mScrollPosition;
public VacationsRecyclerView(#NonNull Context context) {
super(context);
}
#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);
}
}
}
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
mScrollPosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
SavedState newState = new SavedState(superState);
newState.mScrollPosition = mScrollPosition;
return newState;
}
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];
}
};
}
}
The easiest way to achieve what you're trying to get would be to use the RecyclerView within a Fragment instead of directly within an Activity.
Unlike activities, fragments by default don't get re-created on rotation, but preserve their state. This way, the recyclerview will always remain at the same scroll position.
I am using a RecyclerView this way in one of my apps, which displays lists with > 200 elements.
I've tried everything suggested by everyone but finally, this gist work for me
https://gist.github.com/FrantisekGazo/a9cc4e18cee42199a287
I just import this in my project and replace the recyclerView provided by default with this
"StateFulRecyclerView" and it solved my problem and handle the scroll position automatically during screen rotation or any configuration change. No need to use onSaveInstanceState to maintain scroll positon.
very simple. save the position on pause and restore is onresume.
ON pause
lastVisitPosition = ((LinearLayoutManager)recycleView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
onResume
((LinearLayoutManager) recycleView.getLayoutManager()).scrollToPosition(lastVisitPosition);
or
((LinearLayoutManager) recycleView.getLayoutManager()).scrollToPositionWithOffset(lastVisitPosition,0)
One solution is by saving scroll positions of recycleview
in onSaveInstanceState() method of fragment you can save the scroll position of RecycleView
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
LinearLayoutManager layoutManager = (LinearLayoutManager)
recyclerView.getLayoutManager();
outState.putInt("scrolled_position",
layoutManager.findFirstCompletelyVisibleItemPosition());
}
then you can retrieve saved scroll position in onViewStateRestored() method
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
int scrollPosition = savedInstanceState.getInt("scrolled_position");
recyclerView.scrollToPosition(scrollPosition);
}
}
I want to save my fragment with recycler view, to restore it whlie screen rotate. I was looking for instructions in the net and I get:
In my Fragment
private Parcelable recyclerViewState;
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
list.getLayoutManager().onRestoreInstanceState(recyclerViewState);
}
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
recyclerViewState = layoutManager.onSaveInstanceState();
}
in my Activity
//onCreate
if (savedInstanceState != null) {
getSupportFragmentManager().getFragment(savedInstanceState, LIST_USERS_FRAGMENT);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().getFragment(outState, LIST_USERS_FRAGMENT);
}
I was debbuging and it looks fine, however while I rotate my screen I get blank fragment, with no recycler view. Could You help me investigate why it's happening ?
The default behavior in Android is destroying all the attached fragments on configuration changes. To bypass fragment recreation you should use setRetainInstance(true) in the OnCreate callback of the fragment.
There are a different options to handle the orientation changes:
Lock screen orientation
<activity
android:name="com.example.test.activity.MainActivity"
android:screenOrientation="portrait"/>
Prevent Activity to recreated
<activity
android:name="com.example.test.activity.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"/>
Save basic state
public class MainActivity extends Activity{
private static final String SELECTED_ITEM_POSITION = "ItemPosition";
private int mPosition;
#Override
protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
// Save the state of item position
outState.putInt(SELECTED_ITEM_POSITION, mPosition);
}
#Override
protected void onRestoreInstanceState(final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Read the state of item position
mPosition = savedInstanceState.gettInt(SELECTED_ITEM_POSITION);
}
}
Save complex objects
Override
onRetainNonConfigurationInstance()
getLastNonConfigurationInstance()
After API level 13 these methods have been deprecated in favor of the more Fragment’s setRetainInstance(boolean) capability, which provides a much cleaner and modular means of retaining objects during configuration changes.
I have a little project, one activity and two fragment. I have started from the "Bottom Navigation Activity" sample on a new projet.
The I use the Android Annotations (https://github.com/androidannotations/androidannotations/wiki) for injecting my fragment, It display correctly but when I rotate, I loose all my information and the app display the first fragment, even if I displayed the second one. I tried to save my data, from an edit text with a Bundle object, in the "onSaveInstanceState" method, I get back it in the "onActivityCreated" or in the "onCreate" method but after their method were re-call and my data desappear.
I don't know how to save which fragment was displayed and how re-displayed it with the android annotations.
In my Activity class I have those methods :
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
And in my main fragments :
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(dpdEditText != null){
rttNumberTemp = rttEditText.getText().toString();
outState.putString("rttNumberTemp", rttNumberTemp);
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(Tag, "onActivityCreated");
if(savedInstanceState != null){
Log.d(Tag, "onActivityCreated if");
updateEditText(rttEditText,savedInstanceState.getString("rttNumberTemp"));
}
}
So my question is how can I save my fragment, and its data and which fragment is displayed ?
I finally find my mistake. In my main activity I have the following method :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
So the program call the view by the injection and call it again with the setContentView. So I update the method like that :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
the ArrayList is defined on class level.
these are my savedInstance methods:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putStringArrayList("savedList", list);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
list=savedInstanceState.getStringArrayList("savedList");
}
but still, when i change orientation the ArrayList is blank
When you use onRestoreInstanceState() to restore state, its called after onStart() so you update your list with the saved state after you define your adapter. Your best option is to restore the list in onCreate() the same way you do it on onRestoreInstanceState(). You can also redefine the adapter or call notifyDataSetChanged().
TextView textviewname;
String textname;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//declare the ui like this
textviewname = (TextView) findViewById(R.id.textviewid);
if(savedInstanceState==null){
//the first time the activity was created
//do the normal flow of the code
//Example:getting the intent data for(savedList)or processing it
Intent i = getIntent();
textname = i.getStringExtra("savetext");
//or textname="name_data";
}else{
//this is called when orientation change occur
//get the savedata
list=savedInstanceState.getStringArrayList("savedList");
textname=savedInstanceState.getString("savetext");
}
textviewname.setText(textname);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//saving data when orientation change trigger
//saving data before ondestroy happens
//onSaveInstanceState is called before going to another activity or orientation change
outState.putStringArrayList("savedList", list);
outState.putString("savetext", textname);
//all imnportant data saved and can be restored
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//this is only called after ondestroy-> oncreate occur
list=savedInstanceState.getStringArrayList("savedList");
textname=savedInstanceState.getString("savetext");
//oncreate->onSaveInstanceState->ondestroy->oncreate->onRestoreInstanceState
}
try to use
list.addAll(savedInstanceState.getStringArrayList("savedList"));
instead
list=savedInstanceState.getStringArrayList("savedList");