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.
Related
I'm posting my room query result using the below code
_data.postValue(databaseImpl.findRepoById(id).value)
But it seems the returned object is empty. I'm guessing we need to observe databaseImpl.findRepoById(id), not access it directly but since it's in a viewmodel I don't have a lifecycleOwner to assign to the room query livedata.
databaseImpl.observe(this, Observer { //!!this is modelview and not activity
_data.postValue()
})
What is the right way to use Room query and update a mediator live data?
You could try use coroutines to query your data so you can have something like this for your DAO:
#Query("SELECT * FROM users")
fun findRepoById(id): Flow<User>
In your DatabaseImpl class return have it as:
fun findRepoById(id): Flow<User> = userDao.findRepoById(id)
Then in your ViewModel you could do this:
viewModelScope.launch {
databaseImpl.findRepoById(id).map { _data.postValue(it) }
}
Have a look at this article Room and Coroutines by Florina Muntenescu
on how you could set up coroutines and use it with room.
emit accepts the data class whereas emitSource accepts LiveData<T> ( T -> data ). Considering the following example :- I have two type of calls :-
suspend fun getData(): Data // returns directly data
and the other one ;
suspend fun getData(): LiveData<Data> // returns live data instead
For the first case i can use:-
liveData {
emit(LOADING)
emit(getData())
}
My question : Using the above method would solve my problem , WHY do we need emitSource(liveData) anyway ?
Any good use-case for using the emitSource method would make it clear !
As you mentioned, I don't think it solves anything in your stated problem, but I usually use it like this:
If I want to show cached data to the user from the db while I get fresh data from remote, with only emit it would look something like this:
liveData{
emit(db.getData())
val latest = webService.getLatestData()
db.insert(latest)
emit(db.getData())
}
But with emitSource it looks like this:
liveData{
emitSource(db.getData())
val latest = webService.getLatestData()
db.insert(latest)
}
Don't need to call emit again since the liveData already have a source.
From what I understand emit(someValue) is similar to myData.value = someValue whereas emitSource(someLiveValue) is similar to myData = someLiveValue. This means that you can use emit whenever you want to set a value once, but if you want to connect your live data to another live data value you use emit source. An example would be emitting live data from a call to room (using emitSource(someLiveData)) then performing a network query and emitting an error (using emit(someError)).
I found a real use-case which depicts the use of emitSource over emit which I have used many times in production now. :D The use-case:
Suppose u have some user data (User which has some fields like userId, userName ) returned by some ApiService.
The User Model:
data class User(var userId: String, var userName: String)
The userName is required by the view/activity to paint the UI. And the userId is used to make another API call which returns the UserData like profileImage , emailId.
The UserData Model:
data class UserData(var profileImage: String, var emailId: String)
This can be achieved internally using emitSource by wiring the two liveData in the ViewModel like:
User liveData -
val userLiveData: LiveData<User> = liveData {
emit(service.getUser())
}
UserData liveData -
val userDataLiveData: LiveData<UserData> = liveData {
emitSource(userLiveData.switchMap {
liveData {
emit(service.getUserData(it.userId))
}
})
}
So, in the activity / view one can ONLY call getUser() and the getUserData(userId) will be automatically triggered internally via switchMap.
You need not manually call the getUserData(id) by passing the id.
This is a simple example, imagine there is a chain of dependent-tasks which needs to be executed one after the other, each of which is observed in the activity. emitSource comes in handy
With emitSource() you can not only emit a single value, but attach your LiveData to another LiveData and start emitting from it. Anyway, each emit() or emitSource() call will remove the previously added source.
var someData = liveData {
val cachedData = dataRepository.getCachedData()
emit(cachedData)
val actualData = dataRepository.getData()
emitSource(actualData)
}
The activity that’s observing the someData object, will quickly receive the cached data on the device and update the UI. Then, the LiveData itself will take care of making the network request and replace the cached data with a new live stream of data, that will eventually trigger the Activity observer and update the UI with the updated info.
Source: Exploring new Coroutines and Lifecycle Architectural Components integration on Android
I will like share a example where we use "emit" and "emitsource" both to communicate from UI -> View Model -> Repository
Repository layer we use emit to send the values downstream :
suspend fun fetchNews(): Flow<Result<List<Article>>> {
val queryPath = QueryPath("tata", apikey = AppConstant.API_KEY)
return flow {
emit(
Result.success(
openNewsAPI.getResponse(
"everything",
queryPath.searchTitle,
queryPath.page,
queryPath.apikey
).articles
)
)
}.catch { exception ->
emit(Result.failure(RuntimeException(exception.message)));
}
}
ViewModel layer we use emitsource to pass the live data object to UI for subscriptions
val loader = MutableLiveData<Boolean>()
val newsListLiveData = liveData<Result<List<Article>>> {
loader.postValue(true)
emitSource(newRepo.fetchNews()
.onEach {
loader.postValue(false)
}
.asLiveData())
}
UI Layer - we observe the live data emitted by emitsource
viewModel.newsListLiveData.observe(viewLifecycleOwner, { result ->
val listArticle = result.getOrNull()
if (result.isSuccess && listArticle != null) {
setupList(binding.list, listArticle)
} else {
Toast.makeText(
appContext,
result.exceptionOrNull()?.message + "Error",
Toast.LENGTH_LONG
).show()
}
})
We convert Flow observable to LiveData in viewModel
I am following the google android project "guess it" through udacity where they introduce livedata and mutablelivedata. We've got to the point where we're creating a livedata equivalent to a mutablelivedata object and creating a get() backingproperty for the livedata to the mutablelivedata instance. We make all changes to the data in the viewModel using the mutablelivedata instance. Our UI Fragment sets observers on the viewModel's livedata objects, NOT the mutablelivedata objects.
Dispite the observer being on the livedata instance and not the mutablelivedata instance, when the mutablelivedata object is updated, the livedata observer code is being triggered. I love that it works like this but I don't exactly understand how it works. Can anyone explain this to me?
In ViewModel
val _word = MutableLiveData<String>()
val word : LiveData<String>
get() = _word
Edit in ViewModel
private fun nextWord() {
//Select and remove a word from the list
if (wordList.isEmpty()) {
//gameFinished()
} else {
_word.value = wordList.removeAt(0)
}
}
In UI Fragment
viewModel.word.observe(this, Observer{newWord ->
binding.wordText.text = newWord
})
Our UI Fragment sets observers on the viewModel's livedata objects, NOT the mutablelivedata objects.
It is the same object. _word and word both point to the MutableLiveData. However, from a data type standpoint, word is defined as a LiveData, to hide the implementation details from the fragment.
So, the observers are being placed on the same object that is getting the value updates.
Personally, I prefer to write this code as:
private val _word = MutableLiveData<String>()
val word : LiveData<String> = _word
...as IMHO that is easier to read and has the same effect.
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
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.