Issue
I'm working on a simple APOD app that implements:
RecyclerView
CardView
Firebase
Picasso
The app grabs images and text from Firebase
and Firebase Storage
, displays them in a CardView
, and sets an OnClickListener
to each View
. When the user clicks on an image, I open a new Activity
through an Intent
. The second Activity
displays the original clicked image, and more info about it.
I've implemented this all using a GridLayoutManager
, 1 column if the user's phone is VERTICAL, 3 columns if the user's phone is HORIZONTAL.
The problem I'm having is I can't seem to save the RecyclerView
's position on orientation change. I've tried every single option that I could find, but none seem to work. The only conclusion I could come up with, is that on rotation, I'm destroying Firebase
's ChildEventListener
to avoid a memory leak, and once orientation is complete, Firebase
re-queries the database because of the new instance of ChildEventListener
.
Is there any way I can save my RecyclerView
's position on orientation change? I do not want android:configChanges
as it won't let me change my layout, and I've already tried saving as a Parcelable
, which was unsuccessful. I'm sure it's something easy I'm missing, but hey, I'm new to developing. Any help or suggestions on my code is greatly appreciated. Thanks!
Below are my classes, which I have shortened only to the necessary code.
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);
}
}
}
Solution
EDIT:
I was finally able to make this work using multiple factors. If any one of these were left out, it simply would not work.
Create new GridLayoutManager
in my onCreate
gridLayoutManager = new GridLayoutManager(getApplicationContext(), columns);
Save the RecyclerView state in 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);
}
Include configChanges
in the AndroidManifest.xml
Saving and restoring the RecyclerView
state was not enough. I also had to go against the grain and change my AndroidManifest.xml
to 'android:configChanges="orientation|screenSize"'
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize">
Restore the RecyclerView
state in onConfigurationChanged
using a Handler
The handler was one of the most important parts, if it's not included, it simply will not work and the position of the RecyclerView
will reset to 0. I guess this has been a flaw going back a few years, and was never fixed. I then changed the GridLayoutManager
's setSpanCount
depending on the 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);
}
Like I said, all of these changes needed to be included, at least for me. I don't know if this is the most efficient way, but after spending many hours, it works for me.
Answered By - Drew Szurko
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.