Android Room. Which method of insertion will be faster? - android

I am using Room as an abstraction layer over SQLite. After reading this page I found out that we can insert multiple objects at the same time. Currently I use a For loop to insert objects, i.e one object in each For loop iteration. The two ways of inserting that I know of currently are:
Using a For loop and inserting each object one at a time
#Insert(onConflict = OnConflictStrategy.REPLACE)
public void addActivity(Person person);
Inserting an array or a list of objects.
#Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(Person ... people);
When I was coding the insertion of objects, I did not know of the second way of insertion. Now I want to know if there is any noticeable difference in speeds between the two ways so that I can change my code to increase performance of my app.

As requested by OP in a comment of their question, here's (for the sake of clarity, as an answer) what I did to check the performance:
Before, inserting objects one by one:
#Dao
abstract class MyDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(items: MyObject): Long
// ...
}
Syncing code:
val response = backend.downloadItems() // download from server
val items = response.getData() // this is a List<MyObject>
if (items != null) {
for (i in items) {
myDao.persist(s)
}
}
This took a minute on a Huawei P10+.
I changed this to:
#Dao
abstract class MyDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(items: Iterable<MyObject>)
// ...
}
Syncing code:
val response = backend.downloadItems() // download from server
val items = response.getData() // this is a List<MyObject>
response.getData()?.let { myDao.insert(it) }
This took less than a second.
The point here is to use specifically the Iterable<> version of the DAO #Insert method, which as said by #iDemigod, uses the Iterable<> version of the EntityInsertionAdapter.
The body of said function is in #iDemigod's answer, and it uses a single prepared statement for all the insertions.
Parsing the SQL into a statement is expensive, and using a statement creates a transaction for the whole insert batch, which can help solve other issues (I had an observable LiveData<> on the database, which was notified 12k times during the insert... performance was awful).

Under the hood Room generated classes are using EntityInsertionAdapter for this particular situation. And there is two methods, we need to check:
This one is used for inserting a single entity
public final long insertAndReturnId(T entity) {
final SupportSQLiteStatement stmt = acquire();
try {
bind(stmt, entity);
return stmt.executeInsert();
} finally {
release(stmt);
}
}
While this one is used to insert an array of entities
public final void insert(Iterable<T> entities) {
final SupportSQLiteStatement stmt = acquire();
try {
for (T entity : entities) {
bind(stmt, entity);
stmt.executeInsert();
}
} finally {
release(stmt);
}
}
AS you can see the internals are pretty much the same as yours - stmt.executeInsert(); is called once or in the loop. The only performance change using the insertUsers method I can think of is the change notification, which will happen only once, when all the users will be inserted. But if you're already doing you insertion in the loop wrapped with #Transaction then there would be no change.

Related

Is it a good practice to observeForever in Repository class? db+network paged list

