I’m using LiveData by Google now and they recomend to use a MVVM patter design. For some of my requests I use RxJava2, and listen for responses in SubscribeWith(…).
For example, when I press a button to send some data to the remote data source, I’m showing some loading animation and want to hide it onComplete() event (inside subscribeWith(…)). The problem is that I don’t have an access to the View from ModelView. How it’s possible to let the View know that loading animation should be hidden?
My current idea is to create in interface inside ViewModel and implement it in View. But it ruins the concept of View and ViewModel separation.
Well you can use liveData for this :D
At your ViewModel class you can create a live data object like this
MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
and for example make a function called downloadFinished and call it in the onComplete
for your remote code
private void downloadFinished() {
isLoading.setValue(true);
}
At your activity that use the view model you observe the value of the loading and hide the progress or what ever you want
TestViewModel viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
viewModel.isLoading.observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean isLoading) {
if (isLoading != null) {
if (isLoading) {
// hide your progress bar
}
}
}
});
you can use DataBinding for that too make a separate layout to reuse it everywhere laoding_state_xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
then include it inside your desired layout
<?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">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.blogspot.soyamr.notforgotagain.view.signin.SignInViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.signin.SignInFragment">
<include
android:id="@+id/include"
layout="@layout/toolbar_application"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/signInButtonView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="68dp"
android:layout_marginEnd="16dp"
android:onClick="@{() -> viewModel.logIn()}"
android:text="@string/sign_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passwordTextInputLayout" />
<TextView
android:id="@+id/noAccountTextview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="4dp"
android:text="@string/no_account"
android:textColor="@android:color/black"
app:layout_constraintEnd_toStartOf="@+id/createAccountTextView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/signInButtonView" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/emailTextInputLayout"
style="@style/myTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="80dp"
android:layout_marginEnd="16dp"
app:errorEnabled="true"
app:errorText="@{viewModel.emailErrorMessage}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email"
android:inputType="textEmailAddress"
android:text="@={viewModel.emailText}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordTextInputLayout"
style="@style/myTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
app:errorText="@{viewModel.passwordErrorMessage}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/emailTextInputLayout">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword"
android:text="@={viewModel.passwordText}" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/createAccountTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:text="@string/create_one"
android:textColor="@color/textBlue"
app:layout_constraintBottom_toBottomOf="@+id/noAccountTextview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/noAccountTextview"
app:layout_constraintTop_toTopOf="@+id/noAccountTextview" />
<!-- **here is the important include**-->
<include
android:id="@+id/here_must_be_id_or_no_databinding"
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}"
layout="@layout/loading_state" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
i included the whole XML for clarification.
then in your view model add this
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SignInViewModelFactory(private val repository: NoteRepository) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SignInViewModel(repository) as T
}
class SignInViewModel(val repository: NoteRepository) : ViewModel() {
private val _isLoading = MutableLiveData(true)
val emailText = MutableLiveData("")
val passwordText = MutableLiveData("")
val isLoading: LiveData<Boolean> = _isLoading
fun logIn() {
//start loading, this will make the view start loading directly
_isLoading.value = true
if (isValidInput()) {
val res = repository.logIn(LoginUser(emailText.value!!, passwordText.value!!))
}//remove loading view
_isLoading.value = false
}
//code ..
}
notice that you are observing isLoading
variable inside the XML so whenever its value is changed the view will observe the change and start act on it.
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