is it good to use ViewModel approach in single item view inside adapter of recycler view?

Viewed 1179

I'm using MVVM architecture components in my app normally but I've noticed that i can create a new ViewModel specific for the single item view inside adapter with a recycler view.

so my question is this a good practice? to create viewmodel for single item? i already create a viewmodel for the fragment but why for the adapter and the single item view ?! that would be 2 view models would be created! thanks for help.

Lets have a look:

AllShopsItem.java

works as the model of the item

public class AllShopsItem {

private String image;
private String name;
private float rate;
private String details;
private String miniCharge;
private String shipping;

public AllShopsItem(String image, String name, float rate, String details, String miniCharge, String shipping) {
    this.image = image;
    this.name = name;
    this.rate = rate;
    this.details = details;
    this.miniCharge = miniCharge;
    this.shipping = shipping;
}

public String getImage() {
    return image;
}

public void setImage(String image) {
    this.image = image;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public float getRate() {
    return rate;
}

public void setRate(float rate) {
    this.rate = rate;
}

public String getDetails() {
    return details;
}

public void setDetails(String details) {
    this.details = details;
}

public String getMiniCharge() {
    return miniCharge;
}

public void setMiniCharge(String miniCharge) {
    this.miniCharge = miniCharge;
}

public String getShipping() {
    return shipping;
}

public void setShipping(String shipping) {
    this.shipping = shipping;
}
}//end model class

SingleItemAllShopsViewModel.java

view model of the item

public class SingleItemAllShopsViewModel extends BaseViewModel {
private AllShopsItem allShopsItem;

public SingleItemAllShopsViewModel(AllShopsItem allShopsItem) {
    this.allShopsItem = allShopsItem;
}

public void setUp() {
    // perform set up tasks, such as adding listeners
}

public void disposeListeners() {
    // perform tear such as removing listeners
}

@Bindable
public AllShopsItem getAllShopsItem() {
    return allShopsItem;
}

@BindingAdapter({"imageUrl"})
public static void setImageUrl(ImageView view, String imagePath){
    Timber.d(imagePath);
    ConnectionHelper.loadImage(view,  imagePath);
}

}//end viewmodel

AllShopsViewHolder.java

view holder of adapter

public class AllShopsViewHolder extends RecyclerView.ViewHolder {
private SingleItemAllShopsBinding binding;

public AllShopsViewHolder(@NonNull View itemView) {
    super(itemView);
    bind();
}

public void bind() {
    if (binding == null) {
        binding = DataBindingUtil.bind(itemView);
    }
}

public void unbind() {
    if (binding != null) {
        binding.unbind(); // Don't forget to unbind
    }
}

public void setViewModel(SingleItemAllShopsViewModel viewModel) {
    if (binding != null) {
        binding.setViewModelBinding(viewModel);
    }
}

}//end view holder

AllShopsAdapter.java

adapter class

public class AllShopsAdapter extends RecyclerView.Adapter<AllShopsViewHolder> {
private ArrayList<AllShopsItem> dataList;

public void setNewDataList(ArrayList<AllShopsItem> dataList) {
    this.dataList = dataList;
    notifyDataSetChanged();
}

public void updateDataList(ArrayList<AllShopsItem> dataList) {
    this.dataList.clear();
    this.dataList.addAll(dataList);
    notifyDataSetChanged();
}

@Override
public void onViewAttachedToWindow(@NonNull AllShopsViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    Timber.d("onViewAttachedToWindow");
    holder.bind();
}

@Override
public void onViewDetachedFromWindow(@NonNull AllShopsViewHolder holder) {
    super.onViewDetachedFromWindow(holder);
    Timber.d("onViewDetachedFromWindow");
    holder.unbind();
}

@NonNull
@Override
public AllShopsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_item_all_shops,
            new FrameLayout(parent.getContext()), false);
    return new AllShopsViewHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull AllShopsViewHolder holder, int position) {
    SingleItemAllShopsViewModel allShopsViewModel = new SingleItemAllShopsViewModel(getCurrentItem(position));
    holder.setViewModel(allShopsViewModel);
}

public AllShopsItem getCurrentItem(int pos) {
    return dataList.get(pos);
}

@Override
public int getItemCount() {
    return dataList != null && !dataList.isEmpty() ? dataList.size() : 0;
}
}//end adapter

single_item_all_shops.xml

single item design

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>
    <variable
        name="viewModelBinding"


