DHines
DHines

Reputation: 179

Android MVVM Architecture Components: How do I update a single data entity from a list fragment?

I've been trying to learn the new Android Architecture Components. I've been basing my application off of this example: Basic Sample

I have it working, but I am stuck on how to insert, update or delete the data in the Room database. In the example it does not provide examples for those.

Going off of the example provided I am trying to figure out how to make changes to each Product from the ProductListFragment. In my application I am not using Product but a defined Zone. So for example in the list I want to have a button where when pressed will delete that Zone. Or a button that may update a property of the Zone, for example Zone has an active property. When the button is click on that CardView it will update the zone's active property.

ZoneListFragment

public class ZoneListFragment extends android.support.v4.app.Fragment {

    public static final String TAG = "ZoneListViewModel";
    private ZoneAdapter mZoneAdapter;
    private FragmentZoneListBinding mBinding;
    private ZoneListViewModel viewModel;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_zone_list, container, false);

        mZoneAdapter = new ZoneAdapter(mZoneClickCallback, mZoneDirectionClickCallback);
        mBinding.zonesList.setAdapter(mZoneAdapter);

        return mBinding.getRoot();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        viewModel = ViewModelProviders.of(this).get(ZoneListViewModel.class);
        subscribeUi(viewModel);
    }

    private void subscribeUi(ZoneListViewModel viewModel) {
        // Update the list when the data changes
        viewModel.getZones().observe(this, new Observer<List<ZoneEntity>>() {
            @Override
            public void onChanged(@Nullable List<ZoneEntity> myZones) {
                if (myZones != null) {
                    mBinding.setIsLoading(false);
                    mZoneAdapter.setZoneList(myZones);
                } else {
                    mBinding.setIsLoading(true);
                }
                mBinding.executePendingBindings();
            }
        });
    }

    private final ZoneClickCallback mZoneClickCallback = new ZoneClickCallback() {
        @Override
        public void onClick(Zone zone) {

        }
    };

    private final ZoneDirectionClickCallback mZoneDirectionClickCallback = new ZoneDirectionClickCallback() {
        @Override
        public void onClick(Zone zone) {
            zone.setDirection(zone.isDirection() ? false : true);
            viewModel.updateZone(zone);
        }
    };
}

ZoneListViewModel

public class ZoneListViewModel extends AndroidViewModel {
    private final MediatorLiveData<List<ZoneEntity>> mObservableZones;
    private DataRepository repository;

    public ZoneListViewModel(Application application) {
        super(application);

        mObservableZones = new MediatorLiveData<>();
        // set by default null, until we get data from the database.
        mObservableZones.setValue(null);

        LiveData<List<ZoneEntity>> zones = ((Zoneify) application).getRepository()
                .getZones();

        // observe the changes of the zones from the database and forward them
        mObservableZones.addSource(zones, mObservableZones::setValue);
    }

    public LiveData<List<ZoneEntity>> getZones() {
        return mObservableZones;
    }
}

ZoneAdapter

public class ZoneAdapter extends RecyclerView.Adapter<ZoneAdapter.ZoneViewHolder> {

    List<? extends Zone> mZoneList;

    @Nullable
    private final ZoneClickCallback mZoneClickCallback;
    @Nullable
    private final ZoneDirectionClickCallback mZoneDirectionClickCallback;

    public ZoneAdapter(@Nullable ZoneClickCallback zoneClickCallback,
                       @Nullable ZoneDirectionClickCallback zoneDirectionClickCallback) {
        mZoneClickCallback = zoneClickCallback;
        mZoneDirectionClickCallback = zoneDirectionClickCallback;
    }

    public void setZoneList(final List<? extends Zone> zoneList) {
        if (mZoneList == null) {
            mZoneList = zoneList;
            notifyItemRangeInserted(0, zoneList.size());
        } else {
            DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                @Override
                public int getOldListSize() {
                    return mZoneList.size();
                }

                @Override
                public int getNewListSize() {
                    return zoneList.size();
                }

                @Override
                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                    return mZoneList.get(oldItemPosition).getId() ==
                            zoneList.get(newItemPosition).getId();
                }

                @Override
                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                    Zone newZone = zoneList.get(newItemPosition);
                    Zone oldZone = mZoneList.get(oldItemPosition);
                    return newZone.getId() == oldZone.getId()
                            && Objects.equals(newZone.getName(), oldZone.getName())
                            && Objects.equals(newZone.getAddress(), oldZone.getAddress())
                            && newZone.isActive() == oldZone.isActive()
                            && newZone.isDirection() == oldZone.isDirection()
                            && Objects.equals(newZone.getLatLng(), oldZone.getLatLng())
                            && Objects.equals(newZone.getNotification(), oldZone.getNotification());
                }
            });
            mZoneList = zoneList;
            result.dispatchUpdatesTo(this);
        }
    }

    @Override
    public ZoneViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ZoneItemBinding binding = DataBindingUtil
                .inflate(LayoutInflater.from(parent.getContext()), R.layout.zone_item,
                        parent, false);
        binding.setZoneClickcallback(mZoneClickCallback);
        binding.setZoneDirectionClickCallback(mZoneDirectionClickCallback);
        return new ZoneViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ZoneViewHolder holder, int position) {
        holder.binding.setZone(mZoneList.get(position));
        holder.binding.executePendingBindings();
    }

    @Override
    public int getItemCount() {
        return mZoneList == null ? 0 : mZoneList.size();
    }

    static class ZoneViewHolder extends RecyclerView.ViewHolder {

        final ZoneItemBinding binding;

        public ZoneViewHolder(ZoneItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }
}

