ViewModel让开发更放心
概况
做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
文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_40763897/article/details/92772085
- 点赞
- 收藏
- 关注作者
评论(0)