How to get the row count of Room database in android? - 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.

Related

Get one value from LiveData

I have LiveData for Books in ViewModel's constructor:
LiveData<List<Book>> books;
public MyViewModel(#NonNull Application application) {
super(application);
books = bookRepository.getBooks();
}
When user creates new book from UI, I want attribute book_order to be filled with incremented maximum of book_order of other books. To better describe what I want, see following preudocode:
book.book_order = max(books.book_order) + 1;
So when there are three books with book_order 1, 2, 3 respectively, new book would have this attribute set to 4.
Question is, how can I do this with LiveData in ViewModel? I tried using Transformations.map to the new LiveData, but this approach is not working at all, bookMax seems to be null.
public void insertBook(Book book) {
LiveData<Integer> bookMax = Transformations.map(books,
list -> {
int value = 0;
for(Book b: list) {
if (value < b.getBookOrder()) {
value = b.getBookOrder();
}
}
}
);
book.setBookOrder(bookMax + 1)
bookRepository.update(book);
}
Any ideas how to set incremented maximum to the new book? It can be another approach than the one described here. ViewModel was created to separate app logic from UI. However it does not seem to do that in this case, because if I want to observe value, I need to be in Activity. Also, I did not find any alternative how to do this kind of getting one value from DB. Any help appreciated.
Note that your books are livedata, thus may change its value from time to time.
Whereis your bookMax is a single value that should be calculated at the moment of insertion.
To insert you need:
get the current books list
then calculate bookMax
then actually insert.
val bookList: List<Book> = books.value // current value. may be null!
val bookMax: Int = bookList.maxBy { it.order }.order // find max order
// insert
val newBooks = arrayListOf(bookList)
newBooks.add(newBook)
books.value = newBooks // update your livedata
EDIT Here is Java code
// get current value. may be null!
List<Book> bookList = books.getValue();
// so we better handle it early
if (bookList == null) {
bookList = new ArrayList<>();
}
// calculate max order
int maxOrder = -1;
for (Book book : bookList) {
if (maxOrder < book.order) {
maxOrder = book.order;
}
}
// create new book
Book newBook = new Book();
newBook.order = maxOrder + 1;
// add book to the list
bookList.add(newBook);
// do with new list whatever you want
// for example, you can update live data (if it is a MutableLiveData)
books.setValue(bookList);
Using LiveData will not help in your scenario.
LiveData in DAO gets executed on a different thread and the new value is posted in observer code or in your case Transformation.map() callback. So you need to access book.id inside Transformation.map().
However, if you insert book in Transformation.map(), it would trigger an infinite loop since on every table entry update Transformation.map() would be called as LiveData> would change.
So, for your case:
Expose a method which exposes last book id
Insert a new entry.
Add a LiveData> to receive an update and display in UI.
Instead of taking all books for finding max book order, you should make a method in your repository that will provide you max number of book_order from db.
Something like below pseudo code :
int maxOrder = bookRepository.getMaxBookOrder();
Now, all you need to do is while inserting new book, you can use that maxOrder variable to incremental purpose.
So, your insert method will be like :
public void insertBook(Book book) {
int maxOrder = bookRepository.getMaxBookOrder();
book.setBookOrder(maxOrder + 1)
bookRepository.update(book);
}
Here, assuming that you're using ROOM for persisting database, this is the query that can help you get your maximum book_order:
SELECT MAX(book_order) FROM Book
If ROOM isn't your case then, you can do with another approach :
We first retrieve list using repository method and then find maximum from it like below pseudo :
List<Book> books = bookRepository.getBooks().getValue(); // Assuming getBooks() returns LiveData
Then find max from it and then increment it by one :
public void insertBook(Book book) {
List<Book> books = bookRepository.getBooks().getValue();
// calculating max order using loop
int maxOrder = -1;
for (Book book : books) {
if (maxOrder < book.order) {
maxOrder = book.order;
}
}
book.setBookOrder(maxOrder + 1)
bookRepository.update(book);
}
Even you can move this code of finding maximum to repository method as mentioned earlier like :
public int getMaxBookOrder() {
List<Book> books = getBooks().getValue();
// calculating max order using loop
int maxOrder = -1;
for (Book book : books) {
if (maxOrder < book.order) {
maxOrder = book.order;
}
}
return maxOrder;
}
If you really want to do it with a LiveData you can create a custom one:
class BooksLiveData(list: List<Book>) : LiveData<List<Book>>() {
val initialList: MutableList<Book> = list.toMutableList()
fun addBook(book: Book) {
with(initialList) {
// Assuming your list is ordered
add(book.copy(last().bookOrder + 1))
}
postValue(initialList)
}
}
Then you can just create it and use:
val data = bookRepository.getBooks() // Make getBooks() return BooksLiveData now
data.addBook(userCreatedBook) // This'll trigger observers as well
You can still observe this live data, since it's posting initialList when a book is added, it'll notify observers. You can change it more, for example, to return the book that's added etc.
Side note: It might be better to extend from MutableLiveData instead, since LiveData is not supposed to update its value but internally you're posting something so it might be confusing.
Approach 1:
You can use an Auto Increment field or the PrimaryKey such as an id for the book_order's functionality. You can even name it book_order if you want to. Make your Model or Entity class Like:
public class Book{
#PrimaryKey(autoGenerate = true)
private int book_order;
//other data members, constructors and methods
}
So that, the the book_order gets incremented on each Book added to the database.
Next you can have your ViewModel classs like:
public class MyViewModel extends AndroidViewModel {
private BookRepository bookRepository;
LiveData<List<Book>> books;
public MyViewModel(#NonNull Application application) {
super(application);
bookRepository = AppRepository.getInstance(application.getApplicationContext());
books = bookRepository.getBooks();
}
}
Now you can subscribe your list Activity to this ViewModel (ie. make your activity observe the ViewModel) by putting the call to following method in your activity's onCreate():
private void initViewModel() {
final Observer<List<Book>> bookObserver= new Observer<List<Book>>() {
#Override
public void onChanged(#Nullable List<Book> books) {
bookList.clear();
bookList.addAll(books);
if (mAdapter == null) {
mAdapter = new BooksAdapter(bookList, YourActivity.this);
mRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
}
};
mViewModel = ViewModelProviders.of(this)
.get(MyViewModel.class);
mViewModel.mBooks.observe(this, notesObserver); //mBooks is member variable in ViewModel class
}
Doing these things, you will be able to receive updates, ie. whenever a Book is added to your database by the user, the List/ Recycler view should automatically display the newly added Book.
Approach 2:
If this is not what you have wanted at all and you only want to find the latest added book's order, you can skip the third code block completely and use the following in your Dao:
#Query("SELECT COUNT(*) FROM books")
int getCount();
which gives the total number of books ie. rows in the books table, which you can then call from your repository, which in turn can be called from your ViewModel which in turn can be called from the Activity.
Approach 3:
If you want the book_order which I think is the latest number of books in the database after a new book is added, you can use Approach 1 which gives you the List of Book in the ViewModel. You can then get the number of books from the booklist count.
Important!
either way you would want to edit your insertBook() method in your editor or newBook ViewModel and make it something like:
public void insertBook(Book book) {
Book book= mLiveBook.getValue(); //declare mLiveBook as MutableLiveData<Book> in this ViewModel
//maybe put some validation here or some other logic
mRepository.insertBook(book);
}
and in your Repository corresponding insert would look like:
public void insertBook(final Book book) {
executor.execute(new Runnable() {
#Override
public void run() {
mDb.bookDao().insertBook(book);
}
});
}
and corresponding Dao method:
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertBook(Book book);

Notify (RX) data observer only when table size changed

I am using reactive observers (RxJava2) for listening DB changes.
Here is subscription method:
fun subscribeGetTasks(): Flowable<List<Task>> {
val query = taskBox.query().build()
return RxQuery.observable(query).toFlowable(BackpressureStrategy.BUFFER)
}
However, I would like be only notified when table size changed.
How to achieve that?
Thank you
There is no special feature for that. Your observer would have to call count() on the box if you have no query conditions (otherwise there would also be a count() on the Query object).
However, better not use a Query if you are only interested in the count for all objects in a box. Instead, you can do this with ObjectBox:
DataObserver<Class<Task>> taskObserver = new DataObserver<Class<Task>>() {
#Override public void onData(Class<Note> data) {
long taskCount = taskBox.count();
}
};
boxStore.subscribe(Task.class).observer(taskObserver);
For additional reading, please check the docs.

