Adnimal
Adnimal

Reputation: 13

Sending data from fragment A to fragment B using ViewModel (Android Studio, Kotlin)

My use case is very simple. I have two fragments which need to communicate using ViewModel:

LoginFragment has a an editable email address field, whose value needs to be received in another fragment, called ProfileFragment. A straightforward transmission of a string field. My idea is to input the email address from LoginFragment, update a shared ViewModel, and then receive the email address in ProfileFragment.

LoginFramgnet XML:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".LoginFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <EditText
            android:id="@+id/editTextTextEmailAddress"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="50dp"
            android:layout_marginEnd="50dp"
            android:autofillHints=""
            android:backgroundTint="#FF9393"
            android:ems="10"
            android:hint="@string/email"
            android:inputType="textEmailAddress"
            android:textColor="#000000"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

       <Button
            android:id="@+id/btnLogin"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:background="#FFFDD2B5"
            android:text="@string/login"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="@+id/editTextTextPassword"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="@+id/editTextTextPassword"
            app:layout_constraintTop_toBottomOf="@+id/editTextTextPassword"
            app:layout_constraintVertical_bias="0.37" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</FrameLayout>

LoginFragment.kt

class LoginFragment : Fragment() {

private lateinit var viewModel: LoginFragmentViewModel


override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel = ViewModelProvider(this).get(LoginFragmentViewModel::class.java)

    btnLogin.setOnClickListener {
                Toast.makeText( activity,"${editTextTextEmailAddress.text} is logged in!!", Toast.LENGTH_SHORT).show()

                viewModel.setEmailAddress("${editTextTextEmailAddress.text}")

                Log.i("Info", "${viewModel.emailAddress.value}")

                view.findNavController().navigate(R.id.action_loginFragment_to_profileFragment)
    }

ViewModel.kt:

class LoginFragmentViewModel : ViewModel() {


val emailAddress = MutableLiveData<Any>()

fun setEmailAddress(email:String){
    emailAddress.value = email
}

}

ProfileFragment XML:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ProfileFragment">

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txtEmailAddress"
        android:layout_width="260dp"
        android:layout_height="144dp"
        android:text=""
        android:textSize="14sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

ProfileFragment.kt:

class ProfileFragment : Fragment() {


private lateinit var viewModel: LoginFragmentViewModel

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel = ViewModelProvider(this).get(LoginFragmentViewModel::class.java)

    viewModel.emailAddress.observe(viewLifecycleOwner, object : Observer<Any>{
        override fun onChanged(t: Any?) {
            txtEmailAddress.text = t!!.toString()
        }
    })
    Log.i("Info", "${viewModel.emailAddress.value}")
}

The Log on LoginFragment registers that the emailAddress in the ViewModel is changed, based on what is input in the editTextTextEmailAddress.

The Log on ProfileFragment returns "null".

What exactly am I doing wrong?

Upvotes: 1

Views: 510

Answers (1)

nyarian
nyarian

Reputation: 4365

You have two different ViewModel instances bound to two different ViewModelStores (represented as fragments). If you need a shared ViewModel, then you need to use a single ViewModelStore. I believe that changing ViewModelProvider(this) to ViewModelProvider(requireActivity()) for both fragments will do the trick, but you will need to find out for yourself if it's suitable for your design.

Upvotes: 1

Related Questions