Im building an app following architecture guidelines.Implemented room db caching + network.Need to get latest page number from separate entity.
My model:
#Entity(tableName = "top_rated_movie_page")
public class Top_Rated_Movies_Page {
#PrimaryKey(autoGenerate = true)
private int db_id;
private Integer page;
private Integer total_results;
private Integer total_pages;
#Ignore
private List<Result> results;
...
Result class contains data which i display in my paged list which observes changes from db.
Using PagedList.BoundaryCallback i need to fetch new data from network and insert it into db.
But i need to get page number somehow.
My dao:
#Insert
void insertAll(Top_Rated_Movies_Page page,List<Result> top_rated_results);
#Query("SELECT * FROM Result")
DataSource.Factory<Integer, Result> getAllResults();
#Query("SELECT * FROM top_rated_movie_page WHERE page= (SELECT MAX(page) FROM top_rated_movie_page)")
LiveData<Top_Rated_Movies_Page> getMoviePage();
I was thinking to observe Top_Rated_Movies_Page from db in my repository class with observeForever() to get that page number.
Is that the best way to approach this?
Since the only time you'll read the next page key or update the backing DB is through BoundaryCallback, you can just read / write your next page key directly.
So in your onItemAtEndLoad() implementation you want something like:
String nextMoviePage = db.runInTransaction(() -> {
movieDao.nextRemoteKey().key;
});
// Make sure not to run on main thread
MovieNetworkResponse response = networkApi.fetchNextMoviePage(remoteKey);
db.runInTransaction(() -> {
movieDao.clearAll(); // Remove previous key
movieDao.insertKey(RemoteKey(response.nextPageKey)); // Insert new key
movieDao.insertAll(response.movies); // Update DataSource + invalidate()
});
Your DAO:
#Insert
void insertAll(List<Result> top_rated_results);
#Query("SELECT * FROM Result")
DataSource.Factory<Integer, Result> getAllResults();
#Query("SELECT * FROM result_key LIMIT 1")
String nextRemoteKey();
#Insert
void insertKey(RemoteKey remoteKey);
And don't forget to clear out both the items and remoteKey whenever you expect to refresh the data!
In the case where you want to keep track of different keys for query, you can simply add that column to your RemoteKey entity.
FYI: Paging2 has been superseded by Paging3 (though just launched alpha01), here is the similar Paging3 sample which solves exactly your use-case: https://github.com/android/architecture-components-samples/blob/master/PagingWithNetworkSample/app/src/main/java/com/android/example/paging/pagingwithnetwork/reddit/repository/inDb/PageKeyedRemoteMediator.kt

How to get OnConflictStrategy return value from DAO

I am using Room Persistence Library. The operation works as expected. But how do I get onConflict value from DAO back from calling method. I get the following error
error: Not sure how to handle the insert method's return type.
Update
After I change return type from Integer to long, I get this error
Not sure how to handle update method's return type. Currently the supported return types are void, int or Int.
In DAO.java
#Dao
public interface UserDao {
#Insert(onConflict = OnConflictStrategy.ABORT)
long insert(UserEntity userEntity);
#Update(onConflict = OnConflictStrategy.IGNORE)
long update(UserEntity userEntity);
}
In MainActivity.java
#Override
protected Void doInBackground(UserEntity... userEntities) {
long result = userDao.insert(userEntities[0]);
if(result == OnConflictStrategy.ABORT){
result = userDao.update(userEntities[0]);
}
return null;
}
In case of ConflictStrategy.ABORT Room will throw a SQLiteConstraintException. You can catch that to fit your use case.
The compilation error you are getting is due to the fact #Insert operations can return long, long[] or void only. Integer will not do.
#Update will return the number of rows updated by the query. This method will return an int or void. Doc: https://www.sqlite.org/lang_update.html
Insert
If the #Insert method receives only 1 parameter, it can return a long, which is the new rowId for the inserted item. If the parameter is an array or a collection, it should return long[] or List instead.
Update
Although usually not necessary, you can have this method return an int value instead, indicating the number of rows updated in the database.
After reading multiple time, i understood the methods are returning number of records instead of error code. Anyway my question regarding how OnConflictStrategy values are read or used is still unanswered

How to get the row count of Room database in android?

I'm following the practice of having a Repository and a Dao and so on. I was trying to get the row count in my database repository by having a function
int getNumFiles() {
List<AFile> lst = files.getValue(); // files is of type LiveData<List<AFile>> files;
if (lst == null) {
return 0;
} else {
return lst.size();
}
}
But lst always evaluates to null. I guess it has something to do with me not being allowed to query the DB from the UI thread or something? Should I implement it like one implements adding or deleting an element? In other words have a function in the Dao which is called via an AsyncTask in the Database repository? I'm confused about how to do this very simple thing.
There is this answer which shows what one would write in the Dao to find out the number of rows, but it does not explain how the repository should call this.
Room database Count Table Row
#Query("SELECT COUNT(column_name) FROM tableName")
LiveData<Integer> getRowCount(); //with LiveData
#Query("SELECT COUNT(column_name) FROM tableName")
int getRowCount();
I ended up doing it like this (using a new thread for the query).
In the Dao
#Query("SELECT COUNT(id) FROM table")
int getCount();
In the repository
int getNumFiles() {
return afileDao.getCount();
}
Where I need it
final AtomicInteger fcount = new AtomicInteger();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
int num = f_repo.getNumFiles();
fcount.set(num);
}
});
t.setPriority(10);
t.start();
t.join();
// use as fcount.get()
Let's see if this works. I may be off base, but I have struggled with this same issue trying to learn Room databases and most recently trying to get the row count of the table I was working with.
(This is my first post, so I apologize for the shortness of it and welcome constructive thought to make it better.)
Starting with the Dao, I declared the method with the #Query() annotation. This is the point where we will define the query we will be using to retrieve the desired information.
#Query("SELECT COUNT(*) FROM word_table")
LiveData<Integer> getCount();
Second, carry this through the Repository. The Repository will be calling our Dao class to retrieve information and essentially pass the query.
public LiveData<Integer> getCount() {
return mWordDao.getCount();
}
Third, bring it into the ViewModel. The ViewModel will be called by the (in this case) MainActivity and in turn will call the getCount() method from the Repository and back down the chain.
// count
public LiveData<Integer> getCount() { return mRepository.getCount(); }
Finally, create the observable in the MainActivity, seeing as I encased the value with a LiveData<> wrapper.
mWordViewModel.getCount().observe(this, new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer integer) {
word_count.setText(String.valueOf(integer));
}
});
I know that this is simplistic, short and leaves out a lot of detail, but after going over the Room Database code a large number of times, this worked for me to be able to display the number of rows in the database table I was referencing. And it seems to be the way that the Room databases are intended to work.
(The code I was using as a base for branching out into retrieving the row count was grabbed from the codebase labs provided by Google for Room Databases part I.)
You can reach them with the following link and click on the one for Room Databases - Part 1:
Codelabs for Android Developers
Scott
I didn't need LiveData and I used a Coroutine:
// DAO
#Query("SELECT COUNT(*) FROM some_table")
suspend fun getCount(): Int
// REPOSITORY
fun getCount(): Int = runBlocking {
val count = async {
dao.getCount()
}
count.start()
count.await()
}
// VIEWMODEL
when (val count = repository.getCount()) {
// do stuff with count
}
I think a nicer way to do miniature things in the background thread is to create a Handler & HandlerThread and use them to perform one liner tasks.
//The handlers to perform tasks on the background threads
override lateinit var mHandler: Handler
override lateinit var mHandlerThread: HandlerThread
override fun start() {
//Instantiate the handlerThread
mHandlerThread = HandlerThread(MainPresenter::class.java.simpleName)
//A call to the start method has to be executed manually
mHandlerThread.start()
mHandler = Handler(mHandlerThread.looper)
}
And wherever you want to call something in the background thread, simply :
mHandler.post { getTableCountInBg() }
I was in the midst of typing what #Sameer Donga linked to, but refer that instead. Call it like above.
P.S. Ignore the override annotations. They're there because I enforce it on a presenter.
#Query("SELECT COUNT(column_name) FROM table)
LiveData getTotalNumberOfColumns();
or do this if you don't want multiple occurences of a value in the column
#Query("SELECT COUNT(DISTINCT column_name) FROM table)
LiveData getTotalNumberOfColumns();
#Query("SELECT COUNT(DISTINCT column_name) FROM table)
LiveData<Integer> getTotalNumberOfRows();
Add DISTINCT as an argument to the COUNT function.

How to setup an application level livedata observer pattern

Background
I have multiple ways to get data from server, such as background polling and server push. These items may be contain same item.when data is ready, I insert these data into database with Android Room. when the insert item have exist in database, I will abort the item. I expect to get notify when these data insert success, so I use livedata in the Dao:
#Dao
public interface WordDao {
#Query("SELECT * from word_table where isNew = 0")
LiveData<List<Word>> getAlphabetizedWords();
#Insert
void insert(List<Word> word);
#Update
void update(List<Word> words);
}
In the application code:
#Override
public void onCreate() {
super.onCreate();
wordRepository = WordRepository.getInstance(this);
wordRepository.getAllWords().observeForever(new DatabaseObserver(this));
}
In the DatabaseObserver, I receive data change notify. I will handle these data, then I will set one property of the class(set isNew = 1) to indicate that the item has been handled. At the same time, that handle keep the later insert will not notify old insert data.
My Question
Is this pattern using observeForever() will be worked as expect?
When I write the demo code, I encounter that the DatabaseObserver's onChanged method receive same item list. After I change my code like this, the problem is still there. How to resolve this problem?

Android -room persistent library - DAO calls are async, therefore how to get callback?

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.
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
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.
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>?
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

Categories

Resources