Room Count Query gives no result when ran on background thread

I have an Android Application which uses Room. I need to check what are the counts of item inserted in my table so I have written following code in my Dao interface
#Query("SELECT COUNT(*) FROM notes")
int getCount();
In my repository class I have written the code something like below
public int deleteNote(final NoteEntity note) {
final int[] count1 = new int[1];
executor.execute(new Runnable() {
#Override
public void run() {
count1[0] = mDb.schoolDao().getCount();
}
});
return count1[0];
}
Now when I call the above method in my Activity, I get the count as zero but when I force my application to run on the main thread i.e. removing the Runnable code, then it returns the valid count. I know firing any database query on the main thread is not a good option but I fail to understand why I get the count as zero when it runs on the background thread.
Just to be clear there is always data in the database.
You are getting 0 count because count1[0] returned earlier then actual DB query finished. Instead of using array use MutableLiveData. Something like this:
#Query("SELECT COUNT(*) FROM notes") MutableLiveData< Integer> getCount();
Now you'll get MutableLiveData instance directly after code invocation, but you won't be able to use it right now because it's empty. What you need to do is subscribe on new data:
mDb.schoolDao().getCount(). observe (this, new Observer<Integer>() { #Override public void onChanged(#Nullable final Integer val {count[0]=val; }};
Note that you don't need to run this code in background. Just subscribe right after count [] initialisation and when DB return a count value onChanged() method would invoke.
Not sure that it's mistake but you're returning count [0] in method that returns void. Are you sure that this is what you want?

Android Room. Which method of insertion will be faster?

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.

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