ViewModel開發(fā)更放心

      網(wǎng)友投稿 648 2022-05-29

      概況

      做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 創(chuàng)建可監(jiān)聽數(shù)據(jù)

      在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 T create(@NonNull Class modelClass) { if (modelClass.isAssignableFrom(LoginViewModel.class)) { return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource())); } else { throw new IllegalArgumentException("Unknown ViewModel class"); } } }

      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 login(String username, String password) { // handle login Result result = dataSource.login(username, password); if (result instanceof Result.Success) {// 如果登錄成功,則將用戶信息記錄在單例類里,以備共享 setLoggedInUser(((Result.Success) result).getData()); } return 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 { // hide the private constructor to limit subclass types (Success, Error) private Result() { } @Override public String toString() { if (this instanceof Result.Success) { Result.Success success = (Result.Success) this; return "Success[data=" + success.getData().toString() + "]"; } else if (this instanceof Result.Error) { Result.Error error = (Result.Error) this; return "Error[exception=" + error.getError().toString() + "]"; } return ""; } // Success sub-class public final static class Success extends Result { private T data; public Success(T data) { this.data = data; } public T getData() { return this.data; } } // Error sub-class public final static class Error extends Result { private Exception error; public Error(Exception error) { this.error = error; } public Exception getError() { return this.error; } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      ViewModel讓開發(fā)更放心

      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 login(String username, String password) { try { // TODO: handle loggedInUser authentication LoggedInUser fakeUser = new LoggedInUser( java.util.UUID.randomUUID().toString(), "Jane Doe"); return new Result.Success<>(fakeUser); } catch (Exception e) { return new Result.Error(new IOException("Error logging in", e)); } } public void logout() { // TODO: revoke authentication } }

      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 loginFormState = new MutableLiveData<>(); // 持有一個(gè)可觀察的數(shù)據(jù)LoginResult的LiveData類 private MutableLiveData loginResult = new MutableLiveData<>(); // 緩存類,所有需要共享的方法數(shù)據(jù)都在里面 private LoginRepository loginRepository; LoginViewModel(LoginRepository loginRepository) { this.loginRepository = loginRepository; } // 返回一個(gè)可觀察的數(shù)據(jù)持有者類LiveData,它持有LoginFormState數(shù)據(jù) LiveData getLoginFormState() { return loginFormState; } // 返回一個(gè)可觀察的數(shù)據(jù)持有者類LiveData,它持有LoginResult數(shù)據(jù) LiveData getLoginResult() { return loginResult; } // 處理用戶登錄的 public void login(String username, String password) { // 處理用戶登錄 Result result = loginRepository.login(username, password); if (result instanceof Result.Success) {// 成功登錄 LoggedInUser data = ((Result.Success) result).getData(); loginResult.setValue(new LoginResult(new LoggedInUserView(data.getDisplayName())));// 設(shè)置新數(shù)據(jù),并通知觀察者 } else { loginResult.setValue(new LoginResult(R.string.login_failed));// 設(shè)置新數(shù)據(jù),并通知觀察者 } } // 處理表單數(shù)據(jù)變化的 public void loginDataChanged(String username, String password) { if (!isUserNameValid(username)) {// 驗(yàn)證用戶名的有效性 loginFormState.setValue(new LoginFormState(R.string.invalid_username, null)); } else if (!isPasswordValid(password)) {// 驗(yàn)證密碼的有效性 loginFormState.setValue(new LoginFormState(null, R.string.invalid_password)); } else {// 用戶名和密碼都有效 loginFormState.setValue(new LoginFormState(true)); } } // A placeholder username validation check private boolean isUserNameValid(String username) { if (username == null) { return false; } if (username.contains("@")) { return Patterns.EMAIL_ADDRESS.matcher(username).matches(); } else { return !username.trim().isEmpty(); } } // A placeholder password validation check private boolean isPasswordValid(String password) { return password != null && password.trim().length() > 5; } }

      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() { @Override public void onChanged(@Nullable LoginFormState loginFormState) { if (loginFormState == null) { return; } loginButton.setEnabled(loginFormState.isDataValid());// 設(shè)置按鈕狀態(tài) if (loginFormState.getUsernameError() != null) {// 用戶名錯(cuò)誤 usernameEditText.setError(getString(loginFormState.getUsernameError())); } if (loginFormState.getPasswordError() != null) {// 密碼錯(cuò)誤 passwordEditText.setError(getString(loginFormState.getPasswordError())); } } }); // 觀察登錄結(jié)果的變化 loginViewModel.getLoginResult().observe(this, new Observer() { @Override public void onChanged(@Nullable LoginResult loginResult) { if (loginResult == null) { return; } loadingProgressBar.setVisibility(View.GONE);// 關(guān)閉loading動(dòng)畫 if (loginResult.getError() != null) {// 登錄失敗 showLoginFailed(loginResult.getError());// 顯示登錄失敗消息 } if (loginResult.getSuccess() != null) {// 登錄成功 updateUiWithUser(loginResult.getSuccess());// 顯示登錄成功消息 } setResult(Activity.RESULT_OK);// 對(duì)使用startActivityForResult有效 //Complete and destroy login activity once successful finish(); } }); // EditText輸入框的變化 TextWatcher afterTextChangedListener = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // ignore } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // ignore } @Override public void afterTextChanged(Editable s) { // 設(shè)置表單數(shù)據(jù)的變化,通知觀察者 loginViewModel.loginDataChanged(usernameEditText.getText().toString(), passwordEditText.getText().toString()); } }; usernameEditText.addTextChangedListener(afterTextChangedListener);// 注冊(cè)文本變化- passwordEditText.addTextChangedListener(afterTextChangedListener);// 注冊(cè)文本變化- passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {// 軟鍵盤Done事件 if (actionId == EditorInfo.IME_ACTION_DONE) { loginViewModel.login(usernameEditText.getText().toString(), passwordEditText.getText().toString()); } return false; } }); // 登錄按鈕事件 loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { loadingProgressBar.setVisibility(View.VISIBLE); loginViewModel.login(usernameEditText.getText().toString(), passwordEditText.getText().toString()); } }); } private void updateUiWithUser(LoggedInUserView model) { String welcome = getString(R.string.welcome) + model.getDisplayName(); // TODO : initiate successful logged in experience Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show(); Intent intent = new Intent(LoginActivity.this, MainActivity.class); startActivity(intent); } private void showLoginFailed(@StringRes Integer errorString) { Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show(); } }

      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)容。

      上一篇:華為宣布啟動(dòng)數(shù)據(jù)基礎(chǔ)設(shè)施戰(zhàn)略并開源數(shù)據(jù)虛擬化引擎HetuEngine
      下一篇:百分百發(fā)揮AI算力,華為發(fā)出最關(guān)鍵的一擊
      相關(guān)文章
      亚洲国产精品久久久久秋霞影院 | 日韩精品电影一区亚洲| 亚洲AV成人无码久久精品老人| 国产精品亚洲综合网站| 亚洲中文精品久久久久久不卡| 亚洲熟妇av一区| 亚洲国产一区国产亚洲| 亚洲v高清理论电影| 亚洲av永久无码精品秋霞电影影院| 亚洲欧洲自拍拍偷精品 美利坚 | 亚洲一区二区电影| 午夜影视日本亚洲欧洲精品一区| 久久伊人久久亚洲综合| 亚洲短视频男人的影院| 亚洲国产精品久久久久网站 | 亚洲av无一区二区三区| 亚洲国产精品无码第一区二区三区| 亚洲熟女综合一区二区三区| 亚洲国产无线乱码在线观看| 亚洲国产精品无码观看久久| 午夜亚洲福利在线老司机| 亚洲国产成人久久精品99| 亚洲中文无韩国r级电影| 亚洲乱码中文论理电影| 精品韩国亚洲av无码不卡区| 亚洲爆乳精品无码一区二区| 亚洲av色香蕉一区二区三区蜜桃| 亚洲第一成年网站视频| 国产亚洲综合精品一区二区三区| 亚洲精品高清在线| 国产亚洲成人在线播放va| 亚洲狠狠婷婷综合久久久久| 亚洲国产国产综合一区首页| 中文字幕亚洲免费无线观看日本| 亚洲综合一区二区精品久久| 亚洲娇小性色xxxx| 亚洲熟妇少妇任你躁在线观看| 欧美亚洲精品一区二区| 国产L精品国产亚洲区久久| 亚洲综合av永久无码精品一区二区 | 亚洲jjzzjjzz在线观看|