Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android MVVM

Android MVVM

編輯:關於Android編程

1、Android DataBinding:再見MVP,你好MVVM

當我們談到android應用程序的架構模式時,MVP一直是占主流的地位。就像 Ted Mosby, Nucleus and Mortar 框架都是用Presenters 架構,使我們的app結構變得非常清晰,還幫助我們解決一個湊名昭著的問題:android設備的旋轉和狀態持久。當然這不是直接的MVP概念,但事實上這一架構模型,使我們的代碼解耦。

數據綁定,是在Google I/O 2015提出並且Android M預覽版是的支持庫。聲稱改變一切。根據Wikipedia 是介紹的MVP,Presenter 有以下作用:

Presenter 根據模型和視圖。它從存儲庫(模型)中檢索數據,並將其格式化為在視圖中顯示的數據。

問題是,數據綁定框架將代替Presenter 的主要職責(作用在模型和視圖),而其余的功能則是留給增強模型–ViewModel(檢索數據庫和並格式處理)。視圖模型是ViewModel是一個標准的java類,其唯一的責任是代表在一個View中的數據。它可以合並來自多個源(模型)的數據,並展示數據。我以前有一篇在ViewModel的短文章,它主要講述了Data Model 和Transport Model的不同。

構建我們最終的MVVM–模型視圖ViewModel,MVVM概念最初是由微軟在2005年(不要讓它嚇到你了)。下面我說明MVP到MVVM的變化,引用Hanne Dorfmann介紹他的 Ted Mosby framework中使用的圖片

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

所有的數據的綁定和更新都是通過數據綁定框架(Data Binding Framework)來完成的。Observablefield類允許視圖對模型的變化做出反應,而XML引用字段改變時也會改變ViewModel 。當作用在視圖上時,你也可以通過程序訂閱變化,比如一個CheckBox點擊時,讓TextView時禁用。如果有一個標准的java類能夠表示View的狀態,這樣的好處顯而易見。並且我們可以很容易的單元測試。

注意在MVP上圖有一個方法調用 Presenter.loadUsers()的方法。在MVVM模式中這所有的方法都定義在 ViewMode。從維基百科文章:

The view model is an abstraction of the view that exposes public properties and commands

在MVP模式很中,你的模型很可能是“簡單”的類,它只保存數據。不要害怕把業務邏輯放在你的模型或視圖模型中。這是面向對象編程的一個核心原則。回到 Presenter.loadUsers()這個方法,我們沒有在ViewModel中調用這個方法,視圖是通過綁定在xml文件中。如果我們不把數據綁定,我們仍然必須使用以前的android語法:onclick語法,或手動添加監聽代碼。

2、處理系統調用

仍然有一系列的工作在View中去做,比如初始化系統的回調,打開dialog或者涉及Context的對象。不要這些代碼放到ViewModel。如果你把像這種Context放入到ViewModel中,這是錯誤的。

我還沒有找到最好的解決辦法,但有一些可以嘗試。一種方式是在View的ViewModel保持一個presenter 一個接口的引用。這樣你就不會降低可測試性。但是沒有一個單獨的presenter 的類在我們程序中,我堅持認為,只要保持它的簡單接口的具體實現。另一種方式就像event bus或者Square Otto初始化方式 ShowToastMessage(“hello world”)(具體參見eventbus 或者otto的使用)。這將產生一個更大的分離View和ViewModel –但那是好事情嗎?

3、我們是否需要框架呢?

因此,Data Binding框架代替其他的框架嗎?只有部分。我希望看到的是這些框架進化為MVVM風格框架,使我們可以利用最好的數據綁定同時依賴第三方的框架到最小,並保持框架小而簡單。而Presenter 的時代就要結束了,這個Presenter僅僅做一些重要的工作比如生命周期管理和視圖狀態(ViewModel)持久性。不幸的是,這並沒有改變。

