How to load data into a RecyclerView inside a fragment using ViewModel

Viewed 1360

I am trying to create a app with bottom navigation, that switches between several fragments.

Inside one of these I need to list data in a recyclerview. I am rather new to android and I have never used a recyclerview or used ViewModels or fragments before so I am not 100% sure if I am doing this the right way.

Here is what I have right now:

Fragment.java

public class twoFragment extends Fragment implements LifecycleOwner {

    private twoViewModel twoViewModel;
    private RecyclerView recyclerView;
    private RecyclerViewAdapter recyclerViewAdapter;

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {


        twoViewModel = new ViewModelProvider(this).get(twoViewModel.class);
        View root = inflater.inflate(R.layout.two, container, false);

        recyclerView = root.findViewById(R.id.rv_main);
        recyclerView.setHasFixedSize(true);

        twoViewModel.getUserMutableLiveData().observe(getViewLifecycleOwner(), userListUpdateObserver);


        return root;
    }

        private Observer<ArrayList<User>> userListUpdateObserver = new Observer<ArrayList<User>>() {
            @Override
            public void onChanged(ArrayList<User> userArrayList) {

                recyclerViewAdapter = new RecyclerViewAdapter(requireActivity(),userArrayList);
                recyclerView.setAdapter(recyclerViewAdapter);
            }
        };

}

ViewModel.java

public class twoViewModel  extends ViewModel {

    MutableLiveData<ArrayList<User>> userLiveData;
    ArrayList<User> userArrayList;

    public twoViewModel() {
        userLiveData = new MutableLiveData<>();

        // call your Rest API in init method
        init();
    }

    public MutableLiveData<ArrayList<User>> getUserMutableLiveData() {
        return userLiveData;
    }

    public void init(){
        populateList();
        userLiveData.setValue(userArrayList);
    }

    public void populateList(){

    User user = new User();
        user.setTitle("Darknight");
        user.setDescription("Best video ever!");

        userArrayList = new ArrayList<>();
        userArrayList.add(user);
        userArrayList.add(user);
        userArrayList.add(user);
        userArrayList.add(user);
        userArrayList.add(user);
        userArrayList.add(user);

}



}

RecyclerViewAdapter.java

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    Activity context;
    ArrayList<User> userArrayList;

    public RecyclerViewAdapter(Activity context, ArrayList<User> userArrayList) {
        this.context = context;
        this.userArrayList = userArrayList;
    }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View rootView = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
        System.out.println("onCreateViewHolder");
        return new RecyclerViewViewHolder(rootView);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

        User user = userArrayList.get(position);
        RecyclerViewViewHolder viewHolder= (RecyclerViewViewHolder) holder;

        viewHolder.txtView_title.setText(user.getTitle());
        viewHolder.txtView_description.setText(user.getDescription());
        viewHolder.imgView_icon.setImageDrawable(user.getImgIcon());
        System.out.println("onBindViewHolder");
    }

    @Override
    public int getItemCount() {
        return userArrayList.size();
    }

    class RecyclerViewViewHolder extends RecyclerView.ViewHolder {
        ImageView imgView_icon;
        TextView txtView_title;
        TextView txtView_description;

        public RecyclerViewViewHolder(@NonNull View itemView) {
            super(itemView);
            imgView_icon = itemView.findViewById(R.id.imgView_icon);
            txtView_title = itemView.findViewById(R.id.txtView_title);
            txtView_description = itemView.findViewById(R.id.txtView_description);
            System.out.println("RecyclerViewViewHolder");

        }
    }
}

The problem

With this code, the recyclerview is empty when I open the app. However, when I click on the EditText view that I have in the fragment the data appears in recyclerview. Then, as soon as I leave the fragment and go back to it, it is empty.

What am I doing wrong here?

I read somewhere you are not supposed to use ViewModels and recyclerviews together...how does that work?

4 Answers

Try changing your twoFragment to:

public class twoFragment extends Fragment {

private twoViewModel twoViewModel;
private RecyclerView recyclerView;
private RecyclerViewAdapter recyclerViewAdapter;

public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {


    twoViewModel = new ViewModelProvider(this).get(twoViewModel.class);
    View root = inflater.inflate(R.layout.two, container, false);

    recyclerView = root.findViewById(R.id.rv_main);
    recyclerView.setHasFixedSize(true);
    
    recyclerViewAdapter = new RecyclerViewAdapter(requireContext());
    recyclerView.setAdapter(recyclerViewAdapter);

    twoViewModel.getUserMutableLiveData().observe(getViewLifecycleOwner(), userListUpdateObserver);


    return root;
}

    private Observer<ArrayList<User>> userListUpdateObserver = new Observer<ArrayList<User>>() {
        @Override
        public void onChanged(ArrayList<User> userArrayList) {
            recyclerView.setData(userArrayList);
        }
    };

}

And change the RecycleView Adapter to:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

Context context;
ArrayList<User> userArrayList = ArrayList<User>();

public RecyclerViewAdapter(Context context) {
    this.context = context;
}

public setData(ArrayList<User> userArrayList){
    userArrayList = userArrayList;
    notifyDataSetChanged();
}


@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View rootView = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
    System.out.println("onCreateViewHolder");
    return new RecyclerViewViewHolder(rootView);
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    User user = userArrayList.get(position);
    RecyclerViewViewHolder viewHolder= (RecyclerViewViewHolder) holder;

    viewHolder.txtView_title.setText(user.getTitle());
    viewHolder.txtView_description.setText(user.getDescription());
    viewHolder.imgView_icon.setImageDrawable(user.getImgIcon());
    System.out.println("onBindViewHolder");
}

@Override
public int getItemCount() {
    return userArrayList.size();
}

class RecyclerViewViewHolder extends RecyclerView.ViewHolder {
    ImageView imgView_icon;
    TextView txtView_title;
    TextView txtView_description;

    public RecyclerViewViewHolder(@NonNull View itemView) {
        super(itemView);
        imgView_icon = itemView.findViewById(R.id.imgView_icon);
        txtView_title = itemView.findViewById(R.id.txtView_title);
        txtView_description = itemView.findViewById(R.id.txtView_description);
        System.out.println("RecyclerViewViewHolder");

    }
}
}

Side note: I am pretty sure you do not need to implement LifeCyclerOwner explicitly on a fragment, it already implements it.

I couldn't see any errors in your code except you need to set the LayoutManager to the RecyclerView

recyclerView = root.findViewById(R.id.rv_main);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); // <<< Add this
recyclerView.setHasFixedSize(true);

try calling recyclerViewAdapter.notifyDataSetChanged() inside onchanged method in fragment.

Your fragment may observing different userLiveData instance that's why every time when you leave and go back to fragment ,recycler view is empty.To observe same userLiveData instance use below code.

    public class twoViewModel  extends ViewModel {

    MutableLiveData<ArrayList<User>> userLiveData;
    ArrayList<User> userArrayList;

    public twoViewModel() {
        
        // call your Rest API in init method
        init();
    }

    public MutableLiveData<ArrayList<User>> getUserMutableLiveData() {
     if(userLiveData==null)
      userLiveData = new MutableLiveData<>();
        return userLiveData;
    }

public void init(){
        populateList();
        getUserMutableLiveData().setValue(userArrayList);
    }
Related