Android Application Architecture MVVM mode

preface

With the continuous development and maturity of Android application development technology, developers pay more and more attention to the design of Android application architecture. At present, the application architecture of Android mainly includes MVC, MVP and MVVM modes. Let's talk about MVVM mode.

MVP mode

MVVM mode can be said to be the further development of MVP mode, so let's learn about MVP mode first.

The structure of MVP (Model View Presenter) mode is shown in the following figure:

MVP mode divides the application into three layers: the Model layer is mainly responsible for providing data, the View layer is mainly responsible for displaying the interface, and the Presenter layer is mainly responsible for processing business logic.

In MVP mode, the Model layer and View layer cannot communicate directly. The Presenter layer is responsible for acting as an intermediary to realize the indirect communication between the Model layer and View layer. The View layer and the Presenter layer hold each other's references to realize the communication between the View layer and the Presenter layer.

The main advantages of MVP mode are: it separates the Model layer and View layer, separates the View operation and business logic, and reduces the coupling.

MVVM mode

The structure of MVVM (model view ViewModel) mode is shown in the following figure:

Like MVP mode, MVVM mode also divides applications into three layers, and the responsibilities of each corresponding layer are similar:

  • The Model layer is mainly responsible for providing data. The Model layer provides the data structure of business logic (such as entity classes), provides data acquisition (such as obtaining data from local databases or remote networks), and provides data storage.
  • The View layer is mainly responsible for the display of the interface. The View layer does not involve any business logic processing. It holds a reference to the ViewModel layer and notifies the ViewModel layer when business logic processing is required.
  • The ViewModel layer is mainly responsible for the processing of business logic. The ViewModel layer does not involve any View operations. Through the officially provided Data Binding library, the data in the View layer and the ViewModel layer can be bound. Changes in the data in the ViewModel layer can automatically notify the View layer to update. Therefore, the ViewModel layer does not need to hold the reference of the View layer. The ViewModel layer can be regarded as the combination of the data model of the View layer and the Presenter layer.

The biggest difference between MVVM mode and MVP mode is that the ViewModel layer does not hold a reference to the View layer. This further reduces the coupling, and the change of View layer code will not affect the ViewModel layer.

Compared with MVP mode, MVVM mode has the following advantages:

  • The coupling is further reduced. The ViewModel layer does not hold a reference to the View layer. When the View layer changes, as long as the data bound by the View layer remains unchanged, the ViewModel layer does not need to be changed. In MVP mode, when the View layer changes, the interface for operating the View will change accordingly, and the Presenter layer needs to be modified.
  • You don't have to write a lot of boilerplate code. Through the official Data Binding library, the UI and data can be bound without writing a lot of code for findViewById() and operation view. In short, the code of Activity/Fragment can be quite concise.

example

Here is a simple example to practice the MVVM pattern. The complete project code can be viewed on GitHub:

https://github.com/chongyucaiyan/MVVMDemo

The main functions of the example are: click the button to query the weather on the network, and display the weather information on the interface after the query is successful. The main interface is shown in the figure below:

The code organization structure of MVVM mode is recommended to be divided according to business functions. The specific operations are as follows: each business function is stored in an independent package, and each business function package is subcontracted and stored according to model, View and ViewModel. All models are stored under the model package, all activities and fragments are stored under the activity package, and all viewmodels are stored under the ViewModel package. The example is relatively simple. There is only one weather business function module. The final code organization structure is shown in the following figure:


Write Model

The URL to query Hangzhou weather is:

http://www.weather.com.cn/data/cityinfo/101210101.html

Accessing the URL will return a string of JSON strings as follows:

{"weatherinfo":{"city":"Hangzhou","cityid":"101210101","temp1":"5℃","temp2":"20℃","weather":"Sunny to cloudy","img1":"n0.gif","img2":"d1.gif","ptime":"18:00"}}

According to this JSON string, you can write the corresponding entity class. The code of the WeatherData class is as follows:

public class WeatherData {

    private WeatherInfo weatherinfo;

    public WeatherInfo getWeatherinfo() {
        return weatherinfo;
    }

