web-dev-qa-db-fra.com

Comment conserver la position de RecyclerView après un changement d'orientation, tout en utilisant Firebase et ChildEventListener?

Je travaille sur une application APOD simple qui implémente:

  • RecyclerView
  • CardView
  • Firebase
  • Picasso

L'application récupère les images et le texte de Firebase et Firebase Storage, les affiche dans un CardView et définit un OnClickListener pour chaque View. Lorsque l'utilisateur clique sur une image, j'ouvre un nouveau Activity via un Intent. Le deuxième Activity affiche l'image cliquée d'origine et plus d'informations à ce sujet.

J'ai implémenté tout cela en utilisant un GridLayoutManager, 1 colonne si le téléphone de l'utilisateur est VERTICAL, 3 colonnes si le téléphone de l'utilisateur est HORIZONTAL.

Le problème que j'ai est que je n'arrive pas à enregistrer la position de RecyclerView sur le changement d'orientation. J'ai essayé toutes les options que j'ai pu trouver, mais aucune ne semble fonctionner. La seule conclusion que j'ai pu trouver, c'est que lors de la rotation, je détruis les Firebase de ChildEventListener pour éviter une fuite de mémoire, et une fois l'orientation terminée, Firebase interroge à nouveau la base de données en raison de la nouvelle instance de ChildEventListener.

Existe-t-il un moyen de sauvegarder la position de mon RecyclerView sur le changement d'orientation? Je ne veux pas Android:configChanges car cela ne me permettra pas de modifier ma disposition, et j'ai déjà essayé d'enregistrer en tant que Parcelable, ce qui n'a pas réussi. Je suis sûr que c'est quelque chose de facile qui me manque, mais bon, je suis nouveau dans le développement. Toute aide ou suggestion sur mon code est grandement appréciée. Merci!

Vous trouverez ci-dessous mes cours, que je n'ai abrégés qu'au code nécessaire.

MainActivity

public class MainActivity extends AppCompatActivity {

private RecyclerAdapter mRecyclerAdapter;
private DatabaseReference mDatabaseReference;
private RecyclerView mRecyclerView;
private Query query;


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

    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
    setContentView(R.layout.activity_main);
    getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    final int columns = getResources().getInteger(R.integer.gallery_columns);

    mDatabaseReference = FirebaseDatabase.getInstance().getReference();
    query = mDatabaseReference.child("apod").orderByChild("date");

    mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    mRecyclerView.setHasFixedSize(true);
    mRecyclerView.setLayoutManager(new GridLayoutManager(this, columns));


    mRecyclerAdapter = new RecyclerAdapter(this, query);
    mRecyclerView.setAdapter(mRecyclerAdapter);


  }

@Override
public void onDestroy() {
    mRecyclerAdapter.cleanupListener();
  }

}

RecyclerAdapter

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ApodViewHolder> {

private final Context mContext;
private final ChildEventListener mChildEventListener;
private final Query mDatabaseReference;
private final List<String> apodListIds = new ArrayList<>();
private final List<Apod> apodList = new ArrayList<>();

public RecyclerAdapter(final Context context, Query ref) {
    mContext = context;
    mDatabaseReference = ref;


    ChildEventListener childEventListener = new ChildEventListener() {


        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
            int oldListSize = getItemCount();
            Apod apod = dataSnapshot.getValue(Apod.class);

            //Add data and IDs to the list
            apodListIds.add(dataSnapshot.getKey());
            apodList.add(apod);

            //Update the RecyclerView
            notifyItemInserted(oldListSize - getItemCount() - 1);
        }

        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {
            String apodKey = dataSnapshot.getKey();
            int apodIndex = apodListIds.indexOf(apodKey);

            if (apodIndex > -1) {

                // Remove data and IDs from the list
                apodListIds.remove(apodIndex);
                apodList.remove(apodIndex);

                // Update the RecyclerView
                notifyDataSetChanged();
            }
        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }

    };
    ref.addChildEventListener(childEventListener);
    mChildEventListener = childEventListener;

 }

   @Override
    public int getItemCount() {
    return apodList.size();
}
    public void cleanupListener() {
    if (mChildEventListener != null) {
        mDatabaseReference.removeEventListener(mChildEventListener);
    }
  }

}
23
Drew Szurko

MODIFIER:

J'ai finalement pu faire ce travail en utilisant plusieurs facteurs. Si l'un de ces éléments était omis, cela ne fonctionnerait tout simplement pas.

Créer un nouveau GridLayoutManager dans mon onCreate

gridLayoutManager = new GridLayoutManager(getApplicationContext(), columns);

Enregistrer l'état RecyclerView dans onPause

