The current Android development of the main push MVVM, Google in its official documents mentioned the following architecture, the use of the schemeViewModelView and Model can be easily decouple, convenient code maintenance, make the overall structure more clear.

MVVM

To review the MVVM architecture, the MVVM architecture has the advantage of separating the entire view, business logic, and data:

  1. Low coupling. Views can be changed and modified independently of the Model. A ViewModel can be bound to different views. When the View changes, the Model can not change, and when the Model changes, the View can not change.
  2. Reusability. You can put some view logic in a ViewModel and have many views reuse that view logic.
  3. Independent development. Developers can focus on business logic and data development (ViewModel), and designers can focus on page design.
  4. Can be tested. Interfaces are harder to test, and tests can be written against the ViewModel.

From Baidu Baike

ViewModel

For MVVM architecture, the View layer is very well understood, which is the Activity/Fragment in Android, and the Model layer is also very clear, which is used for data access, processing, and persistence operations. A Repository layer is extracted from the above structure to manage the data source.

You may be unsure of the definition of a ViewModel when you first encounter it. For example, the above architecture is based on the Jetpack suite, but for development, there is no need to worry about Jetpack, such as the ViewModel mentioned above. In fact, if the project does not use Jetpack, we can also use this architecture solution to write code. You can think of the ViewModel as an intermediate layer, a business processing layer. A Java class that shares some of the Activity/Fragment work and interacts with the data layer to retrieve the data and pass it to the View layer.

With the above architecture, we can organize the code very clearly when developing a feature. However, this approach can only ensure that the code related to a certain business dimension, a certain function point is clear and easy to maintain. For the whole project, how do we structure the whole project?

Project level

For a small project, since the code quantity is little, business is often not complex, not too much to consider the overall organization of the code, the structure of the management will not bring to the maintenance of the late great burden, after all, less code, maintenance costs are relatively low, but for a large project, chaotic code organization, it is a disaster for later maintenance personnel. The solution introduced here is to refer to this article. I made some changes and introduced them into the development of an APP in the company. Everyone gave good feedback and the development experience was greatly improved.

Let’s take a look at the solution mentioned in this article. The author uploaded the sample code to Github, which is located here. Its directory structure is as follows:

The author separates functionality from the code for the project’s infrastructure. The whole project is divided into core and features two packages. The basic code of the whole project is placed below core. The business is grouped under the Features directory, which organizes the code by the dimension of functional modules. The code for a feature (such as login) is always under featues.login. The benefits:

  1. The structure of the code is clearer and easier to maintain
  2. Later, if the business is up and you want to do componentization, because the code is in the same directory, it is more convenient to extract out.

elaboration

Going back to the Architecture diagram recommended by Google, we integrated the architecture recommended by Google into the project, so that the structure of the project looks like this:

com.xxxx.app

  • coreShared or common configuration for all modules throughout the app
    • dataGlobal data access, such as user information, APP configuration, etc
      • model
        • AppConfigInfo
        • UserInfo
      • datasource
        • impl
          • AppConfigRemoteDataSource
          • AppConfigLocalDataSource
          • UserRemoteDataSource
          • UserLocalDataSource
        • IUserRemoteDataSource
        • IUserLocalDataSource
        • IAppConfigRemoteDataSource
        • IAppConfigLocalDataSource
      • AppConfigRepository
      • UserRepository
    • util
    • view
    • base
      • BaseActivity
      • BaseFragment
      • BaseViewModel
    • hybrid
  • featuresHere are the groups by business
    • login
      • ui
        • xxxActivity
        • xxxViewModelFactory
        • xxxViewModelCallable directlycore/dataIn theUserRepository
    • product
      • dataConsidering that the data layer in the same module can be shared, it is placed in the outer layer
        • model
          • ProductListInfo
          • ProductDetailInfo
        • datasource
          • IProductLocalDataSource
          • IProductRemoteDataSource
          • impl
            • ProductLocalDataSourceImpl
            • ProductRemoteDataSourceImpl
        • ProductRepository
      • ui
        • detail
          • viewPlace the custom View
            • ProductLabelView
          • ProductDetailActivity
          • ProductDetailViewModel
        • list
          • rendermodel
          • viewPlace the custom View
          • .
          • ProductListViewModel(andProductDetailViewModelAll callsProductRepositoryTo get the data)
          • .
        • xxxViewModelFactoryIf the business is simple,ViewModelFactoryI can write it here, and I can share the detail module with the list
    • personal
      • data
      • ui
        • xxxViewModelFactory
        • xxxViewModelIf you need to obtain user information, callcore/dataIn theUserRepository

The above structure is described below

The core directory

For the whole project, some basic code. The operation of system configuration, user information and other data is all in this.

  • dataDirectory: DAO-related operations,modelBelow the package are poJOs, where data is retrieved or persisted entirely byxxxRepositoryTo calldatasourcePackage below the data source to implement
  • utilDirectory: Places some common utility classes
  • viewDirectory: Places common custom views. These custom views are separated from specific business connections and can be used in various business modules
  • baseDirectories: Used to place basic components such asBaseActivity.BaseFragment.BaseViewModel

features

Packages are divided according to service modules and organized in this directory. Here are some instructions for organizing the code

The login module

In the above directory, because involves the DAO operations related to the user login are already on the core directory, assuming that can meet the requirements, then the login module only UI related documents and ViewModel, xxxViewModelFactory

The product module

