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?
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.
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.
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.
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.
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With