I am experiencing the new architectural components from google namely LiveData and ViewModel. By combining both APIs we can have the view model listening and updating the data in a reactive way.
My question is how data should be represented in a ViewModel more specifically, which way is better between having data exploded as separate fields as the following
class UserViewMode : ViewModel() {
var name = MutableLiveData<String>
vat lastName = MutableLiveData<String>
}
Or by encapsulating the data in a data holder/model class which contains both name and last name and having the view model observing this model class in a LiveData observer as the following
class UserViewMode : ViewModel() {
var user = MutableLiveData<User>
}
Thanks in advance.
Second approach is better.
class UserViewModel : ViewModel() {
var user = MutableLiveData<User>
}
Because encapsulating data inside a model (User) object is better than keeping all the data separate.
Main advantages I can see are
1. Clean code and architecture.
2. Model object can be used/passed between other components like GSON (to parse data into model object), Room database.
If there are multiple User objects and they need to be presented in a RecyclerView then you have to encapsulate the data into one object. Otherwise the code becomes a mess with multiple lists.
I think it depends only on how you get the data and you should think of are the fields changing separately or not?
For ex, if you are getting it like:
User, than there is no need to separate it into fields
On the other hand if you are changing the name separately from lastName you should have two fields for that.
Do it how it makes sense.
Related
I'm a beginner with Android development, and I'm trying to make a practice app using MVVM. It's my first experience with this architecture, so I'm pretty unsure of everything that I do so far.
I'm trying to programmatically set the CollapsingToolBar's scroll flags. When the RecyclerView's list is empty, I'd like to disable the scrolling effect. When it has items, I reenable it.
In my ViewModel, I have:
#HiltViewModel
class MealsViewModel #Inject constructor(
private val mealDao : MealDao
) : ViewModel() {
\\ depending on the calendar day, grab the list of meals from the Room Database
private val currentDay: MutableLiveData<Date> = MutableLiveData(Date())
val meals = Transformations.switchMap(currentDay){ date -> mealDao.getMeals(date).asLiveData() }
\\ this is observed in the fragment; if meals is empty from database, return this int (scroll flag)
val enableOrDisableScroll = meals.map {
if (it.isNullOrEmpty()) AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL else
AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP or
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or
AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
}
In the Fragment, enableOrDisableScroll is observed like so:
viewModel.enableOrDisableScroll.observe(viewLifecycleOwner) {
val params: AppBarLayout.LayoutParams = collapsingToolBar.layoutParams
as AppBarLayout.LayoutParams
\\ it is the returned value from the ViewModel observation
params.scrollFlags = it
collapsingToolBar.layoutParams = params
}
(This solution is modified from https://stackoverflow.com/a/32699543/11813571)
Is this proper MVVM design? Is all the "business logic" properly separated from the view (Fragment)? I may be having trouble understanding the term itself. In Android programming, is business logic anything that would decide how a view is created and UI updated? How could my MVVM approach be improved? It works as intended, but I don't know if it's optimal.
Thank you!
This is how Basically MVVM pattern looks like .It is quite simple , the View (Fragment , Activity , Custom Views ) should only communicate with the ViewModel , it shouldn't call any other classes . The ViewModel acts as a middle-men for your data and the views . So the data should be passed from your datasource (which looking at your code seems to be room database ) to the viewModel and from there to the View .For passing the data from ViewModel to the views, one should make use of LiveData /StateFlow /SharedFLow . Now , the data in the viewModel should not come directly from DataSource if you want to follow MVVM strictly . There has to be another layer called Repository between your datasource and viewModel as shown in the diagram . In your code , in ViewModel you have directly called datasource in the form of (dao) , that is not permissible . The repository acts as a middle-men for data to flow between the datasource and the ViewModel . This is how MVVM works .
Answers to your Questions :
1.Is this proper MVVM design :
The ViewModel -Fragment logic has been handled well but you have directly called dao in your viewModel which is faulty . You have to create a Repository , where you have to call Dao and have to pass that repository in the ViewModel .
What is business logic ?
The answer will partly be a matter of the project's complexity and of developer's taste . But make sure that it does not have any View related code in it and it should flow via ViewModel . The business logic should be reusable and should be kept away from other classes
Preamble
In trying to get my head around the Kotlin classes to implement Android's ViewModel (and MVVM pattern) as used with Fragments and Activities, it is not clear to me of the trade-offs among the various complex classes especially how they have inherited implicit operations and visible methods (e.g., from the observer objects, managed scope, etc.) versus the old O-O approach of passing list-items and lists between activities in an intent as a bundle or reference, etc.
To illustrate my learning dilemma, I am implementing a crunchy cookie and and a jar to contain the cookies. The cookies can be created, consumed and viewed inside the cookie jar.
Android code tends to be vague on details of classes and the tutorials use deprecated versions, so it is difficult to follow best-practices with the latest version of the Android Architecture Component libraries.
Pseudo Kotlin code:
data class CrunchieCookie : {
var flavor: String?
var calories: String?
var photo: ImageView?
}
class CrunchieCookieViewModel : ViewModel() {
val _crunchieCookie: CrunchieCookie?
val crunchieCookie: CrunchieCookie = _crunchieCookie
}
class CookieJarListViewModel: ViewModel() {
val _cookieJar: MutableLiveData<CrunchieCookie>?
val cookieJar: LiveData<CrunchieCookie> = _cookieJar
}
Purpose
I am expecting to create, update and destroy crunchie-cookies
I am expecting to put crunchie-cookies in a cookie-jar (and take them out)
I am expecting to list all the crunchie-cookies in the cookie-jar in a scrolling ListView
I am expecting to click on a crunchie-cooking in the cookie-jar to open an detail view of the cookie
Finally, storing the cookie-jar in a remote DB, so planning for the local/remote data-source in the future
So, to my way of thinking, the cookie viewmodel will be used in CRUD operations and reused in the detail view from the list model.
MAKING #Tenfour04 's COMMENT AN ANSWER.
Your ViewModel should have a LiveData<List>. The Fragment containing the ListView should observe the LiveData for changes and pass the List along to the ListView when the LiveData value changes. If you're actually just modifying the contents of a MutableList, then you need to set the value of the MutableLiveData to that same list to inform it that there's a change it needs to notify observers about. – Tenfour04 Sep 9 at 0:02
Given this scenario:
Model
data class User(var id: int, var name: String)
View Model
val Users: LiveData<List<User>>
val SelectedUser: LiveData<User>
fun changeSelectedUserName(){SelectedUser.Name = "foo"}
UI
<android:TextView Text="#{viewmodel.SelectedUser.name}"/>
<android:Button Text="Change!" onClik="#{() -> viewmodel.changeSelectedUserName()}"/>
When user clicks 'Change!' button the textview won't change because the 'name' field is not LiveData.
Questions
Should data class re-expose its fields as LiveData too?
If so, what will happen to regular fields? Are they replaced or keeped with another naming convention?
What is the correct naming convention if I'm using retrofit? So I can keep both the interface methods and LiveData working with the less amount of code?
Your issue is that you are modifying the User object properties directly, rather than update the LiveData.
For this to work, you have to do one of the following:
Make User extend BaseObservable, and invoke notifyPropertyChanged(BR.name) when the name property changes, and remove the LiveData<User>.
Make User extend BaseObservable, and put #Bindable annotation on the property getters, and remove LiveData<User>.
Make User properties val, and to make a modification, create a new User instance with the changed properties, and set it as value of the MutableLiveData<User>.
Ditch SelectedUser entirely, and replace it with two ObservableFields, one for selectedId: ObservableInt, and one for selectedName: ObservableField<String>. Now you can modify the values in place and also create bindings against it from databinding.
Remove databinding and use viewbinding instead, now you don't have to worry about notifying the databinding framework of property changes. 😏
Should data class re-expose its fields as LiveData too?
No
I am having a DB populated with weather data, in both imperial and metric units. Now, I have made two different classes which act as a model to get data from the DB. CurrentWeatherMetric has only metric columns, and CurrentWeatherImperial has only imperial fields.
Since I am using MVVM architecture pattern, the ViewModel provides me this data, by calling a function in ViewModel getData(Unit.METRIC) where Unit is an enum class I've made to distinguish the data.
The problem arises here.
My viewModel looks like:
class WeatherViewModel(
private val weatherRepository: WeatherRepositoryImpl
) : ViewModel() {
lateinit var currentWeather: LiveData<CurrentWeather>
lateinit var forecastWeather: LiveData<List<ForecastWeather>>
fun getValuesOfUnit(unit: Unit) {
currentWeather = when (unit) {
Unit.IMPERIAL->weatherRepository.getCurrentWeatherImperial()
Unit.METRIC->weatherRepository.getCurrentWeatherMetric()
}
getWeather()
}
private fun getWeather() {
viewModelScope.launch {
try {
weatherRepository.getWeather()
} catch (e: IOException) {
}
}
}
}
As you can see, lateinit var currentWeather: LiveData<CurrentWeather>,
I had to make another class which store the data of the query with units. I made this so that I could easily implement databinding with it. But I feel this is a really wrong way to do things and hence I have asked this question. How can I get rid of that lateinit variable and implement databinding to adapt to any of the data.
In my current data binding layout, I have data field as:
<data>
<variable
name="viewModel"
type="com.mythio.weather.ui.WeatherViewModel" />
</data>
And I bind to views by:
app:someattribute="#{viewModel.currentWeather.temperature}"
If the question title makes a little sense about what I am asking, or seems misleading, please feel free to edit this to make it a better question.
When using MVVM architecture pattern, Google's recommended way is to make ViewModel that handles connection between your data and view, so it contains UI logic as well as some portion of business logic bound to your UI.
Moreover, implementation of ViewModel in recommended way helps you handle UI lifecycle (Activity/Fragments) in better and hassle-free way.
When using data-binding with MVVM, it's good practice to bind ViewModel directly to xml so that, when data changes you can directly reflect it to UI using LiveData without wiring it manually.
Hence, LiveData can be used as Data-Value holder as it's also Lifecycle-aware component.
On the other hand, Repositories are good way to manage business logic and providing "single source of truth" for data driving through app. So, all data sources like local-db, API calls, shared-preferences etc. should be accessed via repository.
So, yes!! Things you're doing are good & you're on the right track while following MVVM Architecture Pattern.
Note: You can refer here for more info and some improvements in your code.
Looking to the code of some Google's demo app (like sunflower or Google io 2018 app) and I've noticed that for the viemodels' backing properties they use a separate instance of the same type with a custom getter; like this:
private val _userData: MutableLiveData<User>
val userData: LiveData<User>
get() = _userData
but why do they do that? Isn't better to directly make the _userData accessible?
Could it be because while _userData is a MutableLiveData they don't want the observer to be able to change the value?
userData which is exposed to the Activity or Fragment must be immutable since the view only needs to observe to the LiveData. So, we need to make the actual _userData returns a LiveData.
One way is using the Kotlin coding convention and create two variables, _userData and userData, one is mutable and another one is not:
If a class has two properties which are conceptually the same but one
is part of a public API and another is an implementation detail, use
an underscore as the prefix for the name of the private property.