Main Goal:-
I have a list of sports news. Each item contains a sport name and some info. Clicking on it will show the latest news regarding that particular sport. The user has the option to swipe to dismiss a news, if they don't want it in the list or they can also drag and drop it, for example, if they want to see some news on top of others. Each item in the list is represented programmatically as a Sport.java object.
I want to retain the state of the list upon device orientation changes.
What I've tried:-
For the list, I have an arraylist of sport objects (ArrayList). I learned that to save a list of custom objects, they objects themselves need to be Parcelable. For this, I implemented the Parcelable.java interface like this:
package com.example.android.materialme;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
/**
* Data model for each row of the RecyclerView.
*/
class Sport implements Parcelable {
//Member variables representing the title and information about the sport
private String title;
private String info;
private String detail;
private final int imageResource;
/**
* Constructor for the Sport data model
* @param title The name if the sport.
* @param info Information about the sport.
*/
Sport(String title, String info, String detail, int imageResource) {
this.title = title;
this.info = info;
this.detail = detail;
this.imageResource = imageResource;
}
protected Sport(@NonNull Parcel in) {
title = in.readString();
info = in.readString();
detail = in.readString();
imageResource = in.readInt();
}
public static final Creator<Sport> CREATOR = new Creator<Sport>() {
@Override
public Sport createFromParcel(Parcel in) {
return new Sport(in);
}
@Override
public Sport[] newArray(int size) {
return new Sport[size];
}
};
String getTitle() {
return title;
}
String getInfo() {
return info;
}
int getImageResource(){
return imageResource;
}
String getDetail(){
return detail;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(title);
parcel.writeString(info);
parcel.writeString(detail);
parcel.writeInt(imageResource);
}
}
and then I used
outState.putParcelableArrayList(KEY, sportsList);
but, this doesn't work. The screen is just blank upon rotating device.
I tried debugging the app and found that the arraylist was being passed correctly with the data intact, it's just that the app is not being able to display it for some reason.
Also, the implementation of the fab button is so that it resets the whole list to its initial condition upon click. The fab works normally but if the orientation is changed once, it stops working (app doesn't crash). Changing the orientation back also doesn't fix the fab. So, to get the list again for any other test, I have to rerun the entire app.
Complete Code:-
MainActivity.java
package com.example.android.materialme;
import android.content.res.TypedArray;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import java.util.Collections;
public class MainActivity extends AppCompatActivity {
//Member variables
private RecyclerView mRecyclerView;
private ArrayList<Sport> mSportsData;
private SportsAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(view -> resetSports());
//Initialize the RecyclerView
mRecyclerView = (RecyclerView)findViewById(R.id.recyclerView);
//Set the Layout Manager
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//Initialize the ArrayLIst that will contain the data
mSportsData = new ArrayList<>();
//Initialize the adapter and set it ot the RecyclerView
mAdapter = new SportsAdapter(this, mSportsData);
mRecyclerView.setAdapter(mAdapter);
initializeData(savedInstanceState);
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
Collections.swap(mSportsData, from, to);
mAdapter.notifyItemMoved(from, to);
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
mSportsData.remove(viewHolder.getAdapterPosition());
mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
});
helper.attachToRecyclerView(mRecyclerView);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("state", mSportsData);
}
/**
* Method for initializing the sports data from resources.
*/
private void initializeData(Bundle savedInstanceState) {
if(savedInstanceState!=null){
mSportsData.clear();
mSportsData = savedInstanceState.getParcelableArrayList("state");
} else {
//Get the resources from the XML file
String[] sportsList = getResources().getStringArray(R.array.sports_titles);
String[] sportsInfo = getResources().getStringArray(R.array.sports_info);
String[] sportsDetail = getResources().getStringArray(R.array.sports_detail);
TypedArray sportsImageResource = getResources().obtainTypedArray(R.array.sports_images);
//Clear the existing data (to avoid duplication)
mSportsData.clear();
//Create the ArrayList of Sports objects with the titles and information about each sport
for (int i = 0; i < sportsList.length; i++) {
mSportsData.add(new Sport(sportsList[i], sportsInfo[i], sportsDetail[i], sportsImageResource.getResourceId(i, 0)));
}
sportsImageResource.recycle();
}
//Notify the adapter of the change
mAdapter.notifyDataSetChanged();
}
public void resetSports(){
initializeData(null);
}
}
App Images:-
#1 Initial List
#2 Changed List (Card #2 for sport basketball is swiped)
Even though the question is 4 months old and you probably don't need the answer anymore:
The problem is that you initialize the adapter with mSportsData, but reassign another value to the variable later in initializeData(). The ArrayList bound to the adapter is still the empty one it got initialized with.
A way to solve it would be to initialize mSportsData with either a new ArrayList if savedInstanceState is null or else the saved value, and to call initializeData only if savedInstanceState is null. You can remove the argument and therefore the if from initalizeData() completely.
// Initialize the ArrayList that will contain the data.
if (savedInstanceState == null) {
mSportsData = new ArrayList<>();
} else {
mSportsData = savedInstanceState.getParcelableArrayList("state");
}
//Initialize the adapter and set it ot the RecyclerView (nothing changed here)
mAdapter = new SportsAdapter(this, mSportsData);
mRecyclerView.setAdapter(mAdapter);
if (savedInstanceState == null) {
initializeData();
}