我最近了解到Android ViewModel框架,這實際上可能是一個很好的適合MVVM和Android的數據綁定。

4、總結

當我第一聽說Android M 所有工作是為了改進sdk和關注開發者們。我是多麼的興奮。當接受到數據綁定的時候,我是如此的吃驚,我已經和其他平台上的數據綁定工作多年:WinForms,WPF,Silverlight和Windows手機。我知道這將有助於我們寫更清潔的架構和更少的耦合代碼。這個框架與我們一起工作,而不是反對我們,現在,它終於要來了。

但是,它仍然有一些缺點。事實上你定義的xml文件,它沒有編譯,也就是沒辦法進行單元測試。這樣你會了現許多錯誤在運行期間,而不是在編譯期間。不幸的是,如果你經常忘記綁定view呢?
這就是為什麼我希望谷歌能夠讓Android Studio支持數據綁定到最大的語法和編譯期間檢查的XML,自動完成。支持重命名在xml字段中。從我用的Android Studdio1.3測試版-中思考這樣的問題。有些東西是支持的,很也有許多不支持,但是我看到了很大的希望。

5、Code example

下面分別使用mvp和mvvm的代碼示例

這裡寫圖片描述

MVP – VIEW – XML



    

    

    

        

        

    

    

        

        
    

    

        

        

    

    

        

        
    

    

MVP – VIEW – JAVA

package com.nilzor.presenterexample;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import com.hannesdorfmann.mosby.mvp.MvpFragment;
import com.hannesdorfmann.mosby.mvp.MvpView;
import butterknife.InjectView;
import butterknife.OnClick;

public class MainActivityFragment extends MvpFragment implements MvpView {
    @InjectView(R.id.username)
    TextView mUsername;

    @InjectView(R.id.password)
    TextView mPassword;

    @InjectView(R.id.newUserRb)
    RadioButton mNewUserRb;

    @InjectView(R.id.returningUserRb)
    RadioButton mReturningUserRb;

    @InjectView(R.id.loginOrCreateButton)
    Button mLoginOrCreateButton;

    @InjectView(R.id.email_block)
    ViewGroup mEmailBlock;

    @InjectView(R.id.loggedInUserCount)
    TextView mLoggedInUserCount;

    public MainActivityFragment() {
    }

    @Override
    public MainPresenter createPresenter() {
        return new MainPresenter();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        attachEventListeners();
    }

    private void attachEventListeners() {
        mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
        mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
    }

    /** Prepares the initial state of the view upon startup */
    public void setInitialState() {
        mReturningUserRb.setChecked(true);
        updateDependentViews();
    }

    /** Shows/hides email field and sets correct text of login button depending on state of radio buttons */
    public void updateDependentViews() {
        if (mReturningUserRb.isChecked()) {
            mEmailBlock.setVisibility(View.GONE);
            mLoginOrCreateButton.setText(R.string.log_in);
        }
        else {
            mEmailBlock.setVisibility(View.VISIBLE);
            mLoginOrCreateButton.setText(R.string.create_user);
        }
    }

    public void setNumberOfLoggedIn(int numberOfLoggedIn) {
        mLoggedInUserCount.setText(""  + numberOfLoggedIn);
    }

    @OnClick(R.id.loginOrCreateButton)
    public void loginOrCreate() {
        if (mNewUserRb.isChecked()) {
            Toast.makeText(getActivity(), "Please enter a valid email address", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(getActivity(), "Invalid username or password", Toast.LENGTH_SHORT).show();
        }
    }
}

MVP – PRESENTER

package com.nilzor.presenterexample;

import android.os.Handler;
import android.os.Message;
import com.hannesdorfmann.mosby.mvp.MvpPresenter;

public class MainPresenter implements MvpPresenter {
    MainModel mModel;
    private MainActivityFragment mView;

    public MainPresenter() {
        mModel = new MainModel();
    }

