preface

In the early days of Android application development, activities/fragments took on too much responsibility, not only for the display of the application interface, but also for handling the business logic. As a result, activities/fragments can easily become bloated and complex, making the application difficult to test, maintain, and extend. With the continuous development and maturity of Android application development technology, the design of Android application architecture has been paid more and more attention by developers. At present, Android application architecture mainly has MVC, MVP and MVVM mode, this article will introduce the MVVM mode.

The relevant knowledge

MVVM mode of Android application architecture

Learning program

MVVM mode open source learning project – play Android client

The MVP pattern

The MVVM mode is a step up from MVP mode, so let’s take a look at MVP mode first.

The structure of the MODEL-View-Presenter (MVP) mode is as follows:

MVP mode divides applications into three layers: Model layer for data, View layer for interface display, and Presenter layer for business logic.

In MVP mode, the Model layer and the View layer do not communicate directly, and the Presenter layer acts as a middleman, enabling indirect communication between the Model layer and the View layer. The View layer and Presenter layer hold references to each other to achieve communication between the View layer and Presenter layer.

The main advantage of the MVP mode is that it separates the Model layer from the View layer, separating View operations from business logic, and reducing coupling.

The MVVM pattern

The structure of the MVVM (Model-View-ViewModel) mode is shown in the figure below:

The MVVM mode, like the MVP mode, divides the application into three layers, and each layer has similar responsibilities:

  • The Model layer is mainly responsible for providing data. The Model layer provides the data structure for the business logic (for example, entity classes), the fetching of the data (for example, from a local database or a remote network), and the storage of the data.
  • View layer, mainly responsible for interface display. The View layer does not involve any business logic processing, it holds references to the ViewModel layer and notifies the ViewModel layer when business logic processing is needed.
  • ViewModel layer, mainly responsible for business logic processing. The ViewModel layer does not involve any view operations. Data in the View layer and ViewModel layer can be bound by the official Data Binding library. Changes in the Data in the ViewModel layer are automatically notified to the View layer for update, so the ViewModel layer does not need to hold references to the View layer. The ViewModel layer can be viewed as a 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 references to the View layer. This further reduces coupling, and changes to the View layer code do not affect the ViewModel layer.

MVVM mode has the following advantages over MVP mode:

  • This further reduces coupling. The ViewModel layer does not hold a reference to the View layer. When the View layer changes, the ViewModel layer does not need to change as long as the data bound to the View layer remains unchanged. In MVP mode, when the View layer changes, the interface that operates the View changes accordingly, and the Presenter layer changes.
  • No more boilerplate code to write. The official Data Binding library enables Binding between the UI and the Data without having to write a lot of findViewById() and code to manipulate views. In general, the Activity/Fragment code can be fairly clean.

Here is a simple example to put the MVVM pattern into practice. The full project code is available on GitHub:

Github.com/chongyucaiy…

Example to achieve the main function is: click the button network to query the weather, the weather information is displayed on the interface after the successful query. The main interface is as shown in the picture below:

The code organization structure for MVVM mode is suggested as followsBusiness functionsThe specific operations are as follows: each service function is stored in an independent package, and each service function package is subdivided into 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. This example is relatively simple, with only one Weather business function module. The final code organization structure is shown in the figure below:

Write the Model

Query hangzhou weather URL as:

www.weather.com.cn/data/cityin…

Accessing the URL returns a JSON string like this:

1 {" weatherinfo ": {" city" : "hangzhou", "cityid" : "101210101", "temp1" : "5 ℃", "temp2" : "20 ℃", "weather" : "clear to overcast," "img1" : "n0. GIF", "img2" : "d1 .gif","ptime":"18:00"}}Copy the code

Using this JSON string, you can write the corresponding entity class. The code for the WeatherData class looks like this:

public class WeatherData { private WeatherInfo weatherinfo; public WeatherInfo getWeatherinfo() { return weatherinfo; } public void setWeatherinfo(WeatherInfo weatherinfo) { this.weatherinfo = weatherinfo; 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; }}Copy the code

Write the ViewModel

ViewModel does not involve any view operations, only business logic processing. With the officially provided Data Binding library, the UI is automatically updated when the Data in the ViewModel changes. The code for QueryWeatherViewModel looks like this:

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(); }}}Copy the code

Write the View

View does not involve any business logic processing and only displays the interface. In the XML layout file, the official Data Binding library is used to bind the UI to the Data in the ViewModel. When the Data in the ViewModel changes, the UI is automatically updated. The code for the XML layout file looks like this:

<? The 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=" /> <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 ° C "/> <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 ° C "/> <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=" fine "/> <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>Copy the code

In the Activity, load the layout file, create the ViewModel, and bind the View and ViewModel using the official Data Binding library. The code for QueryWeatherActivity looks like this:

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); MViewModel = new QueryWeatherViewModel(); . / / bind View and ViewModel mDataBinding setViewModel (mViewModel); } @Override protected void onDestroy() { super.onDestroy(); Mviewmodel.cancelrequest (); }}Copy the code

conclusion

The MVVM mode divides the application into three layers: Model layer is mainly responsible for data provision, View layer is mainly responsible for interface display, and ViewModel layer is mainly responsible for business logic processing. Each layer has a single responsibility, a clear structure, and the application can be easily tested, maintained, and extended.