zone_item.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:map="http://schemas.android.com/tools">
    <data>
        <variable name="zone"
            type="com.davidh.zoneify.data.model.Zone"/>
        <variable name="zoneClickcallback"
            type="com.davidh.zoneify.view.ZoneClickCallback"/>
        <variable name="zoneDirectionClickCallback"
            type="com.davidh.zoneify.view.ZoneDirectionClickCallback"/>
    </data>

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="100dp"
        android:onClick="@{() ->  callback.onClick(zone)}"
        android:orientation="horizontal"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        app:cardUseCompatPadding="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="125dp"
            android:orientation="horizontal"
            android:background="@color/colorPrimary">

            <com.google.android.gms.maps.MapView
                android:id="@+id/map"
                android:layout_width="0dp"
                android:layout_weight="1"
                android:layout_height="match_parent"
                map:cameraZoom="15"
                map:mapType="normal"
                map:liteMode="true"
                app:initMap="@{zone.latLng}"/>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_weight="3"
                android:layout_height="match_parent"
                android:orientation="vertical">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="10dp"
                        android:layout_marginTop="5dp"
                        android:text="@{zone.name}"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title.Inverse"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="10dp"
                        android:layout_marginTop="5dp"
                        android:text="@{zone.notification}"
                        android:textAppearance="@style/TextAppearance.AppCompat.Small.Inverse"/>

                </LinearLayout>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="fill_parent"
                    android:orientation="horizontal"
                    android:layout_gravity="center|bottom">

                    <Button
                        android:layout_width="0dp"
                        android:layout_height="@dimen/imageButtonCardView"
                        android:layout_weight="1"
                        android:layout_gravity="center_horizontal|center|center_vertical"
                        android:background="@color/colorPrimary"
                        android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button.Inverse"
                        style="?android:attr/borderlessButtonStyle"
                        android:id="@+id/btn_zone_direction"
                        android:drawableTop="@{zone.direction ? @drawable/ic_leaving_24 : @drawable/ic_entering_24}"
                        android:text="@{zone.direction ? @string/leaving : @string/entering}"
                        android:onClick="@{() ->  directionCallback.onClick(zone)}"/>

                    <Button
                        android:layout_width="0dp"
                        android:layout_height="@dimen/imageButtonCardView"
                        android:layout_weight="1"
                        android:layout_gravity="center_horizontal|center|center_vertical"
                        android:background="@color/colorPrimary"
                        android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button.Inverse"
                        style="?android:attr/borderlessButtonStyle"
                        android:id="@+id/btn_zone_active"
                        android:drawableTop="@{zone.active ? @drawable/ic_alarm_on_24 : @drawable/ic_alarm_off_24}"
                        android:text="@{zone.active ? @string/on : @string/off}"/>

                    <Button
                        android:layout_width="0dp"
                        android:layout_height="@dimen/imageButtonCardView"
                        android:layout_weight="1"
                        android:layout_gravity="center_horizontal|center|center_vertical"
                        android:background="@color/colorPrimary"
                        android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button.Inverse"
                        style="?android:attr/borderlessButtonStyle"
                        android:id="@+id/btn_zone_edit"
                        android:drawableTop="@drawable/ic_edit_24"
                        android:text="@string/edit"
                        android:padding="@dimen/buttonCardViewPadding"/>

                    <Button
                        android:layout_width="0dp"
                        android:layout_height="@dimen/imageButtonCardView"
                        android:layout_weight="1"
                        android:layout_gravity="center_horizontal|center|center_vertical"
                        android:background="@color/colorPrimary"
                        android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button.Inverse"
                        style="?android:attr/borderlessButtonStyle"
                        android:padding="@dimen/buttonCardViewPadding"
                        android:id="@+id/btn_zone_delete"
                        android:drawableTop="@drawable/ic_delete_24"
                        android:text="@string/delete"/>

                </LinearLayout>

            </LinearLayout>

        </LinearLayout>

    </android.support.v7.widget.CardView>
</layout>

The rest of the setup is the same as in the Basic Sample. Just stuck on how to update a zone from the list.

Upvotes: 1

Views: 2463

Answers (1)

LordRaydenMK
LordRaydenMK

Reputation: 13321

On delete/update click you will need to update your model (your repository). The repository will emit a new LiveData item, the adapter will be updated and DiffUtils will do it's job. It will either remove or update the item.

For example a delete action:

  • Set a click listener to the Delete button
  • From the listener call viewModel.deleteItem(id) (this is your ZoneListViewModel)
  • In the ViewModel call repository.deleteItem(id)
  • In the repository use Room to delete the item
  • Room will emit a new value with the updated list (without the deleted item).
  • The Adapter observes this list and together with DiffUtils will remove the item.

Here is an example from android-architecture-blueprints

Upvotes: 5

Related Questions