ViewModel讓開發(fā)更放心
概況
做android開發(fā),有時(shí)我們會(huì)采用MVP模式,把業(yè)務(wù)邏輯從Activity中分解出來。但是Presenter的生命周期不容易管理。對(duì)于一個(gè)復(fù)雜的Activity和Fragment來說,可能綁定了多個(gè)Presenter、Manager或者View,代碼寫起來就會(huì)很復(fù)雜。尤其是當(dāng)這些被其他人復(fù)用的時(shí)候,很難讓別人也注意到這一點(diǎn),很容易發(fā)生內(nèi)存溢出問題。
LifeCycle
Google推出的LifeCycle就可以解決輔助類的生命周期問題。
在API 26.1.0之后,android.support.v4.app中的FragmentActivity和Fragment都集成了LifeCycle的相關(guān)功能。
LiveData 和 ViewModel 生命周期組件是 Android 官方架構(gòu)組件中的核心組件, 它可以使各種實(shí)例作為觀察者與 Activity 和 Fragment 等具有生命周期特性的組件綁定在一起。我們將需要綁定生命周期的實(shí)例注冊(cè)給該組件, 該組件就會(huì)在指定的某個(gè)生命周期方法執(zhí)行時(shí)通知這個(gè)實(shí)例。它們用到了觀察者模式。
LiveData
LiveData是一個(gè)可觀察的數(shù)據(jù)持有者類。與常見的觀察者不同,LiveData是有生命周期感知的。這種感知確保LiveData只更新處于生命周期狀態(tài)內(nèi)的應(yīng)用程序組件(這一點(diǎn)太重要了)。
ViewModel
ViewModel 有兩個(gè)功能, 第一個(gè)功能可以使 ViewModel 以及 ViewModel 中的數(shù)據(jù)在屏幕旋轉(zhuǎn)或配置更改引起的 Activity 重建時(shí)存活下來, 重建后數(shù)據(jù)可繼續(xù)使用; 第二個(gè)功能可以幫助開發(fā)者輕易實(shí)現(xiàn) Fragment 與 Fragment 之間, Activity 與 Fragment 之間的通訊以及共享數(shù)據(jù)。
使用方法
通過創(chuàng)建MyViewModel extends ViewModel
在MyViewModel內(nèi)部新建MutableLiveData
在Activity 或 Fragment中獲取獲取MyViewModel里的MutableLiveData并添加監(jiān)聽 viewModel.liveData.observer()
實(shí)例
我們以一個(gè)登錄頁LogActivity來舉例說明吧。一般我們把業(yè)務(wù)交給ViewModel來處理。View則根據(jù)數(shù)據(jù)的變化來做調(diào)整,這部分是寫在Fragment或Activity類里。
(1)分析登錄頁LoginActivity的需求
登錄數(shù)據(jù)有效性驗(yàn)證,如用戶名、密碼的有效性驗(yàn)證,可以用一個(gè)類來記錄這些登錄表單的狀態(tài):
class LoginFormState { @Nullable private Integer usernameError;// 用戶名錯(cuò)誤 @Nullable private Integer passwordError;// 密碼錯(cuò)誤 // 我們用這個(gè)字段來控制登錄按鈕的狀態(tài) private boolean isDataValid; LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) { this.usernameError = usernameError; this.passwordError = passwordError; this.isDataValid = false; } LoginFormState(boolean isDataValid) { this.usernameError = null; this.passwordError = null; this.isDataValid = isDataValid; } @Nullable Integer getUsernameError() { return usernameError; } @Nullable Integer getPasswordError() { return passwordError; } boolean isDataValid() { return isDataValid; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
當(dāng)數(shù)據(jù)有效性通過后,就開始登錄,登錄或成功或失敗,我們用一個(gè)類來記錄這些登錄的結(jié)果:
class LoginResult { @Nullable private LoggedInUserView success; // 成功則返回?cái)?shù)據(jù) @Nullable private Integer error; // 錯(cuò)誤則返回一個(gè)錯(cuò)誤碼 LoginResult(@Nullable Integer error) { this.error = error; } LoginResult(@Nullable LoggedInUserView success) { this.success = success; } @Nullable LoggedInUserView getSuccess() { return success; } @Nullable Integer getError() { return error; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
成功返回的數(shù)據(jù)的類:
class LoggedInUserView { private String displayName; //... other data fields that may be accessible to the UI LoggedInUserView(String displayName) { this.displayName = displayName; } String getDisplayName() { return displayName; } }
1
2
3
4
5
6
7
8
9
10
11
12
(2)以上這些需求,都將在LoginViewModel里幫我們完成這些數(shù)據(jù)校驗(yàn)和登錄等數(shù)據(jù)邏輯。
由于在LoginActivity里要通過以下這種方式來獲得LoginViewModel:
loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory()) .get(LoginViewModel.class);
1
2
因此我們要先建一個(gè)工廠類LoginViewModelFactory來創(chuàng)建我們的LoginViewModel:
public class LoginViewModelFactory implements ViewModelProvider.Factory { @NonNull @Override @SuppressWarnings("unchecked") public
1
2
3
4
5
6
7
8
9
10
11
12
13
從上面我們可以看到在創(chuàng)建LoginViewModel時(shí)傳了一個(gè)LoginRepository單例進(jìn)去。LoginRepository在實(shí)例化時(shí)傳了一個(gè)LoginDataSource實(shí)例進(jìn)去。
根據(jù)前面的分析我們可知LoginViewModel會(huì)提供登錄及表單數(shù)據(jù)的有效性驗(yàn)證,但是如果需要共享登錄的方法,甚至退出登錄的方法、登錄的狀態(tài)、登錄后用戶的信息我們?cè)撛趺崔k呢?很簡單只要將這些部分放在一個(gè)單例類里就好了。在我們這里就是LoginRepository這個(gè)類來充當(dāng)共享的單例類。它的代碼如下:
public class LoginRepository { private static volatile LoginRepository instance; private LoginDataSource dataSource; // If user credentials will be cached in local storage, it is recommended it be encrypted // @see https://developer.android.com/training/articles/keystore private LoggedInUser user = null; // private constructor : singleton access private LoginRepository(LoginDataSource dataSource) { this.dataSource = dataSource; } public static LoginRepository getInstance(LoginDataSource dataSource) { if (instance == null) { instance = new LoginRepository(dataSource); } return instance; } // 用戶的登錄狀態(tài),用戶已登錄則 返回true,否則返回false public boolean isLoggedIn() { return user != null; } // 退出時(shí),清空LoginRepository單例類里所有的數(shù)據(jù) public void logout() { user = null; dataSource.logout(); } // 登錄成功后,我們就將用戶的信息保存在LoginRepository單例類里 private void setLoggedInUser(LoggedInUser user) { this.user = user; // If user credentials will be cached in local storage, it is recommended it be encrypted // @see https://developer.android.com/training/articles/keystore } // 共享的登錄方法 public Result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
用戶數(shù)據(jù)實(shí)體類:
public class LoggedInUser { private String userId; private String displayName; public LoggedInUser(String userId, String displayName) { this.userId = userId; this.displayName = displayName; } public String getUserId() { return userId; } public String getDisplayName() { return displayName; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
登錄結(jié)果的實(shí)體類:
public class Result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
負(fù)責(zé)處理用戶登錄和驗(yàn)證,并獲取用戶數(shù)據(jù)的數(shù)據(jù)源:
public class LoginDataSource { public Result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
LoginViewModel登場:
public class LoginViewModel extends ViewModel { // 持有一個(gè)可觀察的數(shù)據(jù)LoginFormState的LiveData類 private MutableLiveData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
前面都是在講LoginViewModel,最后我們講一講在LoginActivity里如何使用它:
package com.tisson.kmc.ui.login; import android.app.Activity; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.tisson.kmc.MainActivity; import com.tisson.kmc.R; import com.tisson.kmc.ui.login.LoginViewModel; import com.tisson.kmc.ui.login.LoginViewModelFactory; public class LoginActivity extends AppCompatActivity { private LoginViewModel loginViewModel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); // 獲取ViewModel loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory()) .get(LoginViewModel.class); final EditText usernameEditText = findViewById(R.id.username); final EditText passwordEditText = findViewById(R.id.password); final Button loginButton = findViewById(R.id.login); final ProgressBar loadingProgressBar = findViewById(R.id.loading); // 觀察表單數(shù)據(jù)的變化 loginViewModel.getLoginFormState().observe(this, new Observer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
Demo已在Github上了,歡迎下載學(xué)習(xí)。
Android
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。