private final String KEY_RECYCLER_STATE = "recycler_state"; 
private static Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;`


@Override
protected void onPause() {
    super.onPause();

    mBundleRecyclerViewState = new Bundle();
    mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
    mBundleRecyclerViewState.putParcelable(KEY_RECYCLER_STATE, mListState);


}

Incluez configChanges dans le AndroidManifest.xml

L'enregistrement et la restauration de l'état RecyclerView n'étaient pas suffisants. J'ai aussi dû aller à contre-courant et changer mon AndroidManifest.xml vers 'Android: configChanges = "orientation | screenSize"'

<activity
        Android:name=".MainActivity"
        Android:configChanges="orientation|screenSize"
        >

Restaurer l'état RecyclerView dans onConfigurationChanged en utilisant un Handler

Le gestionnaire était l'une des parties les plus importantes, s'il n'est pas inclus, il ne fonctionnera tout simplement pas et la position du RecyclerView sera réinitialisée à 0. Je suppose que cela a été une faille remontant à quelques années, et n'a jamais été corrigé. J'ai ensuite changé le GridLayoutManagersetSpanCount en fonction de l'orientation.

    @Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    columns = getResources().getInteger(R.integer.gallery_columns);

    if (mBundleRecyclerViewState != null) {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                mListState = mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE);
                mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);

            }
        }, 50);
    }

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {

        gridLayoutManager.setSpanCount(columns);

    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {

        gridLayoutManager.setSpanCount(columns);

    }
    mRecyclerView.setLayoutManager(gridLayoutManager);
}

Comme je l'ai dit, tous ces changements devaient être inclus, au moins pour moi. Je ne sais pas si c'est le moyen le plus efficace, mais après avoir passé de nombreuses heures, cela fonctionne pour moi.

3
Drew Szurko

Alors que l'activité de rotation est détruite et recréée à nouveau, cela signifie que tous vos objets sont détruits et recréés et que la mise en page est à nouveau redessinée.

Si vous voulez arrêter la recréation d'activité, vous pouvez utiliser Android: configChanges mais c'est une mauvaise pratique et pas la bonne façon.

Il ne reste plus qu'à sauvegarder la position de la vue de recyclage avant la rotation et à l'obtenir après la recréation de l'activité.

// pour sauvegarder la position de recyclage

 @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
      // Save UI state changes to the savedInstanceState.
      // This bundle will be passed to onCreate if the process is
      // killed and restarted.
      savedInstanceState.putInt("position", mRecyclerView.getAdapterPosition()); // get current recycle view position here.
      super.onSaveInstanceState(savedInstanceState);
    }

// pour restaurer la valeur

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

    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
    setContentView(R.layout.activity_main);
    getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    final int columns = getResources().getInteger(R.integer.gallery_columns);

    mDatabaseReference = FirebaseDatabase.getInstance().getReference();
    query = mDatabaseReference.child("apod").orderByChild("date");

    mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    mRecyclerView.setHasFixedSize(true);
    mRecyclerView.setLayoutManager(new GridLayoutManager(this, columns));


    mRecyclerAdapter = new RecyclerAdapter(this, query);
    mRecyclerView.setAdapter(mRecyclerAdapter);
   if(savedInstanceState != null){
     // scroll to existing position which exist before rotation.
     mRecyclerView.scrollToPosition(savedInstanceState.getInt("position"));
   }  

  }

J'espère que cela vous aidera.

7
jitsm555

En fait, il existe une bien meilleure approche pour restaurer la position de la vue du recycleur lors de la recréation de l'activité, et cela en utilisant l'option de gestionnaire de disposition de la vue du recycleur pour stocker et restaurer la dernière position (le dernier état de la disposition de la vue du recycleur). Jetez un oeil à ce tutoriel .

En quelques mots, vous devez appeler les méthodes du gestionnaire de disposition onSaveInstanceState () et onRestoreInstanceState (état Parcelable) dans les méthodes d'activité correspondantes. À la fin, vous devez restaurer l'état du gestionnaire de disposition juste après avoir rempli l'adaptateur avec des données.

Si vous jetez un œil à l'intérieur de l'implémentation de LinearLayoutManager par exemple, vous verrez comment cette classe gère l'état (stockage/restauration)

0
Sniper

L'activité est recréée chaque fois qu'il y a un changement d'orientation. Vous devrez donc enregistrer la position dans le bundle, puis la réutiliser lorsque onCreate () sera appelé la deuxième fois.

@Override
public void onCreate(Bundle savedInstanceState) {

   super.onCreate(savedInstanceState);

   int recyclerViewPosition;
   if(savedInstanceState != null){
     recyclerViewPosition = savedInstanceState.getInt(RECYCLER_VIEW_POSITION);
   }

   mRecyclerView.scrollToPosition(recyclerViewPosition);

}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    //save the current recyclerview position 
    outState.putInt(RECYCLER_VIEW_POSITION, mRecyclerView.getAdapterPosition());
}

Vous voudrez peut-être jeter un œil à RecyclerView.State () link pour plus d'options sur la façon de restaurer l'état de la vue du recycleur.

0
Bharat