ViewModel让开发更放心

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

概况

做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<T> 创建可监听数据
  • 在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 extends ViewModel> T create(@NonNull Class<T> 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<LoggedInUser> login(String username, String password) { // handle login Result<LoggedInUser> result = dataSource.login(username, password); if (result instanceof Result.Success) {// 如果登录成功,则将用户信息记录在单例类里,以备共享 setLoggedInUser(((Result.Success<LoggedInUser>) 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<T> { // 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<T> 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<LoggedInUser> 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> loginFormState = new MutableLiveData<>(); // 持有一个可观察的数据LoginResult的LiveData类 private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>(); // 缓存类,所有需要共享的方法数据都在里面 private LoginRepository loginRepository; LoginViewModel(LoginRepository loginRepository) { this.loginRepository = loginRepository; } // 返回一个可观察的数据持有者类LiveData,它持有LoginFormState数据 LiveData<LoginFormState> getLoginFormState() { return loginFormState; } // 返回一个可观察的数据持有者类LiveData,它持有LoginResult数据 LiveData<LoginResult> getLoginResult() { return loginResult; } // 处理用户登录的 public void login(String username, String password) { // 处理用户登录 Result<LoggedInUser> result = loginRepository.login(username, password); if (result instanceof Result.Success) {// 成功登录 LoggedInUser data = ((Result.Success<LoggedInUser>) 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<LoginFormState>() { @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<LoginResult>() { @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
  • 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上了,欢迎下载学习

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/92772085

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。