I have a Simple DAO including CRUD function
FeedEntryDAO.java
#Dao
public interface FeedEntryDAO {
#Query("SELECT * FROM feedEntrys")
LiveData<List<FeedEntry>> getAll();
#Query("SELECT * FROM feedEntrys WHERE uid = :uid LIMIT 1")
LiveData<FeedEntry> findByUid(int uid);
#Insert
void insertAll(FeedEntry... feedEntries);
#Delete
void delete(FeedEntry feedEntry);
#Update
int update(FeedEntry feedEntry);
}
For the select , it is okay to return the LiveData type.
Inside the Activity the code is pretty for the selection
viewModel.getFeedEntrys().observe(this,entries -> {...});
However, when I try to insert, update , delete the data. The code seems a little bit ugly and also create a asynctask every time.
new AsyncTask<FeedEntry, Void, Void>() {
#Override
protected Void doInBackground(FeedEntry... feedEntries) {
viewModel.update(feedEntries[0]);
return null;
}
}.execute(feedEntry);
I have 2 question about it:
Can I use LiveData to wrap the delete, insert , update function ?
Better way to maintain such asynctask class for delete, insert , update?
Appreciate any suggestions and advices. Thank you.
Can i use LiveData to wrap Delete, Insert, Update calls?
No, you can't. I wrote an answer to the issue. The reason is, that LiveData is used to notify for changes. Insert, Update, Delete won't trigger a change. It will return the deleted rows, the inserted ID or the affected rows. Even if it looks horrible it makes sense not having LiveData wrapped around your stuff. Anyway, it would make sense to have something like Single around the calls to let the operation triggered and operated on a RX-Java operation.
If you want to trigger those calls, you observe on a selection query which notify your LiveData onec you have updated, inserted or deleted some/any data.
Better way to maintain such asynctask class for delete, insert , update?
After looking at your example it looks like that you misuse the (Model/View/)ViewModel-Pattern. You should never access your repository in your view. I'm not sure if you'r doing this because its not visible in your sample. Anyway, after observing your LiveData and getting a result, there's no need to wrap the updating of data inside your viewModel in an AsyncTask. That means, that you should alway take care of
a) view <-> viewmodel <-> repository and not view <-> repository and view <-> viewmodel
and
b) don't try to use threads which are not needed. You observe LiveData on a Background Thread (#WorkerThread) by default (if not annotated with #MainThread) and get the value in the ui-thread (#MainThread).
Concerning question 2:
For Kotlin users there is now a really nice way to achieve this,
because since Room 2.1 there is direct support for coroutines. A neat example is given here.
You can use a "suspend function" directly in the DAO, which takes care that nothing is executed on the main thread:
#Dao
interface BarDao {
#Query("SELECT * FROM bar WHERE groupId = 2")
fun getAllBars(): LiveData<MutableList<Bar>>
#Query( "SELECT * FROM bar WHERE groupId = 0 LIMIT 1")
fun getRecentBar(): LiveData<Bar>
#Insert
suspend fun insert(bar: Bar)
#Update
suspend fun update(bar: Bar)
#Delete
suspend fun delete(bar: Bar)
}
then in your viewModel you would just:
fun insert(bar: Bar) = viewModelScope.launch {
barDao.insert(bar)
}
fun update(bar: Bar) = viewModelScope.launch {
barDao.update(bar)
}
fun delete(bar: Bar)= viewModelScope.launch {
barDao.delete(bar)
}
For the second question, there is another neater alternative to AsyncTask; which is using java Executor, the good news is that you can use a single instance of Executor instead of multiple instances of the AsyncTask for all CRUD operations.
Demo Example
public class Repository {
private static Repository instance;
private MyDatabase mDatabase;
private Executor mExecutor = Executors.newSingleThreadExecutor();
private Repository(Application application) {
mDatabase = MyDatabase.getInstance(application.getApplicationContext());
}
public static Repository getInstance(Application application) {
if (instance == null) {
instance = new Repository(application);
}
return instance;
}
public void insert(final MyModel model) {
mExecutor.execute(new Runnable() {
#Override
public void run() {
mDatabase.getMyModelDAO().insert(model);
}
});
}
public void update(final MyModel model) {
mExecutor.execute(new Runnable() {
#Override
public void run() {
mDatabase.getMyModelDAO().update(model);
}
});
}
public void delete(final MyModel model) {
mExecutor.execute(new Runnable() {
#Override
public void run() {
mDatabase.getMyModelDAO().delete(model);
}
});
}
}
The supported return types for each of the supported libraries are listed here. I've included the table here for convenience.
Query type
Kotlin language features
RxJava
Guava
Jetpack Lifecycle
One-shot write
Coroutines (suspend)
Single<T>, Maybe<T>, Completable
ListenableFuture<T>
N/A
One-shot read
Coroutines (suspend)
Single<T>, Maybe<T>
ListenableFuture<T>
N/A
Observable read
Flow<T>
Flowable<T>, Publisher<T>, Observable<T>
N/A
LiveData<T>
(Web archive link)
To Address your 2nd Question:
Google has posted an Android Room Codelab here which has laid out a concise MVVM architecture for implementing Room in Android:
(source: google.com)
Here the recommendation is to have database operations handled by a public static ExecutorService within the Database class. The location of the ExecutorService class can vary, just remember the idea is in MVVM your view does not care about how data are actually CURD'ed, that's the responsibility of the ViewModel, not View.
github repository for this code lab
In short, to apply a similar idea to your code, it would be something like this:
class YourRepository {
private FeedEntryDAO mFeedEntryDAO;
YourRepository(Application application) {
YourDatabase db = YourDatabase.getDatabase(application);
mFeedEntryDAO = db.feedEntryDAO();
mAllWords = mWordDao.getAlphabetizedWords();
}
void update(FeedEntry feedEntry) {
Database.databaseExecutor.execute(() - > {
mFeedEntryDAO.update(feedEntry);
});
}
}
class YourViewModel extends ViewModel {
private YourRepository mRepository;
void update(FeedEntry feedEntry) {
mRepository.update(feedEntry)
}
}
By doing this, your View can directly call viewModel.update(feedEntries[0]).
One important thing to mention is the mFeedEntryDAO.update(feedEntry) will automatically trigger the onChanged callback of your observer on the getFeedEntrys LiveData.
This is quite handy in your case. You can read more about how the trigger happens here.
You can use #Dao annotation in abstract classes too, so:
Create an abstract #Dao BaseDao class with the abstract methods #Insert insert(entities) and with the concrete method insert(entities, callback) that do that ugly AsyncTask job, calling the abstract #Insert insert(entities) on onBackground and your callback on onPostExecute.
Make your FeedEntryDAO also abstract extend BaseDao and the #Query methods abstract.
The result usage in Kotlin is quite pretty:
database.entityDao().insert(entities) { ids ->
// success
}
To app's UI to update automatically when the data changes this, use a return value of type LiveData in your query method description. Room generates all necessary code to update the LiveData when the database is updated.
#Dao
interface MyDao {
#Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
}
Note: As of version 1.0, Room uses the list of tables accessed in the
query to decide whether to update instances of LiveData.
Related
I'm using RoomDao with kotlin coroutines and Flow. What I'm trying to do is collect one Training with all its Exercises with all Repetitions per Exercise. Exercises and Repetitions are Flows, cuz this values can be changed and I want to observe them.
The problem is that when I updating exercises, getTrainingExerciseLinksBy doesn't triggers, and I don't know, why. Here is my code in UseCase:
suspend fun getTrainingWithExercisesAndRepetitionsBy(trainingId: Long): Flow<UiTrainingWithExercisesAndRepetitions> {
/// This method returns Flow<List<TrainingExerciseLink>>
return trainingExerciseLinksRepository.getTrainingExerciseLinksBy(trainingId).flatMapConcat { trainingExerciseLinks ->
trainingExerciseLinks.map { trainingExerciseLink ->
/// This method returns Flow<List<ExerciseRepetition>>
repetitionsRepository.getExerciseRepetitionsBy(trainingExerciseLink.id).map { repetitions ->
/// do some other selects for collecting data about exercise in one training
}.flowOn(Dispatchers.IO)
}.zipFlows()
}.flowOn(Dispatchers.IO)
}
In my ViewModel I'm observing this method like this:
viewModelScope.launch {
useCase.getTrainingWithExercisesAndRepetitionsBy(trainingId)
.distinctUntilChanged()
.collect {
_exercisesListLiveData.value = it.exercises
_trainingListLiveData.value = it.trainingData
}
}
What is wrong with this code?
UPD:
In my DAO I'm using Flows for subscribing on database's updates, like this:
#Dao
abstract class TrainingExerciseLinkDao {
#Query("select * from TrainingExerciseLink where trainingId = :trainingId")
abstract fun getTrainingExerciseLinksBy(trainingId: Long): Flow<List<TrainingExerciseLink>>
}
and ExerciseRepetitionsDao:
#Dao
abstract class ExerciseRepetitionDao {
#Query("select * from ExerciseRepetitionEntity where trainingExerciseId = :trainingExerciseId")
abstract fun getExerciseRepetitionsBy(trainingExerciseId: Long): Flow<List<ExerciseRepetitionEntity>>
}
Actually I found the answer, so maybe somebody will jump in the same gap and this thread will be helpful.
The problem in my code was that I used flatMapConcat. This operator waits emits from original Flow and from flatMapped Flow at one time, so in this case it will trigger callback. To fix this, flatMapLatest should be used. You can read more about difference between this operators here.
So my code now looks like this:
suspend fun getTrainingWithExercisesAndRepetitionsBy(trainingId: Long): Flow<UiTrainingWithExercisesAndRepetitions> {
/// This method returns Flow<List<TrainingExerciseLink>>
/// Here is main change: flatMapConcat -> flatMapLatest
return trainingExerciseLinksRepository.getTrainingExerciseLinksBy(trainingId).flatMapLatest { trainingExerciseLinks ->
trainingExerciseLinks.map { trainingExerciseLink ->
/// This method returns Flow<List<ExerciseRepetition>>
repetitionsRepository.getExerciseRepetitionsBy(trainingExerciseLink.id).map { repetitions ->
/// do some other selects for collecting data about exercise in one training
}.flowOn(Dispatchers.IO)
}.zipFlows()
}.flowOn(Dispatchers.IO)
}
You are using it wrong , as when database updates your getTrainingWithExercisesAndRepetitionsBy does not know,
to get over this issue use flows in your dao like this example as Room supports Flow then
viewModelScope.launch {
viewModel.yourFunctionThatGetsDataFromRepository(trainingId)
.distinctUntilChanged()
.collect {
_exercisesListLiveData.value = it.exercises
_trainingListLiveData.value = it.trainingData
}
}
and if more you can refer this example
i'm working on project where i have to insert and delete data from room db , so basically i was using the old approach which is to implement Asynctask for background operations but since it is no longer recommended , i decided to use Rxjava instead , i tried to implement it but i'm not getting any result so far , and this is a piece of code where it shows the insertion of data
Completable.fromAction(new Action() {
#SuppressLint("CheckResult")
#Override
public void run() throws Exception {
recordingDb.insertRecording(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
And this is the deletion method
public void DeleteData(modelUidd modelUidd) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
recordingDb.delete(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
So basically i tried to use completable with the operator fromaction , i'm not sure if what i implemented is correct or not , any help would appreciated guys , thank you
The problem is that you are actually not subscribing to the observables, so nothing is happening.
To subscribe to an observable, you have to call the .subscribe() method.
I suggest that your methods defined in your DAO classes (or you "repository" classes), such as DeleteData in your example, return the Observable. Then, you can call the method in the DAO to get the Observable and subscribe to it from (ideally) a ViewModel or, if not, directly from an Activity. The moment you call the subscribe you will trigger the actual insertion or deletion, and will get a response from the onSuccess or onError defined callbacks.
For example:
public class MyViewModel extends ViewModel {
private MyRepository myRepository;
private final CompositeDisposable disposables;
#Inject
public MyViewModel(MyRepository myRepository) {
...
this.myRepository = myRepository;
disposables = new CompositeDisposable();
...
}
public void callObservableInRepository() {
disposables.add(myRepository.myObservable()
.subscribe(onSuccess -> {...} , onError -> {...}));
}
#Override
protected void onCleared() {
disposables.clear();
}
}
You can also check these two other answers for more information:
About async operations in RxJava
Using CompositeDisposable in ViewModel
Suppose there is a Dao class with the following two methods:
1)
delete(items: List<Item>): Completable
2)
insert(items: List< Item >): Single<List<Long>>
How can I chain them into a #transaction method in Dao class starting with ‘delete method’ and then returning ‘insert method’ result?
I want to have a method with a signature like this:
#Transaction
fun deleteAndInsert(): Single<List<Long> > {
...
}
I'm assuming your main goal is to have the return type of deleteAndInsert() as Single.
You can achieve that with small modifications
first by making delete() and insert() functions synchronous.
Since #Transaction only works synchronously, we need to create another function that calls both delete() and insert(). Also, annotate this function with #Transaction
Create another new function that creates a Single and calls the above function.
abstract class SampleDao{
protected abstract fun delete()
protected abstract fun insert(items: List<Item>) : List<Long>
#Transaction
protected open fun deleteAndInsertSync(items: List<Item>): List<Long>{
delete()
return insert(items)
}
fun deleteAndInsert(items:List<Item>): Single<List<Long>>{
return Single.create {
it.onSuccess(deleteAndInsertSync(items))
}
}
}
I don't think it is possible.
I once tried it and I got a compile-time error saying:
"Method annotated with #Transaction must not return deferred/async
return type io.reactivex.Single. Since transactions are thread
confined and Room cannot guarantee that all queries in the method
implementation are performed on the same thread, only synchronous
#Transaction implemented methods are allowed. If a transaction is
started and a change of thread is done and waited upon then a database
deadlock can occur if the additional thread attempts to perform a
query. This restriction prevents such situation from occurring."
Method annotated with #Transaction must not return deferred/async return type io.reactivex.Single. Since transactions are thread confined and Room cannot guarantee that all queries in the method implementation are performed on the same thread, only synchronous #Transaction implemented methods are allowed. If a transaction is started and a change of thread is done and waited upon then a database deadlock can occur if the additional thread attempts to perform a query. This restrictions prevents such situation from occurring.
When I decompile code to java, I see.
public interface CustomerDao {
#Transaction
#NotNull
List deleteAndCreate(#NotNull List var1);
#Query("DELETE FROM customer")
#NotNull
Completable deleteAll();
#Insert
#NotNull
List insertAll(#NotNull List var1);
#Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 3
)
public static final class DefaultImpls {
#Transaction
#NotNull
public static List deleteAndCreate(CustomerDao $this, #NotNull List
users)
{
Intrinsics.checkParameterIsNotNull(users, "users");
$this.deleteAll();
return $this.insertAll(users);
}
}
}
Get Ids of new Data
then delete all items where not in the new data
#Query("DELETE FROM product WHERE id NOT IN(:idsNewItems)")
#Override
public abstract void deleteOldItems(List<String> idsNewItems)
;
If you delete and insert methods are marked with annotation, they are executing in a transaction. So, basicly you don't need to mark your deleteAndInsert method with this annotation. So, than:
fun deleteAndInsert(items: List< Item >): Single<List<Long>> {
return delete().andThan(insert(items))
}
So, I recently started experimentation with coroutines, I switched from Rxjava2 to coroutines, I haven't got a grasp of it yet but still, I ran into a condition where I needed to observe my database change and update the UI corresponding to that.
RxJava used to provide me with Flowables, Completeable etc. using that I would be able to observe changes in Db.
abstract fun insert(data: SomeData): Long
#Query("SELECT * FROM somedata_table")
abstract fun getData(): Flowable<List<SomeData>>
So here now I used to subscribe to getData and always used to observe changes
Now Enter coroutines, I am using a suspended function with a deferred result to return my responses
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(data: SomeData): Long
#Query("SELECT * FROM somedata_table")
abstract fun getData(): List<SomeData>
suspend fun getAllSomeData():Deferred<List<SomeData>>{
return GlobalScope.async (context= coroutineContext){
database.myDao().getData()
}
}
Now I have no way to listen for updates, Channels in coroutines might be the right answer? but I am not sure how to use it with Room.
Use Room 2.2.0 Flows and kotlin coroutines. It's contentious but I dislike LiveData as it gives you results on the UI thread. If you have to do any data parsing you'll have to push everything back to another IO thread. It's also cleaner than using channels directly as you have to do extra openSubscription().consumeEach { .. } calls every time you want to listen to events.
Flow approach Requires the following versions:
// this version uses coroutines and flows in their non-experimental version
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2
androidx.room:room-runtime:2.2.0
androidx.room:room-compiler:2.2.0
Dao:
#Dao
interface MyDao {
#Query("SELECT * FROM somedata_table")
fun getData(): Flow<List<SomeData>>
}
class to do observation:
launch {
dao.getData().collect { data ->
//handle data here
}
}
if your calling class is not itself a CoroutineScope you'd have to call launch with the context of something that is. That can be GlobalScope or some other class you create. Here I'm using lifecycleScope assuming we're in an Activity class.
lifecycleScope.launch {
dao.getData().collect { data ->
//handle data here
}
}
the collect lambda will receive every udpate to the table much like an Rx onNext call.
Currently, there are two different ways of doing that. The first is to use a liveData builder function. To make this work, you need to update lifecycle to androidx.lifecycle:*:2.2.0-alpha01 or any newer version. The LiveData builder function will be used to call getData() asynchronously, and then use emit() to emit the result. Using this method, you will modify your Room getData() function to a suspend function and make the return type wrapped as a LiveData, replacing the Flowable used before.
#Query("SELECT * FROM somedata_table")
abstract suspend fun getData(): LiveData<List<SomeData>>
In your viewmodel you create a liveData which references your Room database
val someData: LiveData<SomeData> = liveData {
val data = database.myDao().getData()
emit(data)
}
The second approach is to get data from our DB as Flow. To use this, you need to update Room to androidx.room:room-*:2.2.0-alpha02 (currently the latest) or a newer version. This update enables #Query DAO methods to be of return type Flow The returned Flow will re-emit a new set of values if the observing tables in the query are invalidated. Declaring a DAO function with a Channel return type is an error
#Query("SELECT * FROM somedata_table")
abstract fun getData(): Flow<List<SomeData>?>
The return type is a flow of a nullable list. The list is nullable because Room will return null when the query has no data fetched.
To fetch data from the flow we will use the terminal operator collect{ } in our Presenter/ViewModel. It is preferable to do this in the ViewModel since it comes with a ViewModelScope. The solution given below assumes we are doing this in a ViewModel where we have a provided viewModelScope.
fun loadData(){
viewModelScope.launch {
database.myDao()
.getData()
.distinctUntilChanged().
.collect{
it?.let{ /** Update your obsevable data here **/
}
}
Gradle dependencies:
dependencies {
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-reactive', version: '1.1.1'
}
Room Dao
#Dao
interface HistoryDao : BaseDao<HistoryEntity> {
#Query("select * from History order by time desc")
fun observe(): Flowable<List<HistoryEntity>>
...
}
Interactor (browserHistoryInteractor below) (layer between dao and Fragment/Presenter)
// To get channel of List<HistoryEntity>:
import kotlinx.coroutines.reactive.openSubscription
fun observe() = historyDao.observe().openSubscription() // convert list to Coroutines channel
Presenter/Fragment/Activity (end point (in my case it is lifecycle-aware presenter))
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
private val compositeJob = Job() // somewhat equivalent "compositeDisposable" in rx
override fun onCreate() {
super.onCreate()
launch(compositeJob) { // start coroutine
val channel = browserHistoryInteractor.observe()
for (items in channel) { // waits for next list of items (suspended)
showInView { view?.setItems(items) }
}
}
}
override fun onDestroy() {
compositeJob.cancel() // as in rx you need to cancel all jobs
super.onDestroy()
}
https://www.youtube.com/watch?v=lh2Vqt4DpHU&list=PLdb5m83JnoaBqMWF-qqhZY_01SNEhG5Qs&index=5 at 29:25
I found that the LiveData returned by Dao will call its observer whenever the row is updated in DB, even if the LiveData value is obviously not changed.
Consider a situation like the following example :
Example entity
#Entity
public class User {
public long id;
public String name;
// example for other variables
public Date lastActiveDateTime;
}
Example Dao
#Dao
public interface UserDao {
// I am only interested in the user name
#Query("SELECT name From User")
LiveData<List<String>> getAllNamesOfUser();
#Update(onConflict = OnConflictStrategy.REPLACE)
void updateUser(User user);
}
Somewhere in background thread
UserDao userDao = //.... getting the dao
User user = // obtain from dao....
user.lastActiveDateTime = new Date(); // no change to user.name
userDao.updateUser(user);
Somewhere in UI
// omitted ViewModel for simplicity
userDao.getAllNamesOfUser().observe(this, new Observer<List<String>> {
#Override
public void onChanged(#Nullable List<String> userNames) {
// this will be called whenever the background thread called updateUser.
// If user.name is not changed, it will be called with userNames
// with the same value again and again when lastActiveDateTime changed.
}
});
In this example, the ui is only interested to user name so the query for LiveData only includes the name field. However the observer.onChanged will still be called on Dao Update even only other fields are updated.
(In fact, if I do not make any change to User entity and call UserDao.updateUser, the observer.onChanged will still be called)
Is this the designed behaviour of Dao LiveData in Room? Is there any chance I can work around this, so that the observer will only be called when the selected field is updated?
Edit : I changed to use the following query to update the lastActiveDateTime value as KuLdip PaTel in comment suggest. The observer of LiveData of user name is still called.
#Query("UPDATE User set lastActiveDateTime = :lastActiveDateTime where id = :id")
void updateLastActiveDateTime(Date lastActiveDateTime, int id);
There is simple solution in Transformations method distinctUntilChanged.expose new data only if data was changed.
In this case we get data only when it changes in source:
LiveData<YourType> getData(){
return Transformations.distinctUntilChanged(LiveData<YourType> source));
}
But for Event cases is better to use this:
https://stackoverflow.com/a/55212795/9381524
This situation is known as false positive notification of observer.
Please check point number 7 mentioned in the link to avoid such issue.
Below example is written in kotlin but you can use its java version to get it work.
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
val distinctLiveData = MediatorLiveData<T>()
distinctLiveData.addSource(this, object : Observer<T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged(obj: T?) {
if (!initialized) {
initialized = true
lastObj = obj
distinctLiveData.postValue(lastObj)
} else if ((obj == null && lastObj != null)
|| obj != lastObj) {
lastObj = obj
distinctLiveData.postValue(lastObj)
}
}
})
return distinctLiveData
}
I stuck with the same problem.
What i did wrong:
1) creating anonimous object:
private LiveData<List<WordsTableEntity>> listLiveData;
// listLiveData = ... //init our LiveData...
listLiveData.observe(this, new Observer<List<WordsTableEntity>>() {
#Override
public void onChanged(#Nullable List<WordsTableEntity> wordsTableEntities) {
}
});
In my case, I called the method several times in which this line was located.
From the docs i supposed, that new Observers take data from LiveData. Because of that, author could receive few onChanged methods from few new anonimous Observers, if he set observe userDao.getAllNamesOfUser().observe(this, new Observer that way.
Its will be better to create named Observer object before LiveData.observe(... and once
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
observer = new Observer<List<WordsTableEntity>>() {
#Override
public void onChanged(#Nullable List<WordsTableEntity> wordsTableEntities) {
adapter.setWordsTableEntities(wordsTableEntities);
progressBar.setVisibility(View.GONE);
}
};
}
and then set it LiveData.observe(observer and we receive data from LieData first time and then, when data will be changed.
2) Observing one Observe object multiple times
public void callMethodMultipleTimes(String searchText) {
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
listLiveData.observe(this, observer);
}
I calling this method multiple times and debug showed me, that i was adding my observer as many times, as i called callMethodMultipleTimes();
Our listLiveData is a global variable and it lives. It changes the object reference here
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
, but the old object in memory is not immediately deleted
This will be fixed, if we call listLiveData.removeObserver(observer); before
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
And returning to 1) - we can not call listLiveData.removeObserver(our anonimous Observer); because we do not have an anonymous object reference.
So, in the result we can do so:
private Observer observer;
private LiveData<List<WordsTableEntity>> listLiveData;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
observer = new Observer<List<WordsTableEntity>>() {
#Override
public void onChanged(#Nullable List<WordsTableEntity> wordsTableEntities) {
adapter.setWordsTableEntities(wordsTableEntities);
progressBar.setVisibility(View.GONE);
}
};
}
public void searchText(String searchText) {
if (listLiveData != null){
listLiveData.removeObservers(this);
}
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
listLiveData.observe(this, observer);
}
I didn't use distinct functions. In my case it works without distinct.
I hope my case will help someone.
P.S. Version of libraries
// Room components
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
// Lifecycle components
implementation "android.arch.lifecycle:extensions:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
Currently there is no way to stop triggering Observer.onChanged which is why I think the LiveData will be useless for most of the queries that are using some joins.
Like #Pinakin mentioned there is a MediatorLiveData but this is just a filter and the data still gets loaded on every change. Imagine having 3 left joins in 1 query where you only need a field or two from those joins. In case you implement PagedList every time any record from those 4 tables (main + 3 joined tables) gets updated, the query will be called again.
This is OK for some some tables with small amount of data, but correct me if I wrong this would be bad in case of bigger tables.
It would be best if we would have some way of setting the query to be refreshed only if the main table is updated or ideally to have a way to refresh only if fields from that query are updated in the database.
Avoid false positive notifications for observable queries
Let’s say that you want to get a user based on the user id in an observable query:
#Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): LiveData<User>
You’ll get a new emission of the User object whenever that user updates. But you will also get the same object when other changes (deletes, updates or inserts) occur on the Users table that has nothing to do with the User you’re interested in, resulting in false-positive notifications. Even more, if your query involves multiple tables, you’ll get a new emission whenever something changed in any of them.
If your query returns a LiveData, you can use a MediatorLiveData that only allows distinct object emissions from a source.
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
val distinctLiveData = MediatorLiveData<T>()
distinctLiveData.addSource(this, object : Observer<T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged(obj: T?) {
if (!initialized) {
initialized = true
lastObj = obj
distinctLiveData.postValue(lastObj)
} else if ((obj == null && lastObj != null)
|| obj != lastObj) {
lastObj = obj
distinctLiveData.postValue(lastObj)
}
}
})
return distinctLiveData
}
In your DAOs, make method that returns the distinct LiveData public and the method that queries the database protected.
#Dao
abstract class UserDao : BaseDao<User>() {
#Query(“SELECT * FROM Users WHERE userid = :id”)
protected abstract fun getUserById(id: String): LiveData<User>
fun getDistinctUserById(id: String):
LiveData<User> = getUserById(id).getDistinct()
}
See more of the code here and also in Java.