    public void setWeatherinfo(WeatherInfo weatherinfo) {
        this.weatherinfo = weatherinfo;
    }
}

The code of WeatherInfo class is as follows:

public class WeatherInfo {

    private String city;

    private String cityid;

    private String temp1;

    private String temp2;

    private String weather;

    private String img1;

    private String img2;

    private String ptime;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCityid() {
        return cityid;
    }

    public void setCityid(String cityid) {
        this.cityid = cityid;
    }

    public String getTemp1() {
        return temp1;
    }

    public void setTemp1(String temp1) {
        this.temp1 = temp1;
    }

    public String getTemp2() {
        return temp2;
    }

    public void setTemp2(String temp2) {
        this.temp2 = temp2;
    }

    public String getWeather() {
        return weather;
    }

    public void setWeather(String weather) {
        this.weather = weather;
    }

    public String getImg1() {
        return img1;
    }

    public void setImg1(String img1) {
        this.img1 = img1;
    }

    public String getImg2() {
        return img2;
    }

    public void setImg2(String img2) {
        this.img2 = img2;
    }

    public String getPtime() {
        return ptime;
    }

    public void setPtime(String ptime) {
        this.ptime = ptime;
    }
}

Write ViewModel

ViewModel does not involve any view operations, only business logic processing. Through the officially provided Data Binding library, when the data in the ViewModel changes, the UI will be automatically updated. The code of QueryWeatherViewModel is as follows:

public class QueryWeatherViewModel {

    private static final String TAG = "QueryWeatherViewModel";

    public final ObservableBoolean loading = new ObservableBoolean(false);

    public final ObservableBoolean loadingSuccess = new ObservableBoolean(false);

    public final ObservableBoolean loadingFailure = new ObservableBoolean(false);

    public final ObservableField<String> city = new ObservableField<>();

    public final ObservableField<String> cityId = new ObservableField<>();

    public final ObservableField<String> temp1 = new ObservableField<>();

    public final ObservableField<String> temp2 = new ObservableField<>();

    public final ObservableField<String> weather = new ObservableField<>();

    public final ObservableField<String> time = new ObservableField<>();

    private Call<WeatherData> mCall;

    public QueryWeatherViewModel() {

    }

    public void queryWeather() {
        loading.set(true);
        loadingSuccess.set(false);
        loadingFailure.set(false);

        mCall = RetrofitManager.get()
                .create(QueryWeatherRequest.class)
                .queryWeather();
        mCall.enqueue(new Callback<WeatherData>() {

            @Override
            public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
                WeatherInfo weatherInfo = response.body().getWeatherinfo();
                city.set(weatherInfo.getCity());
                cityId.set(weatherInfo.getCityid());
                temp1.set(weatherInfo.getTemp1());
                temp2.set(weatherInfo.getTemp2());
                weather.set(weatherInfo.getWeather());
                time.set(weatherInfo.getPtime());

                loading.set(false);
                loadingSuccess.set(true);
            }

            @Override
            public void onFailure(Call<WeatherData> call, Throwable t) {
                if (call.isCanceled()) {
                    Log.i(TAG, "call is canceled.");
                } else {
                    loading.set(false);
                    loadingFailure.set(true);
                }
            }
        });
    }

    public void cancelRequest() {
        if (mCall != null) {
            mCall.cancel();
        }
    }
}

Write View

