I’m implementing a MVVM and data-binding and I’m trying to understand when should I use Observable field over LiveData?
I already run through different documentations and discovered that LiveData is lifecycle aware, but in sample codes in Github these two are being used in ViewModel at the same time. So, I’m confused if LiveData is better than Observable field, why not just use LiveData at all?
Both have their use-cases, for instance:
If you want a life-cycle tolerant container for your UI state model, LiveData is the answer.
If you want to make the UI update itself when a piece of logic is changed in your view model, then use ObservableFields.
I myself prefer using a combination of LivaData and ObservableField/BaseObservable, the LiveData will normally behave as a life-cycle aware data container and also a channel between the VM and the View.
On the other hand the UI state model objects that are emitted through the LiveData are themselves BaseObservable or have their fields as ObservableField.
That way I can use the LiveData for total changes of the UI state.
And set values to the UI state model ObservableField fields whenever a small portion of the UI is to be updated.
Edit:
Here is a quick illustration on a UserProfile component for example:
UIStateModel
data class ProfileUIModel(
private val _name: String,
private val _age: Int
): BaseObservable() {
var name: String
#Bindable get() = _name
set(value) {
_name = value
notifyPropertyChanged(BR.name)
}
var age: Int
#Bindable get() = _age
set(value) {
_age = value
notifyPropertyChanged(BR.age)
}
}
ViewModel
class UserProfileViewModel: ViewModel() {
val profileLiveData: MutableLiveData = MutableLiveData()
...
// When you need to rebind the whole profile UI object.
profileLiveData.setValue(profileUIModel)
...
// When you need to update a specific part of the UI.
// This will trigger the notifyPropertyChanged method on the bindable field "age" and hence notify the UI elements that are observing it to update.
profileLiveData.getValue().age = 20
}
View
You'll observe the profile LiveData changes normally.
XML
You'll use databinding to bind the UI state model.
Edit: Now the mature me prefers Immutability instead of having mutable properties as explained in the answer.
You can use LiveData all the time, as long as there is a LifecycleOwner to observe. I prefer to keep bound fields that are only relevant to the ViewModel as Observable and use LiveData for fields whose state changes are also relevant to the Activity or Fragment.
LiveData - use with LifecycleOwner like activity or fragment
Observable - use with data binding
#Ahmed Ashraf's answer is misleading. If we are talking about data binding only, data binding itself is lifecycle aware, because we set the lifecycleOwner when we use data binding, and it already checks when the view is active.
binding.lifecycleOwner = viewLifecycleOwner
As a result, it's not necessary to use live data to hold a BaseObservable object, we can directly use the BaeObervable object inside the view model.
Coming back to the question, in one-way data binding, you can use LiveData or ObservableFields or even Stateflow, but in 2-way data binding, I still prefer ObservableFields, because it's more flexible, you can easily write custom logic in the observable field setter, and when the UI changes, it can call the setter to do some additional stuff.
class LoginViewModel : BaseObservable {
// val data = ...
#Bindable
fun getRememberMe(): Boolean {
return data.rememberMe
}
fun setRememberMe(value: Boolean) {
// Avoids infinite loops.
if (data.rememberMe != value) {
data.rememberMe = value
// React to the change.
saveData()
// Notify observers of a new value.
notifyPropertyChanged(BR.remember_me)
}
}
}
And in xml file.
<CheckBox
android:id="#+id/rememberMeCheckBox"
android:checked="#={viewmodel.rememberMe}"
/>
Related
I'm using a service locator (as advised in https://developer.android.com/training/dependency-injection#di-alternatives, but I'll switch to proper DI later I promise) to handle auth in my app. I have an authentication service that has a user property that I set and unset using logIn and logOut methods
I'd like my ContentView to react to changes in auth.user but I can't quite figure out how. I've tried wrapping it into by remember { mutableStateOf() } but I don't see any update upon login.. any idea what I am missing?
Thanks in advance! (snippets below)
#Composable
fn ContentView() {
val auth = ServiceLocator.auth
var loggedInUser: User? by remember { mutableStateOf(auth.user) } // <-- I would like my composable to react to changes to auth.user
if (loggedInUser) {
ViewA()
} else {
ViewB()
}
}
object ServiceLocator {
val auth = AuthenticationService()
}
class AuthenticationService {
var user: User? = null
fun logIn() {
// sets user...
}
fun logOut() {
// undefs user...
}
In your code snippet on this line
var loggedInUser: User? by remember { mutableStateOf(auth.user) }
you are creating an instance of MutableState<User?> with an initial value of the value that is at that time referenced by auth.user.
Due to remember { } this initialization happens only when the composable ContentView enters composition and then the MutableState instance is remembered across recompositions and reused.
If you later change the variable auth.user no recomposition will happen, because the value stored in loggedInUser (in the mutable state) has not changed.
The documentation for mutableStateOf explains what this call actually does behind the scenes
Return a new MutableState initialized with the passed in value.
The MutableState class is a single value holder whose reads and writes are observed by Compose. Additionally, writes to it are transacted as part of the Snapshot system.
Let's dissect this piece of information.
Return a new MutableState initialized with the passed in value.
Calling mutableStateOf returns a MutableState instance that is initialized with the value that was passed as the parameter.
The MutableState class is a single value holder
Every instance of this class stores a single value of state. It might store other values for the implementation purposes, but it exposes only a single value of state.
whose reads and writes are observed by Compose
Compose observes reads and writes that happen to instances of MutableState
This is the piece of information that you have missed.
The writes need to happen to the instance of the MutableState (loggedInUser in your case), not to the variable that has been passed in as the initial value (auth.user in your case).
If you really think about it, there is no built-in mechanism in Kotlin to observe changes to a variable, so it is understandable that there has to be a wrapper for Compose to be able to observe the changes. And that we have to change the state through the wrapper instead of changing the variable directly.
Knowing all that you could just move the mutable state into AuthenticationService and things would work
import androidx.compose.runtime.mutableStateOf
class AuthenticationService {
var user: User? by mutableStateOf(null)
private set
// ... rest of the service
}
#Composable
fun ContentView() {
val auth = ServiceLocator.auth
// no remember { } block this time because now the MutableState reference is being kept by
// the AuthenticationService so it won't reset on recomposition
val loggedInUser = auth.user
if (loggedInUser != null) {
ViewA()
} else {
ViewB()
}
}
However now your AuthenticationService depends on mutableStateOf and thus on the Composable runtime which you might want to avoid. A "Service" (or Repository) should not need to know details about the UI implementation.
There are other options to track state changes and not depend on Compose runtime. From the documentation section Compose and other libraries
Compose comes with extensions for Android's most popular stream-based
solutions. Each of these extensions is provided by a different
artifact:
Flow.collectAsState() doesn't require extra dependencies. (because it is part of kotlinx-coroutines-core)
LiveData.observeAsState() included in the androidx.compose.runtime:runtime-livedata:$composeVersion artifact.
Observable.subscribeAsState() included in the androidx.compose.runtime:runtime-rxjava2:$composeVersion or
androidx.compose.runtime:runtime-rxjava3:$composeVersion artifact.
These artifacts register as a listener and represent the values as a
State. Whenever a new value is emitted, Compose recomposes those parts
of the UI where that state.value is used.
Example using a Kotlin MutableStateFlow
// No androidx.compose.* dependencies anymore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class AuthenticationService {
private val user = MutableStateFlow<User?>(null)
val userFlow = user.asStateFlow()
fun logIn() {
user.value = User(/* potential parameters */)
}
fun logOut() {
user.value = null
}
}
And then in the composable we collect the flow as state.
import androidx.compose.runtime.collectAsState
#Composable
fun ContentView() {
val auth = ServiceLocator.auth
val loggedInUser = auth.userFlow.collectAsState().value
if (loggedInUser != null) {
ViewA()
} else {
ViewB()
}
}
To learn more about working with state in Compose see the documentation section on Managing State. This is fundamental information to be able to work with state in Compose and trigger recompositions efficiently. It also covers the fundamentals of state hoisting. If you prefer a coding tutorial here is the code lab for State in Jetpack Compose.
Eventually (when you app grows in complexity) you might want to put another layer between your Service/Repository layer and your UI layer (the composables). A layer that will hold and manage the UI state so you will be able to cover both positive outcomes (a successful login) and negative outcomes (a failed login).
If you are going the MVVM (Model-View-ViewModel) way or the MVI (Model-View-Intent) way, that layer would be covered by ViewModels. In that case the composables manage only some transient UI state themselves, while they get (or observe) the rest of the UI state from the VMs and call the VMs to perform actions. The VMs then interact with the Service/Repository layer and update the UI state accordingly. An introduction to handling the state as the complexity increases is in the video from Google about Using Jetpack Compose's automatic state observation.
I using MVVM on my Android application, on ViewModel I have many observers (from data binding) like ObservableBoolean, ObservableField, I read that I can use LiveData/MutableLiveData instead this observers... What's the difference? I can replace all my data binding observers by LiveData/MutableLiveData?
eg:
replace:
val loading: ObservableBoolean = ObservableBoolean()
By:
val loading: MutableLiveData<Boolean> = MutableLiveData()
Many times has passed and I learned a lot...
Replace all Data Binding Observable by LiveData, because LiveData respect the Activity lifecycle and can be used in JetPack lib's, like Room, Coroutine...
Depends on where you are reading the data from .
In our current project ,we read directly from RoomDB . RoomDB has the capability to send back a liveData object .
Through your ViewModel , you do a query to RoomDB which returns a LiveData (RoomDB will be your Single Source of Truth)
Your View say an Activity or Fragment - Subscribes to this viewmodel as an observer
And you update the view as per the response returned .
You could also directly bind the xml through Android Databinding(Using LiveData with Data Binding)
Mutable Data is used normally if you have any modifications after retrieving
This is a good Place to Start
If your aim is to just change basic properties of views in xml based on a change with the data in primitive data type in view model, it is simple and easy to use Data binding. For rest of the cases, live data is the only way.
I am using 2-way data binding to update a LiveData String object from my ViewModel with a string set in the EditText:
<android.support.design.widget.TextInputEditText
android:id="#+id/writeReviewTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={viewModel.liveReviewTitle}"
/>
So, from my understanding, the ViewModel would have its liveReviewTitle attribute updated every time the text changed in the EditText. I assume this is happening through the usage of a TextWatcher or some sort of listening mechanism that is being taken care of for me by the library. I also thought that when the text needed to be updated, it would have its setter called. Which does not seem to be the case! When the text changes, I need to do some more stuff in my ViewModel, therefore I implemented a custom setter for liveReviewTitle, but it is not being called (I tried debugging).
This is how it looks like in the ViewModel class:
var liveReviewTitle: MutableLiveData<String> = MutableLiveData()
set(value) {
field = value
customLogicHere()
}
Tried debugging this setter but it never seems to be called! What is happening here? Feels a little confusing. The text is being updated, and it is saved in the ViewModel, it is just the setter that is not called.
Of course it's never called, you're not setting a new MutableLiveData, you're setting a new String value inside the MutableLiveData (possibly with setValue).
However, you should be able to intercept the value that's being set and execute custom logic after setting the value if you expose a MediatorLiveData instead of the MutableLiveData directly.
EDIT: the following should work as expected:
val liveReviewTitle: MutableLiveData<String> = MutableLiveData()
private val mediator = MediatorLiveData<String>().apply {
addSource(liveReviewTitle) { value ->
setValue(value)
customLogicHere()
}
}.also { it.observeForever { /* empty */ } }
#EpicPandaForce solution is proper but in EditText two way binding can be obtained in much simpler way.
Add attribute afterTextChanged to your widget as below:
android:afterTextChanged="#{viewModel::doLogic}"
Then in your ViewModel class just write method:
fun doLogic(s: Editable) {
//update Livedata or do other logic
}
EDIT
I have missed important documentation note. Much easier (and far more proprer) will be:
android:text="#={viewModel.someLivedata}
and then in our LifecycleOwner class we can update value of liveData everywhe when we need, and of course we can react on changes from registered observer.
#EpicPandaForce is right about your setter, it's for the MutableLiveData itself and not the value it's holding. So your LiveData should be a val, no need for it to be a var, and the framework should do the right thing as long as you set a LifecycleOwner on the binding. You could add another Observer to your LiveData in place of a custom setter to add your custom logic.
I want to understand the difference between MutableLiveData vs ObservableList in Android ViewModel.
val questions: MutableLiveData<List<Question>> = MutableLiveData()
val options: ObservableList<Option> = ObservableArrayList()
The main difference here is that ObservableList is designed for DataBinding while MutableLiveData for data change observation that is made from Activity or Fragment, which means that MutableLiveData takes into consideration a LifeCycle of a component and will not call it if isn't in active state.
You can use ObservableList from your code of course, but, for example, it will not hold last passed data, unlike LiveData.
Question : Can I implement android app with MVVM without using Databinding.
Problem I am trying to solve is pretty simple:
read a list of items from backend API and show them in a Recylerview.
How I am implementing:
In the View - I have Activity and RecyclerViewAdapter
Model : ApiResponse and data models
network - retrofit API service, RxJava2
for ViewModel part - I have a ViewModel class(that doesn't derive from anything) that basically calls Retrofit Service and gets data using RxJava calls.
ViewModel has calls such as :
void getItems();
void addItemData();
void removeItem();
which call service with RXJava2 as
ObServable<ApiResponse> getItems();
ObServable<ApiResponse> addItemData();
ObServable<ApiResponse> removeItem();
View instantiates ViewModel object.
ViewModel gets the instance of Adapter object during creation.
In the View, clicking a button calls a ClickHandler in the Activity which calls a ViewModel#getItems() method. Since ViewModel has link to Adapter, the viewModel updates the items in the adapter so that RecyclerView is automatically updated.
I am not sure if this is right approach for MVVM.
Databinding seems a bit like spaghetti to me.
Again, can we implement MVVM in android without DataBinding ?
Is the approach OK?
Yes! You can. But i think your approach can be better.
Remember that the view model must not have a reference to your view.
ViewModel expose observables, and in your view, you should observe those observables and react over changes.
You can have something like this:
Note: This example is with Kotlin and LiveData because, why not? But you can take this and use it with Java & Rx
ItemsViewModel : ViewModel() {
private val items = MutableLiveData<List<Items>>()
fun getAllItems() : LiveData<List<Items>> {
return items
}
//..
}
ItemsActivity : Activity() {
private var itemsAdapter: ItemsAdapter? = null
private var viewModel: ItemsViewModel? = null
override fun onCreate(savedInstance: Bundle) {
// ...
// Create your Adapter
itemsAdapter = ItemsAdapter()
recyclerView.adapter = itemsAdapter
// Create and observe your view model
viewModel = ViewModelProviders.of(this).get(ItemsViewModel::class.java)
viewModel.getAllItems().observe(this, Observer {
it?.let {
adapter?.datasource = it
}
}
In this case, the view observes view model, and notify the adapter. Then in your adapter, you do the bind as usual, without databinding.
Definitely possible, it's totally up to you how you interpret the "binding" part of MVVM. In our team, we use MVVM with RxJava instead of Android Data Binding.
Your ViewModel has an interface with outputs and inputs like this:
interface TasksViewModel {
// inputs
Observer<Task> taskAddedTrigger();
Observer<Task> taskClickedTrigger();
Observer<Task> taskCompletedTrigger();
// outputs
Observable<Boolean> isLoading();
Observable<List<Task>> tasks();
}
Your ViewModel then just uses RxJava to map inputs to outputs in a very functional style.
You Fragment supplies Inputs to the ViewModel whenever User input is received. It subscribes to Outputs and updates the user interface accordingly when the ViewModel's Output changes.
Here is a blog post which covers the topic in detail (Disclaimer: I wrote it)
The distinguishing characteristic of MVVM is that the ViewModel is not directly coupled to a View (indeed, you could bind your ViewModel to different layouts). This also has implications on the ease of unit testing. By having a reference to the Adapter, it is technically more like MVC. You don't have to use databinding, but for true MVVM, I think you would need another Observer Pattern mechanism for the View to be notified of changes so that it could pull the data it needs.
Your saying Since ViewModel has a link to Adapter and that is the problem because ViewModel should not have reference to view and In your adapter, you have views so by doing this your not following MVVM at all!!
You can still use MVVM without data binding but you need some way to notify the view about data changes, It can be LiveData (preferred way), Java Observable, Rx or even a custom implementation. The view will get notified about the changes and updates itself, in your case, view will update the adapter.
see my answer here for an example Are actions allowed in MVVM? Android
I think you should use data binding to notify the data changed from network or database, your viewmodel should expose methods for requiring or updating data, when the data arrived you can do some operation on your data, and post them to your container(activity or fragment), in there you can update your RecyclerView and its adapter