    @Override
    public void attachView(MainActivityFragment view) {
        mView = view;
        view.setInitialState();
        updateViewFromModel();
        ensureModelDataIsLoaded();
    }

    @Override
    public void detachView(boolean retainInstance) {
        mView = null;
    }

    private void ensureModelDataIsLoaded() {
        if (!mModel.isLoaded()) {
            mModel.loadAsync(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    updateViewFromModel();
                    return true;
                }
            });
        }
    }

    /** Notifies the views of the current value of "numberOfUsersLoggedIn", if any */
    private void updateViewFromModel() {
        if (mView != null && mModel.isLoaded()) {
            mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);
        }
    }
}

MVP – MODEL

package com.nilzor.presenterexample;

import android.os.AsyncTask;
import android.os.Handler;
import java.util.Random;

public class MainModel {
    public Integer numberOfUsersLoggedIn;
    private boolean mIsLoaded;
    public boolean isLoaded() {
        return mIsLoaded;
    }

    public void loadAsync(final Handler.Callback onDoneCallback) {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn = new Random().nextInt(1000);
                mIsLoaded = true;
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                onDoneCallback.handleMessage(null);
            }
        }.execute((Void) null);
    }
}

用mvvm代碼如下
MVVM – VIEW – XML


    
        
    
    

        

        

        

            

            

        

        

            

            
        

        

            

            

        

        

            

            
        

        

MVVM – VIEW – JAVA

package com.nilzor.presenterexample;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Toast;

import com.nilzor.presenterexample.databinding.FragmentMainBinding;

public class MainActivityFragment extends Fragment {
    private FragmentMainBinding mBinding;
    private MainModel mViewModel;

    public MainActivityFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        mBinding = FragmentMainBinding.bind(view);
        mViewModel = new MainModel(this, getResources());
        mBinding.setData(mViewModel);
        attachButtonListener();
        return view;
    }

    private void attachButtonListener() {
        mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mViewModel.logInClicked();
            }
        });
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        ensureModelDataIsLodaded();
    }

    private void ensureModelDataIsLodaded() {
        if (!mViewModel.isLoaded()) {
            mViewModel.loadAsync();
        }
    }

    public void showShortToast(String text) {
        Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
    }
}

MVVM – VIEWMODEL

package com.nilzor.presenterexample;

import android.content.res.Resources;
import android.databinding.ObservableField;
import android.os.AsyncTask;
import android.view.View;

import java.util.Random;

public class MainModel {
    public ObservableField numberOfUsersLoggedIn = new ObservableField();
    public ObservableField isExistingUserChecked = new ObservableField();
    public ObservableField emailBlockVisibility = new ObservableField();
    public ObservableField loginOrCreateButtonText = new ObservableField();
    private boolean mIsLoaded;
    private MainActivityFragment mView;
    private Resources mResources;

    public MainModel(MainActivityFragment view, Resources resources) {
        mView = view;
        mResources = resources; // You might want to abstract this for testability
        setInitialState();
        updateDependentViews();
        hookUpDependencies();
    }
    public boolean isLoaded() {
        return mIsLoaded;
    }

    private void setInitialState() {
        numberOfUsersLoggedIn.set("...");
        isExistingUserChecked.set(true);
    }

    private void hookUpDependencies() {
        isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
                updateDependentViews();
            }
        });
    }

    public void updateDependentViews() {
        if (isExistingUserChecked.get()) {
            emailBlockVisibility.set(View.GONE);
            loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
        }
        else {
            emailBlockVisibility.set(View.VISIBLE);
            loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
        }
    }

    public void loadAsync() {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
                mIsLoaded = true;
                return null;
            }
        }.execute((Void) null);
    }

    public void logInClicked() {
        // Illustrating the need for calling back to the view though testable interfaces.
        if (isExistingUserChecked.get()) {
            mView.showShortToast("Invalid username or password");
        }
        else {
            mView.showShortToast("Please enter a valid email address");
        }
    }
}

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved