26

From what i have read Room doesn’t allow you to issue database queries on the main thread (as can cause delays on the main thread)). so imagine i am trying to update a textview on the UI main thread which some data how would i get a call back. Let me show you an example. Imagine i want to store my business model data into a object called Events. We would therefore have a EventDao object:

imagine we have this DAO object below:

@Dao
public interface EventDao {

   @Query("SELECT * FROM " + Event.TABLE_NAME + " WHERE " + Event.DATE_FIELD + " > :minDate" limit 1)
   LiveData<List<Event>> getEvent(LocalDateTime minDate);

   @Insert(onConflict = REPLACE)
   void addEvent(Event event);

   @Delete
   void deleteEvent(Event event);

   @Update(onConflict = REPLACE)
   void updateEvent(Event event);

}

and now in some activity i have a textview and i'd like to update its value so i do this:

 myTextView.setText(EventDao.getEvent(someDate));/*i think this is illegal as im trying to call room dao on mainthread, therefore how is this done correctly ? would i need to show a spinner while it updates ?*/

since the fetching is occuring off of the main thread i dont think i can call it like this and expect a smooth update. Whats the best approach here ?

Some more information: i wanted to use the room database as mechanism for retrieving model information instead of keeping it statically in memory. so the model would be available to me locally through the db after i download it through a rest service.

UPDATE: so since i am returning a livedata then i can do this:

eventDao = eventDatabase.eventDao();
eventDao.getEvent().observe(this, event -> {
     myTextView.setText(event.get(0));
});

and that works for something very small. but imagine my database has a million items. then when i do this call, there will be a delay retrieving the data. The very first time this gets called it will be visible to the user that there is a delay. How to avoid this ? So to be clear , there are times i do not want live data, i just need to update once the view. I need to know how to do this ? even if its not with liveData.

