单事件实时数据多次发出空值将给出正确的结果

时间:2019-05-11 09:07:00

标签: android android-architecture-components android-livedata

我在我的应用程序中进行了简单的登录,该操作可验证服务器端的用户名和密码,并以此显示一条敬酒消息。

LoginFragment

private fun onClickLogin(view: View) {

        view.loginButton.setOnClickListener {

            val emailAddress = view.emailTextInputEditText.text.toString()
            val password = view.passwordTextInputEditText.text.toString()

            viewmodel.generalLogin(emailAddress, password).observe(viewLifecycleOwner, Observer {

                if(it != null){

                    if (it.status) {
                        Toast.makeText(
                            context,
                            "Hi, " + it.data?.displayName,
                            Toast.LENGTH_SHORT
                        ).show()

                        val sharedPref = PreferenceManager
                            .getDefaultSharedPreferences(context)
                        val editor = sharedPref.edit()

                        editor.putString(getString(R.string.user_id), it.data?.email).apply()
                        editor.putString(getString(R.string.user_name), it.data?.email).apply()

                        activity?.finish()

                    } else {

                        Toast.makeText(
                            context,
                            "Error, " + it.message,
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }

            })

        }

    }

LoginViewModel

fun generalLogin(email: String, password: String): LiveData<Resource<UserSession>> {

        val encryptedPassword = MCryptHelper.bytesToHex(MCryptHelper().encrypt(password))

        return Transformations.switchMap(loginRepository.generalLogin(email, encryptedPassword)) {
            it.getContentIfNotHandled().let{ resource ->
                val userSessionLiveData = MutableLiveData<Resource<UserSession>>()
                userSessionLiveData.value = resource
                return@switchMap userSessionLiveData
            }

        }
    }

登录库

fun generalLogin(email: String, encryptedPassword: String):MutableLiveData<SingleLiveEvent<Resource<UserSession>>>{

        val login = Global.network.login(email, encryptedPassword)

        login.enqueue(object : Callback<LoginResponse> {

            override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {

                if(response.body()?.status == 1){
                    val resource = Resource<UserSession>(true,"Success")
                    response.body().let {
                        if(it?.session != null){
                            resource.data = UserSession(it.session.userId!!,it.session.fullName!!)
                        }
                    }

                    loginMutableData.value = SingleLiveEvent(resource)

                }else{

                    val resource = Resource<UserSession>(false,response.body()?.msg ?: "Login failed. Try again")
                    loginMutableData.value  = SingleLiveEvent(resource)
                }
            }

            override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
                loginMutableData.value = SingleLiveEvent(Resource(false, t.localizedMessage))
            }

        })

        return loginMutableData

    }

SingleLiveEvent

class SingleLiveEvent<out T>(private val content: T){

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

此示例对用户而言效果很好。但是我注意到的是来自LoginViewModel generalLogin函数,它多次向片段发出空值,同时在其中给出写值。此应用只能正常运行,因为我可以处理LoginFragment上的null检查。当您尝试使用错误的登录凭据进行越来越多的操作时,似乎会增加空发射的数量。

是否有更好的方法来解决此问题?如果有一种方法可以处理这种情况,那就好了,即getContentIfNotHandled()的结果为null时,不要发出任何东西,以便在Fragment中没有要观察的东西。

给出您的建议。谢谢。

1 个答案:

答案 0 :(得分:0)

每一层都有一些错误,其重要性各不相同。让我们一个接一个地检查它们。


[1]在点击侦听器内部的片段附加观察器

每次用户单击按钮时,“ LoginFragment”都会创建一个新的观察者,而以前的观察者仍然活着并在踢。这就是每次尝试登录时发射数量都会增加一的原因。而且,这种设计容易受到屏幕旋转和配置更改的影响。例如,假设用户在登录请求期间旋转了屏幕。该登录请求的结果丢失了,因为没有人观察到该请求,最糟糕的部分是该视图将显示该用户尚未登录,而该存储库则是该用户的视图。

为了正确解决此问题,您需要分离观察逻辑并单击事件逻辑。另外请记住,除非您传入自定义onCreateView()对象或删除观察者,否则请始终在onActivityCreated()LifeCycleOwner中进行观察。


[2]视图模型中switchMap的用法不正确

另一个问题是,每次调用viewModel.generalLogin()时,都会使用另一个switchMap,因此会创建一个全新的LiveDataLiveData不是应该动态创建的内容。它们需要在视图模型初始化时创建一次并观察,直到清除视图模型为止。


[3]存储库

大多数情况下,存储库代码是合理的,但我认为可以通过使generalLogin不返回LiveData来改进存储库代码。只是回去回呼某种风格,或者根本不返回任何东西。另请注意,当前存储库具有loginMutableData。没关系,但这是您需要手动跟踪的另一个变量。您通常希望尽可能使存储库保持无状态。

因此,完整的解决方案是要分散的存储库:

存储库

// Create this new function
fun getLoginStatus(): LiveData<SingleLiveEvent<Resource<UserSession>>> {
    return loginMutableData
}

// Notice this function does not return anything.
fun generalLogin(email: String, encryptedPassword: String) {

    val login = Global.network.login(email, encryptedPassword)

    login.enqueue(object : Callback<LoginResponse> {

        override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
            ...
        }

        override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
            ...
        }

    })
}

ViewModel

val userSessionLiveData: LiveData<SingleLiveEvent<Resource<UserSession>>>
// val userSessionLiveData: LiveData<Resource<UserSession>>

init {

    userSessionLiveData = loginRepository.getLoginStatus()

    // Use below if some mapping needs to be done
    // userSessionLiveData = Transformations.map(loginRepository.getLoginStatus()) {
    //     return it?.contentIfNotHandled?
    // }
}

fun generalLogin(email: String, password: String) {
    val encryptedPassword = MCryptHelper.bytesToHex(MCryptHelper().encrypt(password))
    loginRepository.generalLogin(email, encryptedPassword)
}

片段

void onCreateView(...): View {
    ...
    viewModel.userSEssionLiveData.observe(viewLifecycleOwner, Observer {

        val resource = it?.contentIfNotHandled?
        if (resource == null) return

        val session = resource.data?

        if (resource.status) {
            Toast.makeText(
                context,
                "Hi, " + session.displayName,
                Toast.LENGTH_SHORT
            ).show()

            val sharedPref = PreferenceManager
                .getDefaultSharedPreferences(context)
            val editor = sharedPref.edit()

            editor.putString(getString(R.string.user_id), session.email).apply()
            editor.putString(getString(R.string.user_name), session.email).apply()

            activity?.finish()

        } else {
            Toast.makeText(
                context,
                "Error, " + resource.message,
                Toast.LENGTH_SHORT
            ).show()
        }
    }
    ...
}

private fun onClickLogin(view: View) {

    view.loginButton.setOnClickListener {

        val emailAddress = view.emailTextInputEditText.text.toString()
        val password = view.passwordTextInputEditText.text.toString()

        viewmodel.generalLogin(emailAddress, password)
    }
}