Reputation: 2774
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?
Upvotes: 6
Views: 3396
Reputation: 2774
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!
Upvotes: 10
Reputation: 3593
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.
Upvotes: 4
Reputation: 166
MutableLiveData<LoginModel>()
doesn't initialize the value
property of the MutableLiveData
object. You'll likely need to set a value in the init
call, like this:
init {
loginModel = MutableLiveData<LoginModel>()
loginModel.value = LoginModel()
}
Because you're not setting a value, doing so would cause the `NullPointerException at
return repository.login(loginModel.value!!)
Upvotes: 1