18

Request to moderators: This is not a duplicate question, please read the below information.

Before asking this question, i'd tried almost every available solution on SO, but none of them worked for me, some resulted in crash and some didn't work at all (I'm a beginner, it is possible that i've done something wrong in my code, i'm not blaming anyone's answer available on SO for not working in my case). Below are my codes, please have a look:

fragment_tab1.xml (which contains recyclerview):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:background="#333333"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="96dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="42dp"
            android:background="@color/colorPrimaryDark"

            android:id="@+id/sort1"
            android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/sort"
            android:layout_marginLeft="10dp"
            android:fontFamily="@font/quicksand"
            android:textStyle="bold"
            android:text="Sort by:"
            android:textColor="#ffffff"/>

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                style="@style/Widget.AppCompat.Button.Colored"
                android:text="Oldest first"/>
            <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
                style="@style/Widget.AppCompat.Button.Colored"
            android:text="Newest first"/>
        </LinearLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_below="@id/sort1"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:padding="5dp" />

    </RelativeLayout>

</LinearLayout>

Fragmenttab1.java

public class tab1  extends Fragment {
    RecyclerView mRecyclerView;
    FirebaseDatabase mFirebaseDatabase;
    DatabaseReference mRef;
    LinearLayoutManager manager;
    private static final String TAG = "tab1";
    ProgressDialog progressDialog;
    View rootView;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        //Returning the layout file after inflating
        //Change R.layout.tab1 in you classes
         rootView = inflater.inflate(R.layout.fragment_tab1, container, false);


return rootView;

    }


    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);



        manager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setItemViewCacheSize(20);
        mRecyclerView.setDrawingCacheEnabled(true);
        mFirebaseDatabase = FirebaseDatabase.getInstance();
        mRef = mFirebaseDatabase.getReference("memes");

        mFirebaseDatabase.getInstance().getReference().keepSynced(true);

        progressDialog = new ProgressDialog(getActivity());
        progressDialog.setMessage("Please Wait");
        progressDialog.show();

    }

    public void onStart() {

        super.onStart();


        FirebaseRecyclerAdapter<Model, ViewHolder> firebaseRecyclerAdapter =
                new FirebaseRecyclerAdapter<Model, ViewHolder>(
                        Model.class,
                        R.layout.row2,
                        ViewHolder.class,
                        mRef

                ) {



                    @Override
                    protected void populateViewHolder(ViewHolder viewHolder, Model model, int position) {
                        viewHolder.setDetails(getActivity(), model.getTitle(), model.getDesc(), model.getImage());
                        progressDialog.cancel();
                    }



                    @Override
                    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

                        ViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);

                        viewHolder.setOnClickListener(new ViewHolder.ClickListener(){




                            @Override
                            public void onItemClick(View view, int position){



                                //get data from firebase here
                                String mTitle = getItem(position).getTitle();
                                String mDesc= getItem(position).getDesc();
                                String mImage = getItem(position).getImage();

                                //pass this data to new activity
                                Intent intent = new Intent(view.getContext(), ArticleDetail.class);
                                intent.putExtra("title", mTitle);// put title
                                intent.putExtra("desc", mDesc);// put description
                                intent.putExtra("image", mImage); //put image urls

                                startActivity(intent);
                            }




                            @Override
                            public void onItemLongClick(View view, int position){

                                //TODO your own implementation on long item click


                            }

                        });

                        return viewHolder;
                    }
                };


        mRecyclerView.setAdapter(firebaseRecyclerAdapter);

    }

    @Override
    public void  onPause() {
        super.onPause();
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());

        View firstChild = mRecyclerView.getChildAt(0);
        int firstVisiblePosition = mRecyclerView.getChildAdapterPosition(firstChild);
        int offset = firstChild.getTop();

        Log.d(TAG, "Postition: " + firstVisiblePosition);
        Log.d(TAG, "Offset: " + offset);

        preferences.edit()
                .putInt("position", firstVisiblePosition)
                .putInt("offset", offset)
                .apply();
    }


    @Override
    public void onResume(){
        super.onResume();

    }
    @Override
    public void onSaveInstanceState(Bundle outState) {


        super.onSaveInstanceState(outState);
    }

    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {


        super.onViewStateRestored(savedInstanceState);
    }

}
Manohar Patil
  • 604
  • 1
  • 5
  • 19

5 Answers5

24

After paying so many days, after trying many many solutions, finally found a working solution for my problem, thanks to https://stackoverflow.com/a/42563804/2128364

I've moved the working solution to another methods to make it work in the fragment.

Maybe this can save someone's day, Here is how it worked:

  1. onConfigurationChanged (as suggested in original solution), onSaveInstanceState or onViewRestoredState methods doesn't work, So you need onPause and onResume methods to save and restore the state respectively.
  2. You have to save the state in onPause method:
mBundleRecyclerViewState = new Bundle();    
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(KEY_RECYCLER_STATE, mListState);
  1. And then Restore it in onResume Method:
if (mBundleRecyclerViewState != null) {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            mListState = mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE);
            mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
        }
    }, 50);
}    
mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
hata
  • 11,633
  • 6
  • 46
  • 69