j2emanue
  • 60,549
  • 65
  • 286
  • 456
  • 2
    You are returning a `LiveData`. So, register an observer for changes in the `LiveData`. This is covered in [the documentation for `LiveData`](https://developer.android.com/topic/libraries/architecture/livedata.html). – CommonsWare May 31 '17 at 19:37
  • so a SQL Select statement will also trigger a liveData call back ? – j2emanue May 31 '17 at 19:39
  • Ummm... I think so. I see where you are going with this. The RxJava2 equivalents that a `Dao` supports return a `Flowable` or `Publisher`, and you subscribe to those to get the actual data. I am assuming that `LiveData` works similarly. So far, the work with Room that I have done has been to retrofit a sample that already was using `AsyncTask`; I won't be playing with `LiveData` for another couple of weeks. – CommonsWare May 31 '17 at 19:45
  • @CommonsWare i updated my question – j2emanue May 31 '17 at 19:59
  • "How to avoid this ?" -- don't query a million items. "there are times i do not want live data, i just need to update once the view" -- you are welcome to implement a `@Query` method that does not return a `LiveData` (e.g., returns `List` directly) and do the threading yourself (e.g., `AsyncTask`, `IntentService`, `JobService`, `Thread` and an event bus). – CommonsWare May 31 '17 at 20:06
  • do you know if exist anyway to store the database in memory instead of on the disk then ? i need faster access. otherwise i am contemplating using a static method to hold the model in memory instead and drop room. i am afraid it will show delays on the UI thread to the user. – j2emanue May 31 '17 at 20:16
  • "do you know if exist anyway to store the database in memory instead of on the disk then ?" -- Room (and SQLite) have an in-memory database option, though I don't really see the value. "otherwise i am contemplating using a static method to hold the model in memory instead and drop room" -- um, well, the point behind Room (and SQLite, and files) is to persist data. If you do not persist the data, your in-memory data goes away when the process does. Using Room (or SQLite, or files) does not change the need to perhaps have a caching layer in your app. – CommonsWare May 31 '17 at 20:25
  • you can use android coroutines for it. – Manmohan Sep 22 '19 at 07:13

4 Answers4

12

If you want to do your query synchronously and not receive notifications of updates on the dataset, just don't wrap you return value in a LiveData object. Check out the sample code from Google.

Take a look at loadProductSync() here

Bohsen
  • 4,242
  • 4
  • 32
  • 58
  • You build it with: // Build the database! AppDatabase db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME).build(); – Bohsen Jun 13 '17 at 10:31
  • The Google sample creates the database using an AsyncTask to pass the work off the main thread. Have a look at this: https://github.com/googlesamples/android-architecture-components/blob/master/BasicSample/app/src/main/java/com/example/android/persistence/db/DatabaseCreator.java – Bohsen Jun 13 '17 at 10:33
  • but loadProductSyn is still doing the work on another thread. room enforces this. unless you set allowMainThreadQueries() then all work is done asynchronously. – j2emanue Jun 13 '17 at 10:50
  • But why is that a problem? Do you want to show a progressdialog when the query is triggered? When dealing with millions of rows first index the columns in your database that you query. Secondly if you can't avoid querying the large dataset you should consider creating a database view and query the view. https://www.google.dk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&cad=rja&uact=8&ved=0ahUKEwjU6Nb52LrUAhWpHJoKHeABCnQQFggyMAI&url=https%3A%2F%2Fsqlite.org%2Flang_createview.html&usg=AFQjCNH4Hk6Nt4ibcWHZA3PokqVJo8Bu1w&sig2=l6PuJSnjPiZHDNr4pdt7tA – Bohsen Jun 13 '17 at 11:16
  • thanks for clearing that up. so i had final doubt about loadProduct vs loadProductSync. Why did google call it sync when room database does all queries off main thread. for me the difference between the two DAOs : @Query("select * from products where id = :productId") LiveData loadProduct(int productId); @Query("select * from products where id = :productId") ProductEntity loadProductSync(int productId); is that one returns a liveData so it means your subscriber will continue to get events on changes . the other one just returns a one time event async ?? – j2emanue Jun 13 '17 at 12:39
  • 1
    Well you are right that the query in loadProductSync is run on a background thread, but the point being that you wouldn't receive updates on your dataset, when changes happen. I've just pulled down the code from the google sample and the method is actually never used. I wonder if they iintended to use it in an SyncAdapter example, where you want to synchronize your database with a server. BTW Edited my answer. – Bohsen Jun 15 '17 at 07:00
  • 1
    I have to correct my last comment, because I've just modified the BasicSample project to use the loadProductSync method. When calling mProduct = databaseCreator.getDatabase().productDao().loadProductSync(mProductId); dirrectly in the ProductViewModel class the app crashes with 'Cannot access database on the main thread since it may potentially lock the UI'. Adding .allowMainThreadQueries() to the databasebuilder stops the crash. So this would mean that the query is done on the main thread which was my original assumption. – Bohsen Jun 15 '17 at 07:43
  • 1
    Thank you Bohsen, I was puzzled about the BasicSample project since I did not see "allowMainThreadQueries". So where does this leave us? You either need to use .observe() and LiveData to retrieve the _id of the inserted row, or else add "allowMainThreadQueries" and promise to use them only for short items like long insert(something_small) ??? – Mike Apr 22 '18 at 20:02
  • @Mike If your Id's are autogenerated you can return them from an INSERT-operation easily. Check [this SO answer](https://stackoverflow.com/a/48641762/1495686) . – Bohsen May 03 '18 at 08:00
  • The loadProductSync method has no usage anymore inside BasicSample. – p72b Sep 21 '19 at 20:44
  • this is a very bad example, cause you should avoid create sync query – Zhar Nov 02 '21 at 22:09
7

There is a way to turn off async and allow synchronous access.

when building the database you can use :allowMainThreadQueries()

and for in memory use: Room.inMemoryDatabaseBuilder()

Although its not recommended. So in the end i can use a in memory database and main thread access if i wanted super fast access. i guess it depends how big my data is and in this case is very small.

but if you did want to use a callback.... using rxJava here is one i made for a list of countries i wanted to store in a database:

public Observable<CountryModel> queryCountryInfoFor(final String isoCode) {
    return Observable.fromCallable(new Callable<CountryModel>() {
        @Override
        public CountryModel call() throws Exception {
            return db.countriesDao().getCountry(isoCode);
        }
    }).subscribeOn(Schedulers.io())
       
 .observeOn(AndroidSchedulers.mainThread());

}

you can then easily add a subscriber to this function to get the callback with Rxjava.

Community
  • 1
  • 1
j2emanue
  • 60,549
  • 65
  • 286
  • 456
3

As Bohsen suggested use livedata for query synchronously. But in some special case, we want to do some asynchronous operation based on logic. In below example case, I need to fetch some child comments for the parent comments. It is already available in DB, but need to fetch based on its parent_id in recyclerview adapter. To do this I used return concept of AsyncTask to get back the result. (Return in Kotlin)

Repositor Class

fun getChildDiscussions(parentId: Int): List<DiscussionEntity>? {
        return GetChildDiscussionAsyncTask(discussionDao).execute(parentId).get()
    }

private class GetChildDiscussionAsyncTask constructor(private val discussionDao: DiscussionDao?): AsyncTask<Int, Void, List<DiscussionEntity>?>() {
        override fun doInBackground(vararg params: Int?): List<DiscussionEntity>? {
            return discussionDao?.getChildDiscussionList(params[0]!!)
        }
    }

Dao Class

@Query("SELECT * FROM discussion_table WHERE parent_id = :parentId")
fun getChildDiscussionList(parentId: Int): List<DiscussionEntity>?
Naveen Kumar M
  • 7,497
  • 7
  • 60
  • 74
1

Well, the right answer is to use ListenableFuture or Observable depending if you need one shot query or a new value emitted after database change and the framework you want to use.

From the doc "To prevent queries from blocking the UI, Room does not allow database access on the main thread. This restriction means that you must make your DAO queries asynchronous. The Room library includes integrations with several different frameworks to provide asynchronous query execution."

Exemple with a one shot query. You just have to add this in your gradle file.

// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"

Then your SQL query in your DAO become.

@Query("SELECT * FROM " + Event.TABLE_NAME)
ListenableFuture<List<Event>> getEventList();

Last step is the future call itself.

 ListenableFuture<List<Event>> future = dao.getEventList();
 future.addListener(new Runnable() {
    @Override
    public void run() {
        try {
            List<Event>> result = future.get();
        } catch (ExecutionException | InterruptedException e) {
        }
    }
}, Executors.newSingleThreadExecutor());

Source : https://developer.android.com/training/data-storage/room/async-queries#guava-livedata

Zhar
  • 3,330
  • 2
  • 24
  • 25