RecylcerView adapter with viewModel and Live data

Viewed 7569

I am starting to use android jetpack arch components and have run into some confusion. Also please note data binding is not a option.

I have an activity that has a RecylcerView. I have a ViewModel that looks like the following

public class Movie extends ViewModel {


public Movie movie;
public URL logoURL;

private MutableLiveData<Drawable> logo;

public MutableLiveData<Drawable> getLogo() {

    if (logo == null) {
        logo = new MutableLiveData<>();
    }
    return logo;
}


public PikTvChannelItemVM(Movie movie, URL logo) {

    this.movie = movie;
    this.logoURL = logoURL;


}

public Bitmap getChannelLogo() {

  //Do some network call to get the bitmap logo from the url
 }

}

The above is all fine although in my I recyclerview have the following code below. Although in onbindviewholder when I try to observe for the returned image from the viewmodels live data it needs a life cycle owner reference which I don't have in my recycler view. Please help thanks

public class MovieRecyclerViewAdapter extends RecyclerView
    .Adapter<MovieRecyclerViewAdapter.MovieItemViewHolder> {

public List<MovieViewModel> vmList;


public MovieRecyclerViewAdapter(List<MovieViewModel> vmList) {

    this.vmList = vmList;
    setHasStableIds(true);
}

@Override
public MovieItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    MovieView itemView = new MovieView(parent.getContext(), null);
    itemView.setLayoutParams(new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
    ));
    return new MovieItemViewHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull MovieItemViewHolder holder, int position) {


    vmList.get(position).observe(?????, users -> {

  // As you can see I have no reference to the life cycle owner
       holder.imageView.setimage(some drawable returned by the View Model)
    });
}


@Override
public long getItemId(int position) {

    return position;
}

@Override
public int getItemViewType(int position) {

    return position;
}


@Override
public int getItemCount() {

    return vmList.size();
}

class MovieItemViewHolder extends RecyclerView.ViewHolder {

    private MovieView channelView;

    public MovieItemViewHolder(View v) {

        super(v);
        channelView = (MovieView) v;
    }

    public MovieView getChannelView() {

        return channelView;
    }
 }

}
3 Answers

I would try to send a list to the adapter to display it. I would observe the data outside of the adapter, as it is not the adapter's responsibility to observe the data but rather to display it.

Your adapter list type should not be viewmodel. If you want to use live data without binding, you want auto ui change when your list update.

You can do it like this:

First of all, your movie class must be a model class, not be a viewmodel.

Do it adapter like normal. Just add a setList(Movie) method. This method should update the adapter list. Do not forget notify adapter after update list.

Then create livedata list like

MutableLiveData<List<Movie>> movieLiveData = 
    new MutableLiveData<>();

where you want.

Observe this list in the activity and call adapters setList(Movie)method inside observe{}.

After all this if your list updated, setList(Movie) medhod will be triggered then your ui will be update.

You can do something like this:

ViewModel:

class MyViewModel : ViewModel() {
    private val items = MutableLiveData<List<String>>()

    init {
        obtainList()
    }

    fun obtainList() { // obtain list from repository
        items.value = listOf("item1", "item2", "item3", "item4")
    }

    fun getItems(): LiveData<List<String>> {
        return items
    }
}

Your Fragment (or Activity):

public class ContentFragment extends Fragment {
    private MyViewModel viewModel;
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(MyViewModel.class);
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 
            Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_main, container, false);
        recyclerView = root.findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 4));
       
        // add observer 
        viewModel.getItems().observe(this, items -> {
            adapter = new MyAdapter(items); // add items to adapter
            recyclerView.setAdapter(adapter);
        });

        return root;
    }

Adapter:

class MyAdapter(val list: List<String>) : RecyclerView.Adapter<MyAdapter.TextViewHolder {
    ...
    override fun onBindViewHolder(holder: TextViewHolder, position: Int) {
        holder.textView.text = list[position] // assign titles from the list.
        ...
    }
}

Any custom objects can be used instead of String objects.

Related