Android architecture LiveData and Repositories

929 views Asked by At

I am converting my application to room database and try to follow the google architecture best practices based on "Room with a View".

I am having trouble to understand the repository in terms of clean architecture.

The Words database example contains only one table and one view using it, making it a simple HelloWorld example. But lets start with that.

There is a view which displays a list of words. Thus all words need to be read from the database and displayed. So we have a MainActivity and a Database to connect.

  1. Entity Word
  2. WordDao to access DB
  3. WordViewModel: To separate the activity lifecycle from the data lifecycle a ViewModel is used.
  4. WordRepository: Since the data maybe kept in a database or the cloud or whatever the repository is introduced to handle decision, where data comes from.
  5. Activity with the View

It would be nice if the view is updated when the data changes, so LiveData is used.

This in turn means, the repository is providing the LiveData for the full table:

// LiveData gives us updated words when they change.
val allWords: LiveData<List<Word>>

This is all fine for a single view.

Now to my questions on expanding this concept.

Let us assume, the word table has two columns "word" and "last_updated" as time string.

For easier comparison the time string needs to be converted to milliseconds, so I have a function.

Question: Where to put the fun queryMaxServerDateMS() to get the max(last_updated)?

/**
 * @return Highest server date in table in milliseconds or 1 on empty/error.
 */
fun queryMaxServerDateMS(): Long {
    val maxDateTime = wordDao.queryMaxServerDate()

    var timeMS: Long = 0
    if (maxDateTime != null) {
        timeMS = parseDateToMillisOrZero_UTC(maxDateTime)
    }
    return if (timeMS <= 0) 1 else timeMS
}

For me it would be natural to put this into the WordRepository.

Second requirement: Background job to update the word list in the database.

Suppose I now want a Background Job scheduled on a regular basis which checks the server, if new entries were made and downloads them to the database. The app may not be open.

This question just relays to the question of the above queryMaxServerDateMS.

The job will basically check first, if a new entry was made by asking the server if an entry exists which is newer then the max known entry.

So I would need to get a new class WordRepository, do my query, get max last_update and ask the server.

BUT: I do not need the LiveData in the background job and when val repositoy = WordRepository the full table is read, which is needless and time-, memory and batteryconsuming.

I also can think of a number of different fragments that would require some data of the word table, but never the full data, think of a product detail screen which lists one product.

So I can move it out to another Repository or DbHelper however you want to call it.

But in the end I wonder, if I use LiveData, which requires the View, ViewModel and Repository to be closely coupled together:

Question: Do I need a repository for every activity/fragment instead of having a repository for every table which would be much more logical?

2

There are 2 answers

2
Vitalii Malyi On
  1. Yes, with your current architecture you should put it in the Repository.
  2. No, you don't need a repository for every activity/fragment. Preferably, 1 repository should be created for 1 entity. You can have a UseCase for every ViewModel.

In Clean architecture there's a concept of UseCase / Interactor, that can contain business logic, and in Android it can act as an additional layer between ViewModel and Repository, you can create some UseCase class for your function queryMaxServerDateMS(), put it there and call it from any ViewModel you need.

Also you can get your LiveData value synchronously, by calling getValue().

2
ked On

You do not need repository for each activity or fragment. To answer your question about getting max server time - when you load words from db you pretty much have access to entire table. That means you can either do that computation yourself to decide which is the latest word that's added or you can delegate that work to room by adding another query in dao and access it in your repo. I'd prefer latter just for the simplicity of it.

To answer your question about using repo across different activities or fragment - room caches your computations so that they are available for use across different users of your repo (and eventually dao). This means if you have already computed the max server time in one activity and used it there, other lifecycle owners can use that computed result as far as the table has not been altered (there might be other conditions as well)

To summarize you're right about having repository for tables as opposed to activities or fragments