解析并通过安卓自带LoginActivity学习MVVM
谷歌官方提供了登陆界面,在搜索引擎找到的介绍都不尽如人意,大多是太高端,不适合初学者,于是在这里把这个代码解析一下,顺带学习一下MVVM框架,OK,开整!
首先我们右键创建一个LoginActivity,创建后的界面如下:
如图所示:直接生成了 data 和 ui 两个Package。
为了便于理解,我们先从ui界面开始。看到哪里说到哪里,一步一步来。
1. LoginActivity
首先是LoginActivity,这个是全局的入口所在。
大道至简,两个输入框一个按钮。
主要功能放到了代码中,代码如下:
import 所有的库;//这里导入相应库,因为是自动生成,所以没必要每个库都解析一下
public class LoginActivity extends AppCompatActivity {
//这个将作为我们要讲的第一个点
private LoginViewModel loginViewModel; //vm,作用大概就是处理交互信息
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login); //这两句就是把界面和代码关联起来
//新建VM
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()));
}
}
});
/*监听改变状态获取user和password信息*/
loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
@Override
public void onChanged(@Nullable LoginResult loginResult) {
if (loginResult == null) {
return;
}
loadingProgressBar.setVisibility(View.GONE);
if (loginResult.getError() != null) {
showLoginFailed(loginResult.getError());
}
if (loginResult.getSuccess() != null) {
updateUiWithUser(loginResult.getSuccess());
}
setResult(Activity.RESULT_OK);
//Complete and destroy login activity once successful
finish();
}
});
//对输入框的监听方法
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) {
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());
}
});
}
/*更新UI*/
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();
}
//显示登陆失败
private void showLoginFailed(@StringRes Integer errorString) {
Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
}
}
2. ViewModel
在一开始我们就创建了一个LoginViewModel,如果不知道是什么用处的话,接下来就没办法继续搞了,所以我们把LoginViewModel拿出来解析一下。
import 所有的库;//这里导入相应库,因为是自动生成,所以没必要每个库都解析一下
public class LoginViewModel extends ViewModel {
private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
private LoginRepository loginRepository;
LoginViewModel(LoginRepository loginRepository) {
this.loginRepository = loginRepository;
}
//登录窗体的数据验证状态
LiveData<LoginFormState> getLoginFormState() {
return loginFormState;
}
//登录结果,成功/失败信息
LiveData<LoginResult> getLoginResult() {
return loginResult;
}
//登录,使用账号和密码登录
public void login(String username, String password) {
// can be launched in a separate asynchronous job
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;//不等于空并且长度大于5
}
}
3.OK,有了View有了VM,按照我们MVVM的思维,我们还缺一个Model, 作为登录的Model,那么必然需要账号密码登陆状态等等等等的Model
既然是Model,那么应该放在data之下。我们接下来看第三个代码:LoggedInUser,找不到位置的请上翻到顶部。
/**
* Data class that captures user information for logged in users retrieved from LoginRepository
* 从LoginRepository检索到的已登录用户捕获用户信息的数据类
*/
public class LoggedInUser {
private String userId; //ID
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;
}
}
很好理解不是吗…一个ID一个用户名。
——————————————————————————————————
在data之下还有三个代码,分别是:
LoginDataSource ------创建随机ID和一个用户名//代码里边是例子:用UUID作为ID,用“ Jane Doe”作为名字(提一嘴,JaneDoe 是“无名人士”的意思)。
LoginRepository------ 这个代码是 存储用户的,账户再次调用的时候可以用到。一般作为向服务器发送请求的参数。
Result----------登录结果,返回数据或者 登陆失败。
OK,Model我们看了,好像也没啥…
——————————————————————————
4.主要的东西已经学到了,我们看看剩下的ui里的几个文件:
LoggedInUserView--------------把用户信息显示到UI上
LoginFormState ----------- 主要写了三个属性:1.账号错误,2.密码错误 3.数据是否可用
LoginResult-------登录的结果:登录成功返回信息,或者登录失败返回int值
LoginViewModelFactory-------大概就是new了一个LoginViewModel。
以上是初学者个人对代码的解析,如果有大神看到了这篇文章并发现了不合理之处,请指正,非常感谢。
------本文章长期更新。