type="grand.shopness.view.adapter.itemviewmodel.SingleItemAllShopsViewModel" />
</data>

<androidx.cardview.widget.CardView
    android:id="@+id/cv_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:id="@+id/rl_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingStart="@dimen/dp16w"
        android:paddingEnd="@dimen/dp16w"
        android:paddingTop="@dimen/dp8h"
        android:paddingBottom="@dimen/dp8h">

        <androidx.cardview.widget.CardView
            android:id="@+id/cv_image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            app:cardElevation="8dp"
            android:layout_centerVertical="true"
            android:layout_alignParentStart="true">
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:src="@drawable/slider_img"
            android:scaleType="fitXY"
            tools:ignore="ContentDescription"
            app:imageUrl="@{viewModelBinding.allShopsItem.image}"/>
        </androidx.cardview.widget.CardView>


        <RelativeLayout
            android:id="@+id/rl_info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toEndOf="@id/cv_image"
            android:paddingStart="@dimen/dp4w"
            android:paddingEnd="@dimen/dp4w"
            android:layout_toStartOf="@id/rl_prices"
            android:layout_centerVertical="true">

            <TextView
                android:id="@+id/tv_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:fontFamily="@font/raleway_medium"
                tools:text="Shop name"
                android:textColor="@android:color/black"
                android:textSize="14sp"
                android:textStyle="bold"
                android:text="@{viewModelBinding.allShopsItem.name}"/>

            <RatingBar
                android:id="@+id/ratingBar"
                style="@style/RatingBarStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_name"
                android:indeterminate="false"
                android:numStars="5"
                android:layout_marginTop="@dimen/dp2h"
                android:layout_alignParentStart="true"
                tools:rating="5"
                android:rating="@{viewModelBinding.allShopsItem.rate}"/>

            <TextView
                android:id="@+id/tv_details"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@font/raleway_medium"
                tools:text="detailssssssssssssssasdasdasdsadasdasdasdasdadasdasdasdasdasdasdasdasdsssss"
                android:textColor="@android:color/black"
                android:layout_alignParentStart="true"
                android:layout_below="@id/ratingBar"
                android:maxLines="2"
                android:ellipsize="end"
                android:text="@{viewModelBinding.allShopsItem.details}"/>

        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/rl_prices"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginStart="@dimen/dp4w">

            <TextView
                android:id="@+id/tv_mini_charge_static"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@font/raleway_medium"
                android:text="@string/mini_charge"
                android:textColor="@android:color/black"
                android:layout_centerHorizontal="true"
                android:layout_alignParentTop="true" />

            <TextView
                android:id="@+id/tv_mini_charge"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{viewModelBinding.allShopsItem.miniCharge}"
                tools:text="150"
                android:textColor="@android:color/black"
                android:layout_centerHorizontal="true"
                android:layout_below="@id/tv_mini_charge_static"/>

            <TextView
                android:id="@+id/tv_shipping_static"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@font/raleway_medium"
                android:text="@string/shipping"
                android:textColor="@android:color/black"
                android:layout_centerHorizontal="true"
                android:layout_below="@id/tv_mini_charge"/>

            <TextView
                android:id="@+id/tv_shipping"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="10"
                android:text="@{viewModelBinding.allShopsItem.shipping}"
                android:textColor="@android:color/black"
                android:layout_centerHorizontal="true"
                android:layout_below="@id/tv_shipping_static"/>

        </RelativeLayout>
    </RelativeLayout>
</androidx.cardview.widget.CardView>
</layout>

this how it will looks like:

enter image description here

3 Answers

I don't think so to be honest, it looks pretty weird. Yeah, viewmodel is designed to manage UI-related data and allow data to survive from configuration changes, but doing this you're just adding some complexity to your adapter without any good reason. Your viewmodel is there to help you handle your Activity/Fragment data, that includes your recyclerview data, that's it, just make sure that your viewmodel is saving your recycler view state properly and keep your adapter simple

I think it is a bad idea . the reason is viewModel created for working with lifecycle aware component like Activity,Fragment not for this kind of use

The official documentation state that

ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment official doc

I think Google here didn't have a clear picture. IMO, ViewModel as an architecture component should be different than viewModel that represents observable data for the view.

If ViewModel would be just a set of observable data with available actions over it, it would make sense to have a ViewModel per item. As an architecture component, to me, it looks like it should accompany only Activities & Fragments.

IMO this is where Google messed up MVVM and makes it much harder to write "clean code".

Related