I need some clarification on how LiveData works with Android's Architecture components like Room.
Let's say I use this way of getting live data:
Dao:
#Query("SELECT * FROM check_table")
LiveData<List<DataItem>> getAllItems();
Repository Constructor:
private DataRepository(Application application) {
DataDatabase database = DataDatabase.getInstance(application);
dataDao = database.dataDao();
dataItems = dataDao.getAllData();
}
ViewModel Constructor:
public DataViewModel(#NonNull Application application) {
super(application);
repository = DataRepository.getInstance(application);
dataItems = repository.getDataItems();
}
Getter:
public LiveData<List<DataItem>> getDataItems() {
return dataItems;
}
Is the LiveData in ViewModel being updated everytime even when there are no active listeners?
I'm asking because I want to use the same data in pretty much all my fragments, and I want to know if the data has to be queried every time I add the listener to the data in one of my fragments or the LiveData object is updated in ViewModel and when i switch fragments and add listener to that LiveData in there, it just gets cached LiveData instead of querying for it once again
Related
I am using Room and I have written the Dao class as follows.
Dao
#Dao
interface ProjectDao {
#Query("SELECT * FROM project")
fun getAllProjects(): Flow<List<Project>>
...etc
}
and this Flow is converted to LiveData through asLiveData() in ViewModel and used as follows.
ViewModel
#HiltViewModel
class MainViewModel #Inject constructor(
private val projectRepo: ProjectRepository
) : ViewModel() {
val allProjects = projectRepo.allProjects.asLiveData()
...
}
Activity
mainViewModel.allProjects.observe(this) { projects ->
adapter.submitList(projects)
...
}
When data change occurs, RecyclerView is automatically updated by the Observer. This is a normal example I know.
However, in my project data in Flow, what is the most correct way to get the data of the position selected from the list?
I have already written code that returns a value from data that has been converted to LiveData, but I think there may be better code than this solution.
private fun getProject(position: Int): Project {
return mainViewModel.allProjects.value[position]
}
Please give me suggestion
Room has in built support of flow.
#Dao
interface ProjectDao {
#Query("SELECT * FROM project")
fun getAllProjects(): Flow<List<Project>>
//lets say you are saving the project from any place one by one.
#Insert()
fun saveProject(project :Project)
}
if you call saveProject(project) from any place, your ui will be updated automatically. you don't have to make any unnecessary call to update your ui. the moment there is any change in project list, flow will update the ui with new dataset.
to get the data of particular position, you can get it from adapter list. no need to make a room call.
I saw all of the following scenarios in different example projects from Google's Codelabs and other sources and do not fully understand where the values from the LiveData object are retrieved from.
Scenario 1 - Current Understanding:
According to https://developer.android.com/.../viewmodel one reason to use a ViewModel is to store/cache UI related data that I want to re-use after the corresponding UI has been rebuild after a configuration change.
Given the following simplified ViewModel and Repository: After updateName() is called the first time, the LiveData object of _currentName contains a String. If the UI is then rebuild after a screen rotation, the view that needs to display the current name requests it by observing currentName which in turn returns the value of the LiveData object that is contained in the field of the _currentName property. Am I correct?
ViewModel
class NamesViewModel(): ViewModel() {
private val respository = NamesRepository()
private val _currentName: MutableLivedata<String?> = MutableLiveData(null)
val currentName: LiveData<String?> get() = this._currentName
...
// Called as UI event listener.
fun updateName() {
this._currentName.value = this.repository.updateName()
}
}
Repository
class NamesRepository() {
fun updateName(): String {
val nextName: String
...
return nextName
}
}
Scenario 2:
What happens if the UI is rebuild after a screen rotation in the following case? _currentName in the ViewModel 'observes' currentName in the repository, but it still is a property and therefore stores its own LiveData object in its field. When the view then requests currentName from the ViewModel, the value is retrieved from the LiveData object that is contained in the field of the _currentName property in the ViewModel. Is this correct?
ViewModel
class NamesViewModel(): ViewModel() {
private val respository = NamesRepository()
private val _currentName: LiveData<String?> = this.repository.currentName
val currentName: LiveData<String?> get() = this._currentName
...
// Called as UI event listener.
fun updateName() {
this.repository.updateName()
}
}
Repository
class NamesRepository() {
private val _currentName: MutableLivedata<String?> = MutableLiveData(null)
val currentName: LiveData<String?> get() = this._currentName
fun updateName() {
val nextName: String
...
this._currentName.value = nextName
}
}
Scenario 3:
In the following scenario, if the UI is rebuild and a view requests currentNam from the ViewModel, where is the requested value stored? My current understanding is, that currentName falls back to the field of the property _currentName in the repository. Isn't that against the idea of the ViewModel to store relevant UI data to be re-used after a configuration change? In the case below, it might be no problem to retrieve the value from the repository instead of the viewModel, but what if the repository itself retrieves the value directly from a LiveData object that comes from a Room database? Wouldn't a database access take place every time a view requests _currentName from the viewModel?
I hope somebody can clarify the situation more, in order to understand how to cache UI related data in the viewModel the correct way (or at least to understand what are the incorrect ways).
ViewModel
class NamesViewModel(): ViewModel() {
private val respository = NamesRepository()
val currentName: LiveData<String?> get() = this.repository.currentName
...
// Called as UI event listener.
fun updateName() {
this.repository.updateName()
}
}
Repository
class NamesRepository() {
private val _currentName: MutableLivedata<String?> = MutableLiveData(null)
val currentName: LiveData<String?> get() = this._currentName
fun updateName() {
val nextName: String
...
this._currentName.value = nextName
}
}
To answer your question scenario#1 is correct usage of LiveData.
Firstly, LiveData is not responsible for caching, it is just LifeCycleAware Observable, given that caching is done at ViewModel, when your activity recreates due to any configuration changes, android will try to retrieve the existing instance of ViewModel, if found then it's state and data are retained as is else it will create a new instance of ViewModel.
Second, using LiveData in repository is a bad idea at many levels, repository instances are held by ViewModel and LiveData are part of Android Framework which makes repositories rely on Android Framework thus creating problems in Unit Testing. Always use LiveData only in ViewModels.
I am using live data with room database and my activity observes live data provided from room database.
#Query("SELECT * FROM BUS WHERE BUS_CATEGORY = :busCategory")
LiveData<List<Bus>> getLiveBuses( String busCategory);
ViewModels gets LiveData via Dao(Data Access Object) and activity observes this live data.
Now it works fine. But when busCategory changes i can't modify this live data to get buses for newly selected busCategory.
So how can i observe this same liveData where query parameters is changeable?
I suggest you to to use viewModel. I did the query and observe changes using MutableLiveData.
First step
val mutableBusCategory: MutableLiveData<String> = MutableLiveData()
Setter for mutablelivedata
fun searchByCategory(param: String) {
mutableBusCategory.value = param
}
observable to observe the change
val busObservable: LiveData<Bus> = Transformations.switchMap(mutableBusCategory) { param->
repository.getLiveBuses(param)
}
and final step to observe the live data
busObservable.observe(this, Observer {
//your logic for list})
and to trigger mutablelivedata
searchByCategory(//categoryName)
I don't think this is a reasonable expectation. It would make more sense to fire off a new query and subscribe to that.
I am currently applying Room + ViewModel + LiveData to my project.
In my app, there is "obviously" observe data that is needed, but not all.
The code below is example code for category data. In my situation, category data does not change and always maintains the same value state (13 categories and content does not change). Categories are data that is loaded from the Database through the CategoryItemDao class.
Does category data need to be wrapped with livedata?
Or is there a reason enough to use LiveData in addition to its observerable feature?
I've read the guide to LiveData several times, but I do not understand the exact concept.
CategoryItemDao
#Dao
interface CategoryItemDao {
#Query("SELECT * FROM CategoryItem")
fun getAllCategoryItems(): LiveData<MutableList<CategoryItem>>
}
CategoryRepository
class CategoryRepository(application: Application) {
private val categoryItemDao: CategoryItemDao
private val allCategories: LiveData<MutableList<CategoryItem>>
init {
val db = AppDatabase.getDatabase(application)
categoryItemDao = db.categoryItemDao()
allCategories = categoryItemDao.getAllCategoryItems()
}
fun getAllCategories() = allCategories
}
CategoryViewModel
class CategoryViewModel(application: Application) : AndroidViewModel(application) {
private val repository = CategoryRepository(application)
private val allCategories: LiveData<MutableList<CategoryItem>>
init {
allCategories = repository.getAllCategories()
}
fun getAllCategories() = allCategories
}
This is fine, but you can make a few changes:
Change LiveData<MutableList<CategoryItem>> to LiveData<List<CategoryItem>>. Don't use a MutableList unless you really have to. In your case, List would work fine.
In your CategoryRepository instead of fetching in init, do it during the getAllCategories() call. So change your code like this: fun getAllCategories() = categoryItemDao.getAllCategoryItems()
Similarly do the same in CategoryViewModel as well. Change you code to: fun getAllCategories() = repository.getAllCategories()
A common misconception is to use LiveData only when the data changes. But that's not true. Your 13 categories may not change, but that's in a database. So if you were to accomplish this without a LiveData you have to query the DB and populate the view in the main thread, or you need to wrap this around in a background thread. But if you do this via LiveData, you get the Asynchronous Reactive way of coding for free. Whenever possible, try to make your view observe a LiveData.
i'm studying the Android Architecture Components and i'm a little bit confused. In the sample they use a repository and state that changes within the datasource of the repository are observed by the ViewModels. I don't understand how the changes within the datasource are pushed to the ViewModels, as i cannot see any code within the ViewModels that subscribes them to the repository. Analogously, the fragments observe the ViewModel's LiveData, but they actually subscribe to the LiveData:
// Observe product data
model.getObservableProduct().observe(this, new Observer<ProductEntity>() {
#Override
public void onChanged(#Nullable ProductEntity productEntity) {
model.setProduct(productEntity);
}
});
I cannot see any kind of subscribing within the ViewModels to observe the Repository. Am i missing something?
ViewModel is not observing any data it just returning LiveData object of Product so you can observe the data in ProductFragment
This is how LiveData is observed in ProductFragment, In which the getObservableProduct() method called on ViewModel which returns LiveData<ProductEntity>
// Observe product data
model.getObservableProduct().observe(this, new Observer<ProductEntity>() {
#Override
public void onChanged(#Nullable ProductEntity productEntity) {
model.setProduct(productEntity);
}
});
This method in ViewModel called from ProductFragment
public LiveData<ProductEntity> getObservableProduct() {
return mObservableProduct;
}
In constructor of that ProductViewModel the member variable mObservableProduct is initialized as follows, Which get LiveData<ProductEntity> from Repository
private final LiveData<ProductEntity> mObservableProduct;
mObservableProduct = repository.loadProduct(mProductId);
If you dig deeper, in DataRepository, LiveData<ProductEntity> is fetched from DAO
public LiveData<ProductEntity> loadProduct(final int productId) {
return mDatabase.productDao().loadProduct(productId);
}
And in DAO its nothing but SQL query which returns the LiveData<ProductEntity> which is implemented by RoomCompiler. As you can see DAO using #Dao annotation which used by annotation processor and Write Dao implementation in ProductDao_Impl class.
#Query("select * from products where id = :productId")
LiveData<ProductEntity> loadProduct(int productId);
So In a nutshell, ViewModel holding References to all the data required by Activity or Fragment. Data get initialized in ViewModel and it can survive Activity configuration changes. Thus we are storing its references in ViewModel.
In our case LiveData is just wrapper around our object which is returned by DAO implementation as an Observable object. So we can observe this in any Activity or Fragment. So as soon as the data is changed at Data Source, it called postValue() method on LiveData and we get the callback
Flow of LiveData
DAO -> Repository -> ViewModel -> Fragment