View does not involve any business logic processing, but only displays the interface. In the xml layout file, the UI is bound to the data in the ViewModel through the officially provided Data Binding library. When the data in the ViewModel changes, the UI will be updated automatically. The code of the xml layout file is as follows:

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

    <data>

        <import type="android.view.View" />

        <variable
            name="viewModel"
            type="com.github.cyc.mvvmdemo.weather.viewmodel.QueryWeatherViewModel" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="@dimen/default_content_padding"
        tools:context="com.github.cyc.mvvmdemo.weather.activity.QueryWeatherActivity">

        <Button
            android:id="@+id/btn_query_weather"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="@string/query_weather"
            android:enabled="@{viewModel.loading ? false : true}"
            android:onClick="@{() -> viewModel.queryWeather()}" />

        <RelativeLayout
            android:id="@+id/vg_weather_info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/btn_query_weather"
            android:layout_marginTop="@dimen/query_weather_margin"
            android:visibility="@{viewModel.loadingSuccess ? View.VISIBLE : View.GONE}">

            <TextView
                android:id="@+id/tv_city"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textStyle="bold"
                android:text="@string/city" />

            <TextView
                android:id="@+id/tv_city_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toRightOf="@id/tv_city"
                android:layout_alignBottom="@id/tv_city"
                android:text="@{viewModel.city}"
                tools:text="Hangzhou" />

            <TextView
                android:id="@+id/tv_city_id"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_city"
                android:layout_marginTop="@dimen/query_weather_margin"
                android:textStyle="bold"
                android:text="@string/city_id" />

            <TextView
                android:id="@+id/tv_city_id_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toRightOf="@id/tv_city_id"
                android:layout_alignBottom="@id/tv_city_id"
                android:text="@{viewModel.cityId}"
                tools:text="101210101" />

            <TextView
                android:id="@+id/tv_temp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_city_id"
                android:layout_marginTop="@dimen/query_weather_margin"
                android:textStyle="bold"
                android:text="@string/temperature" />

            <TextView
                android:id="@+id/tv_temp1_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toRightOf="@id/tv_temp"
                android:layout_alignBottom="@id/tv_temp"
                android:text="@{viewModel.temp1}"
                tools:text="5℃" />

            <TextView
                android:id="@+id/tv_tilde"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toRightOf="@id/tv_temp1_value"
                android:layout_alignBottom="@id/tv_temp"
                android:text="@string/tilde" />

            <TextView
                android:id="@+id/tv_temp2_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toRightOf="@id/tv_tilde"
                android:layout_alignBottom="@id/tv_temp"
                android:text="@{viewModel.temp2}"
                tools:text="10℃" />

            <TextView
                android:id="@+id/tv_weather"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_temp"
                android:layout_marginTop="@dimen/query_weather_margin"
                android:textStyle="bold"
                android:text="@string/weather" />

            <TextView
                android:id="@+id/tv_weather_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toRightOf="@id/tv_weather"
                android:layout_alignBottom="@id/tv_weather"
                android:text="@{viewModel.weather}"
                tools:text="Sunny" />

            <TextView
                android:id="@+id/tv_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_weather"
                android:layout_marginTop="@dimen/query_weather_margin"
                android:textStyle="bold"
                android:text="@string/release_time" />

            <TextView
                android:id="@+id/tv_time_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toRightOf="@id/tv_time"
                android:layout_alignBottom="@id/tv_time"
                android:text="@{viewModel.time}"
                tools:text="10:00" />
        </RelativeLayout>

        <ProgressBar
            android:id="@+id/pb_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}" />

        <TextView
            android:id="@+id/tv_query_failure"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="@string/query_failure"
            android:visibility="@{viewModel.loadingFailure ? View.VISIBLE : View.GONE}" />
    </RelativeLayout>
</layout>

In Activity, load the layout file through the officially provided Data Binding library, create the ViewModel, and bind the View and ViewModel. The code of QueryWeatherActivity is as follows:

public class QueryWeatherActivity extends AppCompatActivity {

    // ViewModel
    private QueryWeatherViewModel mViewModel;

    // DataBinding
    private ActivityQueryWeatherBinding mDataBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_query_weather);
        // Create ViewModel
        mViewModel = new QueryWeatherViewModel();
        // Bind View and ViewModel
        mDataBinding.setViewModel(mViewModel);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Cancel request
        mViewModel.cancelRequest();
    }
}

summary

MVVM mode has three layers: the Model layer is mainly responsible for providing data, the View layer is mainly responsible for displaying the interface, and the ViewModel layer is mainly responsible for processing business logic. Each layer has a single and different responsibility, but they have a clear structure and are very convenient to apply

Android zero Foundation Series tutorials: Android Foundation Course

This article is transferred from Android | MVVM mode of Android Application Architecture_ cyc's column - CSDN blog_ android mvvm If there is infringement, please contact to delete.

Keywords: Android

Added by rslnerd on Thu, 09 Dec 2021 12:07:35 +0200