07-31-2023, 09:59 AM
I am experimenting with integrating a Firebase-backed RecyclerView in a React Native app. With hardcoded data it works well, but upon inserting rows loaded dynamically and calling either notifyItemInserted() or notifyDataSetChanged() on the RecyclerView.Adapter, the RecyclerView itself does not reflect the change. This manifests as an initial blank view until the app's refresh button is tapped, which has the sole effect of re-rendering the RecyclerView from the React Native side, or until scrolling or a screen orientation change.
After reading through a dozen or more questions on this site about similar issues not involving React Native and having tried most of the common solutions, I suspect this issue is related specifically to React Native.
Finding a solution to this could be helpful to many, given the ongoing performance issues with React Native's list view implementations.
public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.ViewHolder> {
ArrayList<Post> mDataset;
public PostsAdapter(ArrayList<Post> list){
mDataset = list;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public LinearLayout mPostView;
public ViewHolder(LinearLayout v) {
super(v);
mPostView = v;
}
}
@Override
public PostsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(R.layout.post, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
TextView tv = (TextView) holder.mPostView.findViewById(R.id.title);
tv.setText(mDataset.get(position).title);
}
@Override
public int getItemCount() {
return mDataset.size();
}
}
public class RNPostsViewManager extends SimpleViewManager{
private ArrayList<Post> mDataset = new ArrayList<>();
public static final String REACT_CLASS = "AndroidPostsView";
private RecyclerView mRecyclerView;
private PostsAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private DatabaseReference mDatabase;
private Query initialDataQuery;
private ChildEventListener initialDataListener;
@Override
public String getName() {
return REACT_CLASS;
}
@UiThread
public void addPost (Post p){
mDataset.add(p);
mAdapter.notifyItemInserted(mDataset.size()-1);
}
@Override
public RecyclerView createViewInstance( ThemedReactContext context) {
mAdapter = new PostsAdapter(mDataset);
mRecyclerView = new RecyclerView(context){
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
initialDataQuery.addChildEventListener(initialDataListener);
}
};
mLayoutManager = new LinearLayoutManager(context.getCurrentActivity(), LinearLayoutManager.VERTICAL, false);
DividerItemDecoration mDecoration = new DividerItemDecoration(context, 1);
mDecoration.setDrawable(ContextCompat.getDrawable(context.getCurrentActivity(), R.drawable.sep));
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.addItemDecoration(mDecoration);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(mAdapter);
mDatabase = FirebaseDatabase.getInstance().getReference();
initialDataQuery = mDatabase.child("wp-posts").orderByChild("unixPostDate").limitToFirst(100);
initialDataListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
Post p = dataSnapshot.getValue(Post.class);
addPost(p);
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
};
return mRecyclerView;
}
}
// layout file post.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/background_light"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp"
android:weightSum="1">
<Button
android:id="@+id/button"
style="@style/Widget.AppCompat.Button.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:background="@android:drawable/ic_menu_more"
android:gravity="center_vertical" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:orientation="horizontal"
android:weightSum="1">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:layout_weight="1"
android:gravity="top"
android:text="TextView"
android:textColor="@color/title"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
After reading through a dozen or more questions on this site about similar issues not involving React Native and having tried most of the common solutions, I suspect this issue is related specifically to React Native.
Finding a solution to this could be helpful to many, given the ongoing performance issues with React Native's list view implementations.
public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.ViewHolder> {
ArrayList<Post> mDataset;
public PostsAdapter(ArrayList<Post> list){
mDataset = list;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public LinearLayout mPostView;
public ViewHolder(LinearLayout v) {
super(v);
mPostView = v;
}
}
@Override
public PostsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(R.layout.post, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
TextView tv = (TextView) holder.mPostView.findViewById(R.id.title);
tv.setText(mDataset.get(position).title);
}
@Override
public int getItemCount() {
return mDataset.size();
}
}
public class RNPostsViewManager extends SimpleViewManager{
private ArrayList<Post> mDataset = new ArrayList<>();
public static final String REACT_CLASS = "AndroidPostsView";
private RecyclerView mRecyclerView;
private PostsAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private DatabaseReference mDatabase;
private Query initialDataQuery;
private ChildEventListener initialDataListener;
@Override
public String getName() {
return REACT_CLASS;
}
@UiThread
public void addPost (Post p){
mDataset.add(p);
mAdapter.notifyItemInserted(mDataset.size()-1);
}
@Override
public RecyclerView createViewInstance( ThemedReactContext context) {
mAdapter = new PostsAdapter(mDataset);
mRecyclerView = new RecyclerView(context){
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
initialDataQuery.addChildEventListener(initialDataListener);
}
};
mLayoutManager = new LinearLayoutManager(context.getCurrentActivity(), LinearLayoutManager.VERTICAL, false);
DividerItemDecoration mDecoration = new DividerItemDecoration(context, 1);
mDecoration.setDrawable(ContextCompat.getDrawable(context.getCurrentActivity(), R.drawable.sep));
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.addItemDecoration(mDecoration);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(mAdapter);
mDatabase = FirebaseDatabase.getInstance().getReference();
initialDataQuery = mDatabase.child("wp-posts").orderByChild("unixPostDate").limitToFirst(100);
initialDataListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
Post p = dataSnapshot.getValue(Post.class);
addPost(p);
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
};
return mRecyclerView;
}
}
// layout file post.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/background_light"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp"
android:weightSum="1">
<Button
android:id="@+id/button"
style="@style/Widget.AppCompat.Button.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:background="@android:drawable/ic_menu_more"
android:gravity="center_vertical" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:orientation="horizontal"
android:weightSum="1">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:layout_weight="1"
android:gravity="top"
android:text="TextView"
android:textColor="@color/title"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>