-
MVVM简介
(1).Model:数据层,包含数据实体和对数据实体的操作 (2).View:界面层,对应于Activity,XML,View,负责数据显示以及用户交互。 (3).ViewModel:关联层,将Model和View进行绑定,Model或者View更改时,实时刷新对方。数据驱动视图 -
View层 LoginActivity.kt
class LoginActivity : AppCompatActivity() { private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) val username = findViewById<EditText>(R.id.username) val password = findViewById<EditText>(R.id.password) val login = findViewById<Button>(R.id.login) val loading = findViewById<ProgressBar>(R.id.loading) loginViewModel = ViewModelProvider(this, LoginViewModelFactory()) .get(LoginViewModel::class.java) loginViewModel.loginFormState.observe(this@LoginActivity, Observer { val loginState = it ?: return@Observer // disable login button unless both username / password is valid login.isEnabled = loginState.isDataValid if (loginState.usernameError != null) { username.error = getString(loginState.usernameError) } if (loginState.passwordError != null) { password.error = getString(loginState.passwordError) } }) loginViewModel.loginResult.observe(this@LoginActivity, Observer { val loginResult = it ?: return@Observer loading.visibility = View.GONE if (loginResult.error != null) { showLoginFailed(loginResult.error) } if (loginResult.success != null) { updateUiWithUser(loginResult.success) } setResult(Activity.RESULT_OK) //Complete and destroy login activity once successful finish() }) username.afterTextChanged { loginViewModel.loginDataChanged( username.text.toString(), password.text.toString() ) } password.apply { afterTextChanged { loginViewModel.loginDataChanged( username.text.toString(), password.text.toString() ) } setOnEditorActionListener { _, actionId, _ -> when (actionId) { EditorInfo.IME_ACTION_DONE -> loginViewModel.login( username.text.toString(), password.text.toString() ) } false } login.setOnClickListener { loading.visibility = View.VISIBLE loginViewModel.login(username.text.toString(), password.text.toString()) } } } private fun updateUiWithUser(model: LoggedInUserView) { val welcome = getString(R.string.welcome) val displayName = model.displayName // TODO : initiate successful logged in experience Toast.makeText( applicationContext, "$welcome $displayName", Toast.LENGTH_LONG ).show() } private fun showLoginFailed(@StringRes errorString: Int) { Toast.makeText(applicationContext, errorString, Toast.LENGTH_SHORT).show() } }初始化控件
Activity持有loginViewModel,初始化它,添加对其的观察者,对控件进行更改状态
对控件进行监控
kotlin语言对控件进行简化(username.error=,login.isEnabled= )
-
ViewModel层 LoginViewModel.kt
class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() { private val _loginForm = MutableLiveData<LoginFormState>() val loginFormState: LiveData<LoginFormState> = _loginForm private val _loginResult = MutableLiveData<LoginResult>() val loginResult: LiveData<LoginResult> = _loginResult fun login(username: String, password: String) { // can be launched in a separate asynchronous job val result = loginRepository.login(username, password) if (result is Result.Success) { _loginResult.value = LoginResult(success = LoggedInUserView(displayName = result.data.displayName)) } else { _loginResult.value = LoginResult(error = R.string.login_failed) } } fun loginDataChanged(username: String, password: String) { if (!isUserNameValid(username)) { _loginForm.value = LoginFormState(usernameError = R.string.invalid_username) } else if (!isPasswordValid(password)) { _loginForm.value = LoginFormState(passwordError = R.string.invalid_password) } else { _loginForm.value = LoginFormState(isDataValid = true) } } // A placeholder username validation check private fun isUserNameValid(username: String): Boolean { return if (username.contains('@')) { Patterns.EMAIL_ADDRESS.matcher(username).matches() } else { username.isNotBlank() } } // A placeholder password validation check private fun isPasswordValid(password: String): Boolean { return password.length > 5 } }方法同View层进行交互,还有网络请求,结果通过被观察提供给View层
-
Model层 LoginRepository.kt
登录管理
class LoginRepository(val dataSource: LoginDataSource) { // in-memory cache of the loggedInUser object var user: LoggedInUser? = null private set val isLoggedIn: Boolean get() = user != null init { // If user credentials will be cached in local storage, it is recommended it be encrypted // @see https://developer.android.com/training/articles/keystore user = null } fun logout() { user = null dataSource.logout() } fun login(username: String, password: String): Result<LoggedInUser> { // handle login val result = dataSource.login(username, password) if (result is Result.Success) { setLoggedInUser(result.data) } return result } private fun setLoggedInUser(loggedInUser: LoggedInUser) { this.user = loggedInUser // If user credentials will be cached in local storage, it is recommended it be encrypted // @see https://developer.android.com/training/articles/keystore } }LoginDataSource.kt
登录请求
class LoginDataSource { fun login(username: String, password: String): Result<LoggedInUser> { try { // TODO: handle loggedInUser authentication val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe") return Result.Success(fakeUser) } catch (e: Throwable) { return Result.Error(IOException("Error logging in", e)) } } fun logout() { // TODO: revoke authentication } }-
LiveData使用
在LoginViewModel中使用,通过它的数据变化来驱动View层中的监听
_loginResult.value = LoginResult(error = R.string.login_failed) 这样监听它的观察者就能够收到通知。 -
总结
这种方式比较好,不用在xml中绑定Data代码,也不用写比较奇怪的代码,也实现了数据驱动的方式。
活到老,学到老,加油!!!
-
-