首页 > 解决方案 > 导航组件片段在第一次执行后卡在运行最后一行代码

问题描述

我有 1 个包含 3 个片段的活动:登录/注册/忘记密码

登录,这是这三个工作中的主要片段,但是,我在使用注册/忘记密码片段时遇到了问题。

目前我有这个导航组件:

我的导航组件

登录片段:

class SignInFragment : Fragment() {

    private val loginViewModel: LoginViewModel by activityViewModels()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_sign_in, container, false)

        // see "ProgressButton" in dependencies of build.gradle file.
        // used for more interactive button.
        bindProgressButton(view.button_sign_in_submit)

        loginViewModel.signInState.observe(viewLifecycleOwner, { state ->
            when (state) {
                is UserSignInState.Loading -> view.button_sign_in_submit.showProgress {
                    buttonTextRes = R.string.status_loading
                    progressColor = Color.WHITE
                }
                is UserSignInState.SignedIn -> {
                    view.button_sign_in_submit.hideProgress(R.string.button_sign_in_submit)

                    startActivity(Intent(this.activity, ProfileActivity::class.java))
                    this.activity?.finish()
                }
                is UserSignInState.Failure -> {
                    view.button_sign_in_submit.hideProgress(R.string.button_sign_in_submit)
                    view.sign_in_input_password_layout.error = state.exception.localizedMessage
                }
            }
        })

        // Used for resetting focus and hiding keyboard for better user experience.
        view.sign_in_input_email.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) hideKeyboard() }
        view.sign_in_input_password.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) hideKeyboard() }

        view.button_go_to_sign_up.setOnClickListener {
            hideKeyboard()
            this.findNavController().navigate(R.id.action_signInFragment_to_signUpFragment)
        }

        view.button_go_to_forgot_password.setOnClickListener {
            hideKeyboard()
            this.findNavController().navigate(R.id.action_signInFragment_to_forgotPasswordFragment)
        }

        view.button_sign_in_submit.setOnClickListener {
            hideKeyboard()

            view.sign_in_input_email_layout.error = null
            view.sign_in_input_password_layout.error = null

            val email = view.sign_in_input_email.text.toString()
            val password = view.sign_in_input_password.text.toString()

            if (email.isNotEmpty() && password.isNotEmpty()) {
                if (isEmailValid(email)) {
                    loginViewModel.userSignIn(email, password)
                } else {
                    view.sign_in_input_email_layout.error = getString(R.string.error_message_email_is_not_valid)
                }
            } else {
                view.sign_in_input_password_layout.error = getString(R.string.error_message_please_fill_in_all_fields)
            }
        }
        return view
    }
}

忘记密码片段:

class ForgotPasswordFragment : Fragment() {

    private val loginViewModel: LoginViewModel by activityViewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_forgot_password, container, false)
        // see "ProgressButton" in dependencies of build.gradle file.
        // used for more interactive button.
        bindProgressButton(view.button_forgot_password_submit)

        this.retainInstance = false

        loginViewModel.passwordResetState.observe(viewLifecycleOwner, { state ->
            when (state) {
                is UserPasswordResetState.Loading -> view.button_forgot_password_submit.showProgress {
                    buttonTextRes = R.string.status_loading
                    progressColor = Color.WHITE
                }
                is UserPasswordResetState.Sent -> {
                    view.button_forgot_password_submit.hideProgress(R.string.button_forgot_password_submit)
                    alertDialogSuccess(requireContext(), getString(R.string.alert_dialog_message_forgot_password_email_has_been_sent))
                    activity?.findNavController(R.id.nav_host_fragment_login)?.navigate(R.id.action_forgotPasswordFragment_to_signInFragment)
                }
                is UserPasswordResetState.Error -> {
                    view.button_forgot_password_submit.hideProgress(R.string.button_forgot_password_submit)

                    // Could be possibly replaced with implementation of "alertDialogFailure"
                    view.forgot_password_input_field_email_layout.error = state.exception.localizedMessage
                }
            }

        })

        view.forgot_password_input_field_email.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) hideKeyboard() }

        view.button_forgot_password_submit.setOnClickListener {
            hideKeyboard()

            if (isEmailValid(view.forgot_password_input_field_email.text.toString())) {
                loginViewModel.userResetPassword(view.forgot_password_input_field_email.text.toString())
            } else if (!isEmailValid(view.forgot_password_input_field_email.text.toString())) {
                view.forgot_password_input_field_email_layout.error = getString(R.string.error_message_email_is_not_valid)
            } else {
                view.forgot_password_input_field_email_layout.error = getString(R.string.error_message_unknown)
            }
        }
        return view
    }
}

导航 XML

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph_login"
    app:startDestination="@id/signInFragment">

    <fragment
        android:id="@+id/signInFragment"
        android:name="my.test.movieexpert._login.view.fragments.SignInFragment"
        android:label="fragment_sign_in"
        tools:layout="@layout/fragment_sign_in">
        <action
            android:id="@+id/action_signInFragment_to_signUpFragment"
            app:destination="@id/signUpFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim" />
        <action
            android:id="@+id/action_signInFragment_to_forgotPasswordFragment"
            app:destination="@id/forgotPasswordFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim" />
    </fragment>
    <fragment
        android:id="@+id/signUpFragment"
        android:name="my.test.movieexpert._login.view.fragments.SignUpFragment"
        android:label="fragment_sign_up"
        tools:layout="@layout/fragment_sign_up">
        <action
            android:id="@+id/action_signUpFragment_to_signInFragment"
            app:destination="@id/signInFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popUpTo="@+id/signInFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/forgotPasswordFragment"
        android:name="my.test.movieexpert._login.view.fragments.ForgotPasswordFragment"
        android:label="fragment_forgot_password"
        tools:layout="@layout/fragment_forgot_password">
        <action
            android:id="@+id/action_forgotPasswordFragment_to_signInFragment"
            app:destination="@id/signInFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popUpTo="@+id/signInFragment"
            app:popUpToInclusive="true" />
    </fragment>
</navigation>

会发生什么:我启动我的应用程序。按 - 忘记密码,然后输入我的电子邮件,我收到电子邮件已发送的警报对话框,然后我被转移到我的登录屏幕。但是,当我下次按“忘记密码”时,我看不到相同的字段等,而我得到的只是上次运行的带有成功消息的 AlertDialog。(但我没有收到消息)所以 Fragment 卡住了,除非我重新启动应用程序,否则我永远无法再次进入。

我不知道这是什么原因以及如何解决这个问题,请在这里帮助我。

标签: androidkotlinandroid-fragmentsandroid-jetpackandroid-architecture-navigation

解决方案


问题出在这里:

private val loginViewModel: LoginViewModel by activityViewModels()

by activityViewModels()使状态在以这种方式引用 ViewModel 的所有 Fragment 之间保持不变。

只需替换activityViewModels()viewModels()


推荐阅读