ViewModel开发更放心

网友投稿 511 2022-05-29

概况

做android开发,有时我们会采用MVP模式,把业务逻辑从Activity中分解出来。但是Presenter的生命周期不容易管理。对于一个复杂的Activity和Fragment来说,可能绑定了多个Presenter、Manager或者View,代码写起来就会很复杂。尤其是当这些被其他人复用的时候,很难让别人也注意到这一点,很容易发生内存溢出问题。

LifeCycle

Google推出的LifeCycle就可以解决辅助类的生命周期问题。

在API 26.1.0之后,android.support.v4.app中的FragmentActivity和Fragment都集成了LifeCycle的相关功能。

LiveData 和 ViewModel 生命周期组件是 Android 官方架构组件中的核心组件, 它可以使各种实例作为观察者与 Activity 和 Fragment 等具有生命周期特性的组件绑定在一起。我们将需要绑定生命周期的实例注册给该组件, 该组件就会在指定的某个生命周期方法执行时通知这个实例。它们用到了观察者模式。

LiveData

LiveData是一个可观察的数据持有者类。与常见的观察者不同,LiveData是有生命周期感知的。这种感知确保LiveData只更新处于生命周期状态内的应用程序组件(这一点太重要了)。

ViewModel

ViewModel 有两个功能, 第一个功能可以使 ViewModel 以及 ViewModel 中的数据在屏幕旋转或配置更改引起的 Activity 重建时存活下来, 重建后数据可继续使用; 第二个功能可以帮助开发者轻易实现 Fragment 与 Fragment 之间, Activity 与 Fragment 之间的通讯以及共享数据。

使用方法

通过创建MyViewModel extends ViewModel

在MyViewModel内部新建MutableLiveData 创建可监听数据

在Activity 或 Fragment中获取获取MyViewModel里的MutableLiveData并添加监听 viewModel.liveData.observer()

实例

我们以一个登录页LogActivity来举例说明吧。一般我们把业务交给ViewModel来处理。View则根据数据的变化来做调整,这部分是写在Fragment或Activity类里。

(1)分析登录页LoginActivity的需求

登录数据有效性验证,如用户名、密码的有效性验证,可以用一个类来记录这些登录表单的状态:

class LoginFormState { @Nullable private Integer usernameError;// 用户名错误 @Nullable private Integer passwordError;// 密码错误 // 我们用这个字段来控制登录按钮的状态 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

当数据有效性通过后,就开始登录,登录或成功或失败,我们用一个类来记录这些登录的结果:

class LoginResult { @Nullable private LoggedInUserView success; // 成功则返回数据 @Nullable private Integer error; // 错误则返回一个错误码 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

成功返回的数据的类:

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里帮我们完成这些数据校验和登录等数据逻辑。

由于在LoginActivity里要通过以下这种方式来获得LoginViewModel:

loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory()) .get(LoginViewModel.class);

1

2

因此我们要先建一个工厂类LoginViewModelFactory来创建我们的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

从上面我们可以看到在创建LoginViewModel时传了一个LoginRepository单例进去。LoginRepository在实例化时传了一个LoginDataSource实例进去。

根据前面的分析我们可知LoginViewModel会提供登录及表单数据的有效性验证,但是如果需要共享登录的方法,甚至退出登录的方法、登录的状态、登录后用户的信息我们该怎么办呢?很简单只要将这些部分放在一个单例类里就好了。在我们这里就是LoginRepository这个类来充当共享的单例类。它的代码如下:

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; } // 用户的登录状态,用户已登录则 返回true,否则返回false public boolean isLoggedIn() { return user != null; } // 退出时,清空LoginRepository单例类里所有的数据 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

用户数据实体类:

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

登录结果的实体类:

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

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

负责处理用户登录和验证,并获取用户数据的数据源:

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 { // 持有一个可观察的数据LoginFormState的LiveData类 private MutableLiveData loginFormState = new MutableLiveData<>(); // 持有一个可观察的数据LoginResult的LiveData类 private MutableLiveData loginResult = new MutableLiveData<>(); // 缓存类,所有需要共享的方法数据都在里面 private LoginRepository loginRepository; LoginViewModel(LoginRepository loginRepository) { this.loginRepository = loginRepository; } // 返回一个可观察的数据持有者类LiveData,它持有LoginFormState数据 LiveData getLoginFormState() { return loginFormState; } // 返回一个可观察的数据持有者类LiveData,它持有LoginResult数据 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())));// 设置新数据,并通知观察者 } else { loginResult.setValue(new LoginResult(R.string.login_failed));// 设置新数据,并通知观察者 } } // 处理表单数据变化的 public void loginDataChanged(String username, String password) { if (!isUserNameValid(username)) {// 验证用户名的有效性 loginFormState.setValue(new LoginFormState(R.string.invalid_username, null)); } else if (!isPasswordValid(password)) {// 验证密码的有效性 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); // 观察表单数据的变化 loginViewModel.getLoginFormState().observe(this, new Observer() { @Override public void onChanged(@Nullable LoginFormState loginFormState) { if (loginFormState == null) { return; } loginButton.setEnabled(loginFormState.isDataValid());// 设置按钮状态 if (loginFormState.getUsernameError() != null) {// 用户名错误 usernameEditText.setError(getString(loginFormState.getUsernameError())); } if (loginFormState.getPasswordError() != null) {// 密码错误 passwordEditText.setError(getString(loginFormState.getPasswordError())); } } }); // 观察登录结果的变化 loginViewModel.getLoginResult().observe(this, new Observer() { @Override public void onChanged(@Nullable LoginResult loginResult) { if (loginResult == null) { return; } loadingProgressBar.setVisibility(View.GONE);// 关闭loading动画 if (loginResult.getError() != null) {// 登录失败 showLoginFailed(loginResult.getError());// 显示登录失败消息 } if (loginResult.getSuccess() != null) {// 登录成功 updateUiWithUser(loginResult.getSuccess());// 显示登录成功消息 } setResult(Activity.RESULT_OK);// 对使用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) { // 设置表单数据的变化,通知观察者 loginViewModel.loginDataChanged(usernameEditText.getText().toString(), passwordEditText.getText().toString()); } }; usernameEditText.addTextChangedListener(afterTextChangedListener);// 注册文本变化- passwordEditText.addTextChangedListener(afterTextChangedListener);// 注册文本变化- 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

ViewModel让开发更放心

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上了,欢迎下载学习。

Android

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:华为宣布启动数据基础设施战略并开源数据虚拟化引擎HetuEngine
下一篇:一文搞懂步进电机特性、原理及驱动器设计
相关文章