MVVM(LoginApp demo分析)

1,016 阅读2分钟
  1. MVVM简介

     (1).Model:数据层,包含数据实体和对数据实体的操作
     (2).View:界面层,对应于Activity,XML,View,负责数据显示以及用户交互。
     (3).ViewModel:关联层,将Model和View进行绑定,Model或者View更改时,实时刷新对方。数据驱动视图
    
  2. 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= )

  3. 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层

    1. 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
          }
      }
      
      1. LiveData使用

        在LoginViewModel中使用,通过它的数据变化来驱动View层中的监听

                    _loginResult.value = LoginResult(error = R.string.login_failed)
                    这样监听它的观察者就能够收到通知。
        
      2. 总结

        这种方式比较好,不用在xml中绑定Data代码,也不用写比较奇怪的代码,也实现了数据驱动的方式。

        活到老,学到老,加油!!!