Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM - MutableLiveData of Custom Model not been updating into ViewModel with databinding and always is null

Summary:

When I try to send to the repository a custom Model, the MutableLiveData is null. I think it is because of the observer of the MutableLiveData.

Please, read to the end.

ViewModel

class LoginViewModel @Inject constructor(
        private val repository: MyRepository) : ViewModel() {


    var loginModel: MutableLiveData<LoginModel>

    init {
        loginModel = MutableLiveData<LoginModel>()
    }

    fun loadUser(): LiveData<Response<Custom<Token>>> {

        return repository.login(loginModel.value!!)
    }
}

As you can see here I have a MutableLiveData

My LoginModel is something like this

data class LoginModel(var user : String, var password : String)

A fragment was defined using databinding and binding with the ViewModel above.

login_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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"
    tools:context="com.app.example.view.BaseActivity">

    <data>
        <variable
            name="viewModel"
            type="com.app.example.view.login.LoginViewModel" />

    </data>

    <android.support.constraint.ConstraintLayout
        android:id="@+id/activityMain"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bg_login">

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="80dp"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginTop="80dp"
            app:cardCornerRadius="7dp"
            app:cardElevation="22dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center|top"
                android:layout_marginTop="60dp"
                android:text="@string/bienvenido_text"
                android:textAllCaps="true"
                android:textSize="20sp" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_margin="20dp"
                android:orientation="vertical">

                <android.support.design.widget.TextInputLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <EditText
                        android:id="@+id/etEmail"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="20dp"
                        android:layout_marginRight="20dp"
                        android:cursorVisible="true"
                        android:text="@{viewModel.loginModel.user}"
                        android:gravity="center|start|bottom"
                        android:hint="@string/email_text"
                        android:inputType="textEmailAddress"
                        android:maxLength="50"
                        android:paddingBottom="10dp"
                        android:textSize="18sp" />

                </android.support.design.widget.TextInputLayout>

                <android.support.design.widget.TextInputLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:passwordToggleEnabled="true">

                    <android.support.design.widget.TextInputEditText
                        android:id="@+id/etPassword"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="20dp"
                        android:layout_marginRight="20dp"
                        android:layout_marginTop="30dp"
                        android:cursorVisible="true"
                        android:gravity="center|start|bottom"
                        android:hint="@string/contrasenia_text"
                        android:text="@{viewModel.loginModel.password}"
                        android:inputType="textPassword"
                        android:maxLength="50"
                        android:paddingBottom="10dp"
                        android:textSize="18sp" />

                </android.support.design.widget.TextInputLayout>

                <Button
                    android:id="@+id/btnServerLogin"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_margin="15dp"
                    android:padding="10dp"
                    android:text="@string/ingresar_text"
                    android:textSize="18sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|center"
                android:layout_marginBottom="40dp"
                android:orientation="horizontal">

            </LinearLayout>
        </android.support.v7.widget.CardView>

    </android.support.constraint.ConstraintLayout>

</layout>

Using Databinding, I've implemented the relationship between etEmail and the property user of the Model UserModel and also I've implemented the relationship between etPassword and the property password

when the user click on btnServerLogin, the LoginFragment will execute the following code

binding.btnServerLogin.setOnClickListener {
                loginViewModel!!.loadUser().observe(this, Observer<Response<Custom<Token>>> { this.handleResponse(it) })
}

And here is the problem.

kotlin.KotlinNullPointerException

There reason is because of loginModel.value!! is null

fun loadUser(): LiveData<Response<Custom<Token>>> {

        return repository.login(loginModel.value!!)
}

The MutableLiveData's value is always null. I though It would change at the same time you were typing in your EditText of email or passoword.

Here is the code of the LoginFragment OnActivityCreated in which I've initialized the ViewModel

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    setHasOptionsMenu(true)
    DeviceUtils.setTranslucentStatusBar(activity!!.window, R.color.colorPrimaryDark)

    loginViewModel = ViewModelProviders.of(this, viewModelFactory)
            .get(LoginViewModel::class.java)
    binding.setLifecycleOwner(this)
    binding.viewModel = loginViewModel


    binding.btnServerLogin.setOnClickListener {
                mPostsViewModel!!.loadUser().observe(this, Observer<Response<RespuestaModel<JwtTokenModel>>> { this.handleResponse(it) })
    }

    return
}

What am I doing wrong? Shall I need to add an specific Observer?

Thanks

like image 391
Faustino Gagneten Avatar asked Oct 19 '18 13:10

Faustino Gagneten


People also ask

What is difference between ViewModel and LiveData?

ViewModel : Provides data to the UI and acts as a communication center between the Repository and the UI. Hides the backend from the UI. ViewModel instances survive device configuration changes. LiveData : A data holder class that follows the observer pattern, which means that it can be observed.

What is data binding in MVVM?

Data binding is the key technology that MVVM relies on, to link Views with their View-Models. Data binding provides and maintains the automated Two-Way connection between View and ViewModel. A good understanding of data binding is essential for every MVVM developer.

What is difference between LiveData and MutableLiveData?

Update LiveData objectsLiveData has no publicly available methods to update the stored data. The MutableLiveData class exposes the setValue(T) and postValue(T) methods publicly and you must use these if you need to edit the value stored in a LiveData object.

What is live data in Kotlin?

In this post I'll explore the LiveData class, the problems it wishes to solve and when to use it. Basically, LiveData is an observable data holder. It lets the components in your app, usually the UI, observe LiveData objects for changes.


2 Answers

Thanks to @communistWatermelon, I was able to resolve a piece of the problem but it wasn't enough. Here is the complete solution:

On one hand we have to initialize the value property of the MutableLiveData as @communistWatermelon said so:

var loginModel: MutableLiveData<LoginModel> = MutableLiveData()

init {
    loginModel.value = LoginModel()
}

On the other hand, we need to set properly the binding in the layout

In the case of @{variable.field} the binder will generate a piece of code to get the value by calling variable.getField(). Note that the binder implicitly prefix the “get” word if it is not provided. So @{variable.getField()} is an explicit valid option.

In the case of @={variable.field} the binder will create the same code as before, but with additional code for getting the value from the View and setting it back to your object

After reading that, we need to change the way of the binding

android:text="@{viewModel.loginModel.password}"
android:text="@{viewModel.loginModel.user}"

to

android:text="@={viewModel.loginModel.password}"
android:text="@={viewModel.loginModel.user}"

EDIT

For those who didn't resolve using the above explanation, try registering the lifecycleOwner in your fragment with the following line:

binding.setLifecycleOwner(this)

Good coding!

like image 56
Faustino Gagneten Avatar answered Oct 07 '22 06:10

Faustino Gagneten


This has happened to me as well, except what had happened is I used MutableLiveData on my ViewModel without registering my fragment/activity as LifeCycleOwner to the ViewDataBinding.

The Question would not experience this issue because

    binding.setLifecycleOwner(this)

This may not be relevant to the question, but if anyone else lands here like I did, double check that you do the setLifecycleOwner on your binding.

like image 4
Jacques.S Avatar answered Oct 07 '22 05:10

Jacques.S