I have 1 activity with 3 fragments: Sign In/Sign Up/Forgot Password
Sign In, which is the main fragment among these 3 works just fine, however, I have the issue when working with Sign Up/Forgot Password fragments.
Currently I have this navigation component:
Sign In Fragment:
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
}
}
ForgotPassword Fragment:
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
}
}
Navigation 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>
What happens is: I launch my app. Press - Forgot Password, then enter my e-mail, I get alert dialog that the e-mail has been sent and then I am transferred to my sign-in screen. However, when I next time press "Forgot Password" I do not see that same field and etc, instead all I get is that AlertDialog that was run last time with Success message. (Yet I don't receive the message) so the Fragment gets stuck and I can never again get inside unless I restart the app.
I have no idea what is the cause of this and how to fix this, please help me out here.
The problem lies here:
private val loginViewModel: LoginViewModel by activityViewModels()
by activityViewModels()
makes it so that the state persists between all Fragments that are referencing the ViewModel in such manner.
Simply replace activityViewModels()
with viewModels()