iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 3
0
Software Development

Android Architecture系列 第 3

Architecture Components - ViewModel

  • 分享至 

  • xImage
  •  

昨天我們建好的基本MVVM架構,其中帶有一個lifecycle的問題,當螢幕旋轉時:

旋轉之後New Data就不見了,Why?當旋轉螢幕之類的configuration change發生時,Activity會被重新建立,生命週期會到onDestroy再重新onCreate

         Activity-lifecycle

MainActivity在create時會new一個新的MainViewModel,而非取得原本已持有資料的MainViewModel,因此,這個新的MainViewModel中是沒有資料的,畫面就變成空白。

public class MainActivity extends AppCompatActivity {

    ....

    private MainViewModel viewModel = new MainViewModel(); // <----Here!

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ....
    }
}

那怎麼讓Activity在經過configuration change之後還取得同一個ViewModel?

Lifecycle-aware ViewModel

Architecture Components中的ViewModel library具有lifecycle-aware的特性,不會因為Activity被重建而清除,讓Activity每次重建都能持有同一個instance。

使用Architecture Components首先要在project gradle加入google()

allprojects {
    repositories {
        google()
        jcenter()
    }
}

加入dependencies,Android Studio 3.x使用implementation,若是用Android Studio 2.x就改成compile

dependencies {
    implementation "android.arch.lifecycle:extensions:1.0.0"
    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
}

接著就可以改程式了,原本的MainViewModel讓它extend ViewModel,其他都不用改

import android.arch.lifecycle.ViewModel;
...

public class MainViewModel extends ViewModel {

    ...
}

MainActivity改成這樣:

public class MainActivity extends AppCompatActivity {

    private MainActivityBinding binding;

    private MainViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
        viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        binding.setViewModel(viewModel);
    }
}

不再用new而是改成透過ViewModelProviders協助我們取得ViewModel,其中of()的參數代表著ViewModel的生命範圍(scope),在MainActivity中用of(this)表示ViewModel的生命週期會持續到MainActivity不再活動(destroy且沒有re-create)為止,只要MainActivity還在活動中,ViewModel就不會被清除,每次create都可以取得同一個ViewModel。

下圖表示畫面旋轉導致Activity重新建立時ViewModel的生命週期

        The lifecycle of a ViewModel

圖中Activity經過rotated重新建立,ViewModel則不受影響,一直到最後Activity finished時才結束其生命週期。

執行結果:

Context

使用ViewModel有一點需特別注意的是不要儲存Activity/Fragment的內容或context在ViewModel中,因為configuration changes時當前的Activity及其內容會被destroy,就會變成存放被destroy的內容在ViewModel中而產生memory leak。

若需要在ViewModel中使用Context的話可以改成使用AndroidViewModel,其constructor帶有application供我們取得context

public class MainViewModel extends AndroidViewModel {

    ...

    private final Context mContext; // To avoid leaks, this must be an Application Context.

    public MainViewModel(@NonNull Application application) {
        super(application);
        mContext = application.getApplicationContext(); // Force use of Application Context.
    }
    
    ...
}

ViewModelFactory

ViewModel需透過ViewModelProviders來取得,所以不能在View中直接用constructor parameter來傳遞,若需要傳遞參數的話須建立Factory。

例如我們要將DataModel以參數的方式傳給MainViewModel的話,修改constructor:

public class MainViewModel extends ViewModel {

    ...
    
    private DataModel dataModel;

    public MainViewModel(DataModel dataModel) {
        super();
        this.dataModel = dataModel;
    }
    ...
}

接著建立一個GithubViewModelFactory

public class GithubViewModelFactory implements ViewModelProvider.Factory {

    private DataModel dataModel;

    public GithubViewModelFactory(DataModel dataModel) {
        this.dataModel = dataModel;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (modelClass.isAssignableFrom(MainViewModel.class)) {
            return (T) new MainViewModel(dataModel);
        }
        throw new IllegalArgumentException("Unknown ViewModel class");
    }
}

在Factory的constructor中接收參數DataModel,並於create處依照class回傳帶有參數的ViewModel。

最後於MainActivity中使用factory

public class MainActivity extends AppCompatActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.main_activity);

        GithubViewModelFactory factory = new GithubViewModelFactory(new DataModel());
        viewModel = ViewModelProviders.of(this, factory).get(MainViewModel.class);
        ...
    }
}

將DataModel作為參數來實例化GithubViewModelFactory,再將of(this)改成of(this, factory)就完成了。

以上只是純粹舉例用Factory來傳遞參數的方法,實務上不會這樣傳遞Model,因為這樣就違反MVVM原則讓ViewModel接觸了,Model正確的建立方式過幾天我們會再提到。


GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day03-viewmodel

Reference:
Architecture Compoents ViewModel
Modern Android development with Kotlin (Part 2)


上一篇
MVVM架構
下一篇
Architecture Components - LiveData
系列文
Android Architecture30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
starengram
iT邦新手 3 級 ‧ 2018-11-16 16:49:57

dependencies應該要補加喔
implementation "android.arch.lifecycle:extensions:1.1.0"

我要留言

立即登入留言