Manohar Patil
  • 604
  • 1
  • 5
  • 19
  • This saved my day! mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState); should be inside the handler as you mentioned. It worked like charm. Thank you! – Anju Apr 02 '20 at 04:55
  • You are the saviour for me i have been through this for last one month and didn't find an answer to it, I really apreciate – Vasant Raval Oct 26 '21 at 15:19
  • can you tell me what does delayMilles 50); do in onResume i mean what exactly it is doing – Vasant Raval Oct 26 '21 at 15:20
3

If you don't have already, add the recyclerView dependency in your build.gradle

implementation "androidx.recyclerview:recyclerview:1.2.0"

Than just set the stateRestorationPolicy just after you initialize your list adapter.

 listAdapter = ListAdapter()

 // Set scroll restore policy
 listdapter.stateRestorationPolicy =
      RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY

For more information on stateRestorationPolicy and different types of policies visit this article

sdex
  • 3,169
  • 15
  • 28
Vipul Purohit
  • 9,807
  • 6
  • 53
  • 76
  • I wasn't using Androidx that time. I'd the solution too, but I'll try yours also and revert you back if it works. Thank you. – Manohar Patil Jul 23 '20 at 07:48
  • Where do you place this code? I am using a linearlayout for my recyclerview and I've placed my code (myAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY); right after I declare the adapter and it's not working. I even tried it in onResume and it woudn't work. – BVB09 Jul 29 '20 at 23:37
  • Do you have any default items, like headers or load progress indicators as part of your Adapter? – Vipul Purohit Jul 30 '20 at 06:52
  • 1
    This is for screen rotation, not for storing `RecyclerView` scroll possition on resume. – Stanojkovic Mar 14 '21 at 16:05
0

You should use this method that returns the first item visible:

int findFirstVisibleItemPosition();

then you must save this index at onPause() and when user returns retrieve and set it at onResume():

void scrollToPosition (int position)
No Body
  • 671
  • 5
  • 14
  • Thanks for your reply, I did it, but it doesn't work :( , I did it like this: public void onPause() { super.onPause(); firstVisibleItem = ((LinearLayoutManager)mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition(); } @Override public void onResume(){ super.onResume(); ((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPosition(firstVisibleItem ); } – Manohar Patil Oct 01 '18 at 09:52
  • Did you fill your adapter with last data before scroll to last position? – No Body Oct 01 '18 at 09:57
  • I don't know about that, can you please guide me? – Manohar Patil Oct 01 '18 at 10:00
  • Suppose you first load 10 items and when user go down you load 10 items again. You have 20 items now and index of first visible item may be 12. When user returns you load 10 items but last position is 12 because you didn't load 10 next items. – No Body Oct 01 '18 at 10:08
  • no Sir, i'm not using endless recyclerview as there will be 20-25 items only on each fragment. – Manohar Patil Oct 01 '18 at 10:13
  • Did you load items before scroll to position? – No Body Oct 01 '18 at 10:14
  • How did you save firstVisibleItem? – No Body Oct 01 '18 at 10:17
  • I've save it in onPause method: firstvisibleitem =((LinearLayoutManager)mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition(); and then I restored it in onResume with: ((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPosition (firstvisibleitem); – Manohar Patil Oct 01 '18 at 10:20
  • 1
    I mean where did you save it? SharedPreferences? – No Body Oct 01 '18 at 10:26
0

Problem is scope the variables in this fragment are destroyed when you move to another activity or fragment

Okay finally I think I got the answer for future coders keep all the code in On Create View in fragment it will work maximum times if not try by declaring all variables in class not in local method how to get context in on create view is your home work

0

If someone minds the accepted answer of it using a static object (static Bundle mBundleRecyclerViewState) to keep state, you can use ViewModel instead.

public class MainViewModel extends ViewModel {
    private Parcelable mRecyclerViewState;
    
    /**
     * Back up RecyclerView's state to ViewModel in Fragment's onDestroyView
     * or in Activity's onStop (or in somewhere else appropriate)
     *
     * @param layoutManager RecyclerView.LayoutManager object
     */
    public void saveRecyclerViewState(RecyclerView.LayoutManager layoutManager) {
        mRecyclerViewState = layoutManager.onSaveInstanceState();
    }

    /**
     * Restore backed up RecyclerView's state from ViewModel in onCreate
     *
     * @param layoutManager RecyclerView.LayoutManager object
     */
    public void restoreRecyclerViewState(RecyclerView.LayoutManager layoutManager) {
        layoutManager.onRestoreInstanceState(mRecyclerViewState);
    }
}

usage example (in Fragment):

private MainViewModel mViewModel;
private GridLayoutManager mLayoutManager;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);

    mLayoutManager = new GridLayoutManager(requireContext(), ITEMS_PER_ROW);
    mViewModel.restoreRecyclerViewState(mLayoutManager);
}

@Override
public void onDestroyView() {
    mViewModel.saveRecyclerViewState(mLayoutManager);

    super.onDestroyView();
}
hata
  • 11,633
  • 6
  • 46
  • 69