1

My app consists of 1 fragment. This fragment contains a recyclerview with a large number of entries.

The symptom was that after device rotation it seemed not possible to scroll the recyclerview to the saved position. Whatever I did, no scrolling was the result. So, strange things happened in the UI.

EDIT (revealing the root cause) ...

In my activity I had this code ...

if (findViewById(R.id.fragment_container) != null) {
    FragmentCategoryChecklist f2 = new FragmentCategoryChecklist();
    Bundle b = new Bundle();
    b.putString("contents", "Category Fragment");
    f2.setArguments(b);        
    getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, f2).commit();
 }

This is wrong! See the below code.

When the device is rotated with this code, this creates each time a new fragment. This is wrong because Android ALREADY recreates the (saved) fragment.

The result of this is that a fragment is created with instance data by the code, then immediately destroyed and then recreated without any instance-data. No saved instance data means no scrolling to a previously saved position.

The correct code is shown below. Sorry, my question was only a symptom and not showing the root cause.

Fragment f = getSupportFragmentManager().findFragmentById( R.id.fragment_container);
if( f == null) {
    if (findViewById(R.id.fragment_container) != null) {
        FragmentCategoryChecklist f2 = new FragmentCategoryChecklist();
        Bundle b = new Bundle();
        b.putString("contents", "Category Fragment");
        f2.setArguments(b); 
        getSupportFragmentManager().
            beginTransaction().
            replace(R.id.fragment_container, f2).
            commit();
    }
}
tm1701
  • 7,307
  • 17
  • 79
  • 168
  • I think you can try using a UI handler for that. Post a `Runnable` to the UI message queue and try to `findLastCompletelyVisibleItemPosition` there. This will guarantee that this Runnable will be executed after lifecycle callbacks. If that doesn't work, post some code here because it's hard to find a problem. – Gennadii Saprykin Jun 28 '16 at 17:16
  • Can you elaborate on your answer? – tm1701 Jun 29 '16 at 18:23
  • I haven't posted this as an answer because I'm not 100% sure it will work, so I asked you to try. Does @bpr10's solution work? – Gennadii Saprykin Jun 29 '16 at 18:26
  • If you didn't get what I meant, try it like this: `uiHandler.post(new Runnable() { @Override public void run() { ((LinearLayoutManager) recyclerview.getLayoutManager()).findLastCompletelyVisibleItemPosition()); // this shouldn't return -1 }})`. Where `uiHandler` is a `Handler` object attached to the UI thread. – Gennadii Saprykin Jun 29 '16 at 18:41
  • I have added my code to the question. You will see option 1 ... which returned -1 on the findLastCompletelyVisibleItemPosition(). Your option 2 I've added also, and again -1 is returned. – tm1701 Jun 30 '16 at 17:27

4 Answers4

1

This is a sort of hackish way to go about it, but you can set up a thread to periodically check your RecyclerView

final LinearLayoutManager manager = (LinearLayoutManager) recyclerview.getLayoutManager();
new Thread(){
    @Override
    public void run(){
        try {
            while(true){
                Thread.sleep(543);
                if(manager.findLastCompletelyVisibleItemPosition() != -1){
                   //Return to the UI thread and continue your work
                   //Try runOnUiThread(Runnable)
                   break;
                }
            }
        } catch (Exception ex) {
            //Handle thread interruption, etc
        }
    }
}.start();

There are surely better ways to go about this that don't involve threading. It may help to set a finite number of trials so that you don't have a stray thread that runs forever.

sjyn
  • 99
  • 2
  • 9
1

I believe you can get a callback of the layout render complete from the first and lastVisibleItem position. This answer explains this in the best way. hope this helps.

private boolean canScroll = false;
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {
            @Override 
            public void onLayoutChildren(final Recycler recycler, final State state) {
                super.onLayoutChildren(recycler, state);
                final int firstVisibleItemPosition = findFirstVisibleItemPosition();
                if (firstVisibleItemPosition != 0) {
                    // this avoids trying to handle un-needed calls 
                    if (firstVisibleItemPosition == -1)
                        //not initialized, or no items shown.Can't 
                        canScroll = false;
                        return; 
                } 
                final int lastVisibleItemPosition = findLastVisibleItemPosition();
                canScroll = (lastVisibleItemPosition - firstVisibleItemPosition) > 0
            } 
        }; 

Then you can use the canScroll variable to decide whether to scroll or not.

Community
  • 1
  • 1
bpr10
  • 1,086
  • 8
  • 14
  • Alas, this does not work. It just mentions (Toast message) that the lastVisibleItemPosition is like 9. I still cannot scroll to a position. So at a canScroll == true the following scroll is not effective. ((LinearLayoutManager) recyclerview.getLayoutManager()).scrollToPositionWithOffset( jumpToCurrentSelection, 20); – tm1701 Jun 30 '16 at 17:45
  • Added 1 up for your answer. – tm1701 Jul 02 '16 at 16:35
0

The root cause of the issue is shown after the EDIT part of the question.

The solution of correctly starting fragments after a device rotation is:

Fragment f = getSupportFragmentManager().findFragmentById( R.id.fragment_container);
if( f == null) {
    if (findViewById(R.id.fragment_container) != null) {
        FragmentCategoryChecklist f2 = new FragmentCategoryChecklist();
        Bundle b = new Bundle();
        b.putString("contents", "Category Fragment");
        f2.setArguments(b);    getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, f2).commit();
    }
}

This will create a new fragment after device rotation ONLY if it is not existing.

And the oririginal question?

Is solved very simply, just by adding a ...

((LinearLayoutManager) recyclerview.getLayoutManager()).scrollToPositionWithOffset( jumpToCurrentSelection, 20);
tm1701
  • 7,307
  • 17
  • 79
  • 168
0

On the fragment add this:

@Override
protected Parcelable onSaveInstanceState() {
   Bundle bundle = new Bundle();
   bundle.putParcelable(SAVED_LAYOUT_MANAGER,   recyclerView.getLayoutManager().onSaveInstanceState());
   return bundle;
}

and

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if (state instanceof Bundle) {
        layoutManagerSavedState = ((Bundle) state).getParcelable(SAVED_LAYOUT_MANAGER);
    }
    super.onRestoreInstanceState(state);
}

And Restore your Recycler View data with:

public void setItems(List objects) {
    adapter.setItems(objects);
    restoreLayoutManagerPosition();
}
private void restoreLayoutManagerPosition() {
    if (layoutManagerSavedState != null) {
        recyclerView.getLayoutManager().onRestoreInstanceState(layoutManagerSavedState);
    }
}
Pradeep Kumar Kushwaha
  • 2,231
  • 3
  • 23
  • 34