This module is used to simulate multiple page scenarios under a module. Take the product list page and the details page for example. Because DAO operations can have overlapping scenarios, their data operations are written together. The UI level is divided into list and detail packages according to functions. The viewModels for the list and detail pages can be created using the same ViewModelFactory.

  • rendermodelPackage: There is one in this directory that needs to be specified separately when we get data from the serverProductInfoAfter that, the data is displayed on the page, and some of the information we display on the page is likely to be based onProductInfoData processing. To do that, let’s define aProductInfoRenderModel.javaUsed to host data that only needs to be displayed on the page.ProductInfoRenderModel.javaIs onrendermodelUnder the bag.

The personal module

The Personal module also involves user-related information, which is why the DAO operation of user-related information was originally designed to be placed in the core directory. The xxViewModel of the personal module can be used to query user information by calling the UserRepository directly under core

At this point, the overall structure of the whole project will be sorted out. This scheme is used to divide the code into functional modules, which is convenient for later maintenance. Even if the technical scheme is reformed in a subsequent module, the granularity of its influence can be minimized. Of course, this is mainly to explain the main structure of the project, in the actual project, in addition to these, we will also have adapter, a variety of tools written by ourselves, and so on, this will be subcontracted according to the actual situation. Let’s take a look at the relevant technology stacks involved

Technology stack

The main technology stacks used in this project architecture include ViewModel, ViewModelFactory, LiveData and ROOM in Jetpack. The following is a brief introduction of these technologies and their integration. Of course, for web requests we can use Okhttp, Retrofit, which we won’t cover here.

ViewModel

The Jetpack component provides viewModels that can easily bind data, objects and component life cycles to facilitate data sharing between components, such as the case of multiple fragments in an activity. At the same time, it can effectively decouple from the architecture level, significantly reducing the number of interfaces/methods compared to the MVP architecture pattern. For example, when a user invokes a login interface, the presenter.login method is invoked, and when login succeeds, the view.loginSuccess method is invoked. After using ViewMode, the user calls viewModel.login method when logging in. After successful login, update LiveData in viewModel, and then observe the corresponding behavior of LiveData at the invocation.

ViewModelProvider.Factory

Used to create the ViewModel, ViewModel can not create your own, must use ViewModelProvider. Factory to create. The ViewModel is typically assigned a data warehouse at creation time, as follows:

public class LoginViewModelFactory implements ViewModelProvider.Factory {
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (modelClass.isAssignableFrom(LoginViewModel.class)) {
            return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));
        } else {
            throw new IllegalArgumentException("Unknown ViewModel class"); }}}Copy the code

LiveData

The page needs to be notified when the data changes. You can usually do this with an interface approach, but if you have a lot of data to observe, you need to define a lot of interfaces, and the code can be very redundant. To this end, Google provides the LiveData component, which is an observable data container class that wraps the data so that the data becomes the observed, and the observer can be notified when the data changes.

The ViewModel is used to store data, and LiveData is used to notify the page when the ViewModel changes. Therefore, LiveData is usually used in the ViewModel to wrap the data in the ViewModel that needs to be viewed by the outside world.

Let’s see how these three work together with a concrete example (login)

The sample

The UI level (LoginActivity)

 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); binding = ActivityLoginBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); . viewModel =new ViewModelProvider(this.new LoginViewModelFactory()).get(LoginViewModel.class);
    registerObserver();
    bindClickEvent();
    
}

private void registerObserver(a) {
    viewModel.getLoginResult().observe(this.new Observer<LoginResult>() {
        @Override
        public void onChanged(LoginResult loginResult) {
            if (loginResult.success) {
               // Login succeeded}}});// An exception occurs
    viewModel.exceptionLiveData.observe(this.new Observer<AppException>() {
        @Override
        public void onChanged(AppException e) {
            Toast.makeText(LoginActivity.this, e.getBizMsg(), Toast.LENGTH_SHORT).show(); }}); }private void bindClickEvent(a) {
    binding.btnLogin.setOnClickListener(v-> {
        viewModel.login(phone, pwd, verifyCode, verifyKey);
    });
}
Copy the code
  • usingViewBindingBind resources
  • The bindingViewModel
  • ViewModelListen for changes in data
  • usingViewModelTo access the data

ViewModel

public class LoginViewModel extends BaseViewModel {...private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();

    private LoginRepository loginRepository;

    LoginViewModel(LoginRepository loginRepository) {
        this.loginRepository = loginRepository;
    }

    public LiveData<LoginResult> getLoginResult(a) {
        return loginResult;
    }

    public void login(String username, String password, String verifyCode, String verifyKey) {
        loginRepository.login(username, password, verifyCode, verifyKey, new Gson2ModelCallback<NetResult<LoginNetResponse>>() {

            @Override
            public void onSuccess(NetResult<LoginNetResponse> result) {
                / / success
                if ("SUCCESS".equals(result.getCode())) {                  
                    loginResult.postValue(newLoginResult(result.getData())); }}@Override
            public void onFail(Throwable e) { handleException(e); }}); }}Copy the code

Its function is to call the repository-associated business methods to fetch data, which is then passed to the ViewModel via the postValue method

Repository

The Repository layer calls a specific data source to load the data, but we won’t go into details here. Repository is injected when the ViewModel is created.

So far, the main structure of the scheme is described completely. As for the details of the scheme, according to the actual situation of the specific project to arrange. For example, whether or not to use Rxjava, whether or not to introduce Retrofit, the lead developer of the different modules can organize the code in a modular granularity, so that you don’t have to worry about the random code in someone else’s module to disgust you, and you can enjoy the maximum technical freedom in the module you are responsible for. Even if you don’t want to use MVVM architecture later on in the modules you’re responsible for, your design ideas will only affect the modules you’re responsible for, and the rest of the project will still have a clean and consistent structure.

reference

  • Fernandocejas.com/blog/engine…
  • Baike.baidu.com/item/MVVM/9…
  • Android Jetpack App Guide