2

I have rigged up a simple login fragment with a view model. Here is the fragment :

class LoginFragment : Fragment() {

companion object {
    fun newInstance() = LoginFragment()
}

private lateinit var viewModel: LoginViewModel

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)

    viewModel.loginState.observe(this, Observer{
        handleState(it)
    })

    login_button.setOnClickListener {
        viewModel.isUserValid(username.text.toString(), pass.toString())
    }
}

private fun handleState(status: RegisterState) {
    if (status.statusMessage.equals("Good"))
        view?.findNavController()?.navigate(R.id.action_registerFragment_to_homeFragment)
    else
        Snackbar.make(login_container, "Welcome to SwA", Snackbar.LENGTH_LONG).show();
}
}

and here is my view model :

class LoginViewModel : ViewModel() {

lateinit var auth: FirebaseAuth

private var _loginState = MutableLiveData<LoginState>()
val loginState : MutableLiveData<LoginState> get() = _loginState

init {
    loginState.value = LoginState()
}

fun isUserValid(email: String, password: String): Boolean {
    //Add call to authenticate through firebase
    auth.signInWithEmailAndPassword(email, password)
        .addOnCompleteListener {
            if (it.isSuccessful) {
                // Sign in success, update UI with the signed-in user's information
                val user = auth.currentUser
                //updateUI(user)
            } else {
                // If sign in fails, display a message to the user.
                _loginState.value?.statusMessage = "Authentication Failed"
            }
        }

    return true
}
}

This works and registers a change to the string status when a failed log in is attempted, however it also submits an onChange() when loading the fragment causing the snackbar to appear in the UI before they have actually entered anything when the fragment is created. How can I initialize the view state without triggering an onChange() ?

Vandan Revanur
  • 459
  • 6
  • 17
Doug Ray
  • 988
  • 2
  • 9
  • 29

1 Answers1

3

LiveData class has a method

 boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

which checks if owner of the LifeCycle, Activity or Fragment, is on a state after STARTED which means for an Activity if it has called onStart().

So whenever an Activity or Fragment observes a LiveData it gets LiveData's value by setValue or PostValue after onStart is called.

One of the ways to prevent same value to update UI more than once is to use SingleLiveEvent class, example here. Another one is using Event class, example is here.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Hey Thracian thanks for your comment I looked into the live data source and found this method ! I have moved the initialization of the view model and observing of the view state to on create of the fragment but still see the snackbar appear on navigating to the login screen. Any idea why this would happen ? – Doug Ray Aug 31 '19 at 17:51
  • It's not about where you initialize subscription, i mean where you observe LiveData, it triggers the moment a fragment calls onStart method. You should use SingleLiveEvent for one time action until you call setValue or postValue again or use Event class. The second example for To-Do is great example to learn LiveData and MVVM with clean architecture. We use it for our apps. – Thracian Aug 31 '19 at 17:52
  • We use SingleLiveEvent for action like to check login, move to a next fragment, etc. You can check it out [here](https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java). – Thracian Aug 31 '19 at 17:56
  • I see thanks for explaining that, I was originally trying to use SingleLiveEvent but can't seem to find the correct gradle dependancy and can only resolve the dependancy for live data and mutable live data. Why is this not included in package androidx.lifecycle ? – Doug Ray Aug 31 '19 at 20:59
  • 1
    I don't know. I think it should have been with 3 options. 1- The default : Value can be observed by anytime a observer is STARTED. 2- Single event: Which the behavior of SingleLiveEvent. 3- Specific Observer only observes once: This should let one observer observe value only once but let every other observe it too unlike SingleLiveEvent. Also it would be cool to have some method to tell LiveData data it's consumed so not trigger other Observers. If that answer worked for you, would you mind accepting it? – Thracian Sep 01 '19 at 07:28