Android Room Database: Threadsafe usage of database properties - android

In order to support synchronization of time using NTP for time sensitive data, I've added a Volatile property to our RoomDatabase from where all update operations can request a "timestamp". But I was wondering if this is threadsafe in regards to using Room with suspending functions and transactions? Also not quite sure it actually has to be marked as Volatile as every access should produce a new java.time.Clock.
Example:
abstract class AppDatabase : RoomDatabase() {
abstract val chatRepository: ChatRepository
abstract val taskRepository: TaskRepository
abstract val userSampleRepository: UserRepository
companion object {
private const val DB_NAME = "example_database"
#Volatile
internal var clock: Clock = Clock.systemDefaultZone()
private set
...
#JvmStatic
fun withClock(clock: Clock) {
synchronized(this) {
this.clock = clock
}
}
}
}
When used:
abstract class TaskRepository
...
private suspend fun updateAll(tasks: List<TaskEntity>) = updateAllInternal(
tasks.map {
TaskEntity.Builder()
.copyOf(it)
.withUpdatedAt(OffsetDateTime.now(AppDatabase.clock))
.create()
}
)
...
}
Related passing tests:
#Test
fun whenRequestingDefaultClock_shouldCreateUpdatedTimestamp() {
val firstTimestamp = Instant.now(AppDatabase.clock)
sleep(1) // Adding 1ms delay to ensure Clock is not fixed and returns a different timestamp
val secondTimestamp = Instant.now(AppDatabase.clock)
assertThat(firstTimestamp).isLessThan(secondTimestamp)
}
#Test
fun whenSwitchingToFixedClock_shouldUseSameTimestamp() {
val instant = Instant.now()
val clock = Clock.fixed(instant, Clock.systemDefaultZone().zone)
AppDatabase.withClock(clock)
val firstTimestamp = Instant.now(AppDatabase.clock)
sleep(1) // Adding 1ms delay to ensure Clock is fixed and returns a fixed timestamp
val secondTimestamp = Instant.now(AppDatabase.clock)
assertThat(firstTimestamp).isEqualTo(secondTimestamp)
}

Related

Android Domain layer with Room Dao's and Repositories

My project has grown to point where it makes sense to implement a domain layer.
Reading the doc's I would implement a use case like so.
class FormatDateUseCase(userRepository: UserRepository) {
private val formatter = SimpleDateFormat(
userRepository.getPreferredDateFormat(),
userRepository.getPreferredLocale()
)
operator fun invoke(date: Date): String {
return formatter.format(date)
}
}
And invoke it like this.
class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
init {
val today = Calendar.getInstance()
val todaysDate = formatDateUseCase(today)
/* ... */
}
}
Yet the Room examples i've come across led me to define my user repository like this.
class UserRepository(private val userDao: UserDao) {
fun insertUser(user: User) {
userDao.insert(user)
}
}
So now my FormatDateUseCase has a dependancy on UserDao that gets instantiated by my Room database.
So my question is there a cleaner way to do this than my approach here?
val viewModel: MyViewModel = MyViewModel(
FormatDateUseCase(
UserRepository(
myRoomDatabase.userDao
)
)
)

Passing errors coming from the API call

I am using 2 separate liveData exposed to show the error coming from the API. I am basically checking if there is an exception with the API call, pass a failure status and serverErrorLiveData will be observed.
So I have serverErrorLiveData for error and creditReportLiveData for result without an error.
I think I am not doing this the right way. Could you please guide me on what is the right way of catching error from the API call. Also, any concerns/recommendation on passing data from repository on to view model.
What is the right way of handing loading state?
CreditScoreFragment
private fun initViewModel() {
viewModel.getCreditReportObserver().observe(viewLifecycleOwner, Observer<CreditReport> {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
})
viewModel.getServerErrorLiveDataObserver().observe(viewLifecycleOwner, Observer<Boolean> {
if (it) {
showScoreUI(false)
showToastMessage()
}
})
viewModel.getCreditReport()
}
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
var creditReportLiveData: MutableLiveData<CreditReport>
var serverErrorLiveData: MutableLiveData<Boolean>
init {
creditReportLiveData = MutableLiveData()
serverErrorLiveData = MutableLiveData()
}
fun getCreditReportObserver(): MutableLiveData<CreditReport> {
return creditReportLiveData
}
fun getServerErrorLiveDataObserver(): MutableLiveData<Boolean> {
return serverErrorLiveData
}
fun getCreditReport() {
viewModelScope.launch(Dispatchers.IO) {
val response = dataRepository.getCreditReport()
when(response.status) {
CreditReportResponse.Status.SUCCESS -> creditReportLiveData.postValue(response.creditReport)
CreditReportResponse.Status.FAILURE -> serverErrorLiveData.postValue(true)
}
}
}
}
DataRepository
class DataRepository #Inject constructor(
private val apiServiceInterface: ApiServiceInterface
) {
suspend fun getCreditReport(): CreditReportResponse {
return try {
val creditReport = apiServiceInterface.getDataFromApi()
CreditReportResponse(creditReport, CreditReportResponse.Status.SUCCESS)
} catch (e: Exception) {
CreditReportResponse(null, CreditReportResponse.Status.FAILURE)
}
}
}
ApiServiceInterface
interface ApiServiceInterface {
#GET("endpoint.json")
suspend fun getDataFromApi(): CreditReport
}
CreditScoreResponse
data class CreditReportResponse constructor(val creditReport: CreditReport?, val status: Status) {
enum class Status {
SUCCESS, FAILURE
}
}
It's creates complexity and increased chances for a coding error to have two LiveData channels for success and failure. You should have a single LiveData that can offer up the data or an error so you know it's coming in orderly and you can observe it in one place. Then if you add a retry policy, for example, you won't risk somehow showing an error after a valid value comes in. Kotlin can facilitate this in a type-safe way using a sealed class. But you're already using a wrapper class for success and failure. I think you can go to the source and simplify it. You can even just use Kotlin's own Result class.
(Side note, your getCreditReportObserver() and getServerErrorLiveDataObserver() functions are entirely redundant because they simply return the same thing as a property. You don't need getter functions in Kotlin because properties basically are getter functions, with the exception of suspend getter functions because Kotlin doesn't support suspend properties.)
So, to do this, eliminate your CreditReportResponse class. Change your repo function to:
suspend fun getCreditReport(): Result<CreditReport> = runCatching {
apiServiceInterface.getDataFromApi()
}
If you must use LiveData (I think it's simpler not to for a single retrieved value, see below), your ViewModel can look like:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
val _creditReportLiveData = MutableLiveData<Result<CreditReport>>()
val creditReportLiveData: LiveData<Result<CreditReport>> = _creditReportLiveData
fun fetchCreditReport() { // I changed the name because "get" implies a return value
// but personally I would change this to an init block so it just starts automatically
// without the Fragment having to manually call it.
viewModelScope.launch { // no need to specify dispatcher to call suspend function
_creditReportLiveData.value = dataRepository.getCreditReport()
}
}
}
Then in your fragment:
private fun initViewModel() {
viewModel.creditReportLiveData.observe(viewLifecycleOwner) { result ->
result.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
viewModel.fetchCreditReport()
}
Edit: the below would simplify your current code, but closes you off from being able to easily add a retry policy on failure. It might make better sense to keep the LiveData.
Since you are only retrieving a single value, it would be more concise to expose a suspend function instead of LiveData. You can privately use a Deferred so the fetch doesn't have to be repeated if the screen rotates (the result will still arrive and be cached in the ViewModel). So I would do:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
private creditReportDeferred = viewModelScope.async { dataRepository.getCreditReport() }
suspend fun getCreditReport() = creditReportDeferred.await()
}
// In fragment:
private fun initViewModel() = lifecycleScope.launch {
viewModel.getCreditReport()
.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
}

Unit testing Room android - This job has not completed yet

I am currently unit testing my local data source which uses Room.
I created a test class:
/**
* Integration test for the [WatchListLocalDataSource].
*/
#RunWith(AndroidJUnit4::class)
#MediumTest
class WatchListLocalDataSourceTest {
private lateinit var sut: WatchListLocalDataSourceImpl
private lateinit var database: ShowsDatabase
private lateinit var entityMapper: ShowEntityMapper
private lateinit var testDispatcher: TestCoroutineDispatcher
private lateinit var testScope: TestCoroutineScope
#Before
fun setup() {
entityMapper = ShowEntityMapper()
testDispatcher = TestCoroutineDispatcher()
testScope = TestCoroutineScope(testDispatcher)
val context = InstrumentationRegistry.getInstrumentation().context
// using an in-memory database for testing, since it doesn't survive killing the process
database = Room.inMemoryDatabaseBuilder(
context,
ShowsDatabase::class.java
)
.setTransactionExecutor(testDispatcher.asExecutor())
.setQueryExecutor(testDispatcher.asExecutor())
.build()
sut = WatchListLocalDataSourceImpl(database.watchListDao(), entityMapper)
}
#After
#Throws(IOException::class)
fun cleanUp() {
database.close()
}
#Test
#Throws(Exception::class)
fun observeWatchedShows_returnFlowOfDomainModel() = testScope.runBlockingTest {
val showId = 1
sut.addToWatchList(mockShow(showId))
val watchedShows: List<Show> = sut.observeWatchedShows().first()
assertThat("Watched shows should contain one element", watchedShows.size == 1)
assertThat("Watched shows element should be ${mockShow(showId).name}", watchedShows.first() == mockShow(showId))
}
}
However, the test does not complete, noting:
java.lang.IllegalStateException: This job has not completed yet
The actual method in the sut is:
override suspend fun addToWatchList(show: Show) = withContext(Dispachers.IO) {
watchListDao.insertShow(WatchedShow(entityMapper.mapFromDomainModel(show)))
}
So the problem started with the addToWatchList method in the data source where I explicitly differed it to the Dipachers.IO coroutine scope, this is unnecessary since Room handles the threading internally if you use the suspend keyword for you functions.
This created a problem where the work started on the test coroutine scope was generating a new scope, and since room needs to complete on the same thread it was started on, there was a deadlock creating the java.lang.IllegalStateException: This job has not completed yet error.
The solutions was:
Remove withContext in the DAO insert method and let Room handle the scoping itself.
add .allowMainThreadQueries() to the database builder in the #Before method of the test class, this allows room to work with the test scope provided and ensure all the work is conducted in that defined scope.
Correct code is:
#Before
fun setup() {
entityMapper = ShowEntityMapper()
testDispatcher = TestCoroutineDispatcher()
testScope = TestCoroutineScope(testDispatcher)
val context = InstrumentationRegistry.getInstrumentation().context
// using an in-memory database for testing, since it doesn't survive killing the process
database = Room.inMemoryDatabaseBuilder(
context,
ShowsDatabase::class.java
)
.setTransactionExecutor(testDispatcher.asExecutor())
.setQueryExecutor(testDispatcher.asExecutor())
// Added this to the builder
|
v
.allowMainThreadQueries()
.build()
sut = WatchListLocalDataSourceImpl(database.watchListDao(), entityMapper)
}
And in the dataSource class:
override suspend fun addToWatchList(show: Show) {
watchListDao.insertShow(WatchedShow(entityMapper.mapFromDomainModel(show)))
}

Correct flow between Kotlin, Realm and ViewModels using Coroutines

Disclaimer: This is not actual code from any app, but an example of the flow and my current understanding on how best to do this. I am looking for help improving upon or what I am doing wrong.
I am trying to figure out the best way to structure an android application using the new jetpack viewModels, realm, and coroutines. I put together a gist of the flow that I have so far, and would love some feedback on how I can improve, what I could change, or what I am doing wrong. Ideally with examples or direct changes to my code.
It works as is, I am just not sure if I am using coroutines correctly or efficiently, and if there is a better way to structure the DAO's so that Realm can be injected for better testability. Someone has already mentioned changing the DAO to extend the LiveData<>, and using onActive() and onInactive() for posting the object. Is that a good idea?
// About Model is the model used by Realm. These models contains realm specific types, like RealmList
open class AboutModel(
var name: String = "",
#PrimaryKey
var version: String = ""
): RealmObject() {
/**
* Conversion function, to convert the view model layer object to the data layer object
*/
companion object {
fun from(about: About): AboutModel = AboutModel(about.name, about.version)
}
fun toObject(): About =
About(
this.name,
this.version
)
}
// About class used everywhere outside of the data/realm layer.
// Lines up with the AboutModel class, but free of realm or any other database specific types.
// This way, realm objects are not being referenced anywhere else. In case I ever need to
// replace realm for something else.
class About (val name: String = "Test", val version: String = "1.0.0") {
override fun toString(): String {
return "author is : $name, version is: $version"
}
}
// Couldn't inject the realm instance because its thread would not match with a suspend function.
// Even if both where background threads. Would be better if I could inject it, but couldn't get
// that to work.
class AboutDao() {
private val _about = MutableLiveData<About>()
init {
val realm = Realm.getDefaultInstance()
val aboutModel = realm.where(AboutModel::class.java).findFirst()
_about.postValue(aboutModel?.toObject() ?: About())
realm.close()
}
suspend fun setAbout(about: About) = withContext(Dispatchers.IO) {
val realm: Realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.copyToRealmOrUpdate(AboutModel.from(about))
_about.postValue(about)
}
realm.close()
}
fun getAbout() = _about as LiveData<About>
}
// Database is a singleton instance, so there is only ever one instance of the DAO classes
class Database private constructor() {
var aboutDao = AboutDao()
private set
companion object {
// #Volatile - Writes to this property are immediately visible to other threads
#Volatile private var instance: Database? = null
suspend fun getInstance() = withContext(Dispatchers.IO) {
return#withContext instance ?: synchronized(this) {
instance ?: Database().also { instance = it }
}
}
}
}
// Repo maintains the dao access. Is also setup to run as a singleton
class AboutRepo private constructor(private val aboutDao: AboutDao){
// This may seem redundant.
// Imagine a code which also updates and checks the backend.
suspend fun set(about: About) {
aboutDao.setAbout(about)
}
suspend fun getAbout() = aboutDao.getAbout()
companion object {
// Singleton instantiation you already know and love
#Volatile private var instance: AboutRepo? = null
fun getInstance(aboutDao: AboutDao) =
instance ?: synchronized(this) {
instance ?: AboutRepo(aboutDao).also { instance = it }
}
}
}
// Injector is used to help keep the injection in a single place for the fragments and activities.
object Injector {
// This will be called from About Fragment
suspend fun provideAboutViewModelFactory(): AboutViewModelFactory = withContext(Dispatchers.Default) {
AboutViewModelFactory(getAboutRepo())
}
private suspend fun getAboutRepo() = withContext(Dispatchers.IO) {
AboutRepo.getInstance(Database.getInstance().aboutDao)
}
}
// AboutViewModel's Factory. I found this code online, as a helper for injecting into the viewModel's factory.
class AboutViewModelFactory (private val aboutRepo: AboutRepo)
: ViewModelProvider.NewInstanceFactory() {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return AboutViewModel(aboutRepo) as T
}
}
// About Fragments ViewModel
class AboutViewModel(private val aboutRepo: AboutRepo) : ViewModel() {
suspend fun getAbout() = aboutRepo.getAbout()
suspend fun setAbout(about: About) = aboutRepo.set(about)
}
// Fragment's onActivityCreated, I set the viewModel and observe the model from the view model for changes
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launch {
viewModel = ViewModelProviders.of(
this#AboutFragment,
Injector.provideAboutViewModelFactory()
).get(AboutViewModel::class.java)
withContext(Dispatchers.Main) {
viewModel.getAbout().observe(viewLifecycleOwner, Observer { about ->
version_number.text = about?.version
})
}
}
}

Android LiveData in androidTest returns null

I'm running an androidTest instrumentation test and I have a method that returns LiveData from a DAO object using Room.
I'm calling the method like so:
val animal = roomDatabase.animalsDao().getAnimal(1)
animal.observeForever(mMockObserver)
assertNotNull(animal.value)
I used Mockito to mock the observer:
#Mock
private lateinit var mMockObserver = Observer<Animal>
This should return an instance of LiveData containing the Animal at id 1, but it's null. It's my understanding that in order for LiveData to return anything, there must be an observer. Did I set this up incorrectly?
Note: If I change the signature of getAnimal() in the DAO to return an Animal directly, rather than a LiveData, then it works so I know it's something with LiveData.
After a little more digging I've found a utility method Google provided through their Architecture Components examples on GitHub.
LiveDataTestUtil
public class LiveDataTestUtil {
/**
* Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
* Once we got a notification via onChanged, we stop observing.
*/
public static <T> T getValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
#Override
public void onChanged(#Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
This allows you to pass the LiveData instance and get back the value it holds.
Update (JUnit 4):
You can also use the InstantTaskExecutorRule combined with observeForever to test your LiveData. In Kotlin you set #get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() at the top of your test class to ensure LiveData is handled synchronously, then inside your test cases myLiveData.observeForever { /* Do something when event emitted */ } to get the LiveData value.
Update (JUnit 5)
If you're using JUnit5, then you can use this extension instead of the Rule explained in Update (JUnit4) above.
class InstantTaskExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) {
runnable.run()
}
override fun postToMainThread(runnable: Runnable) {
runnable.run()
}
override fun isMainThread(): Boolean {
return true
}
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
Use this extension by annotating your test class like so:
#ExtendWith(InstantTaskExecutorExtension::class)
class MyTestClass { ... }
If you're new to extensions (they replace JUnit 4 Rules), you can find additional documentation here: https://junit.org/junit5/docs/current/user-guide/#extensions
If you are doing Kotlin, rather than Java, then you can also use:
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
// Original Java: https://github.com/googlesamples/android-architecture-components/blob/master/BasicSample/app/src/androidTest/java/com/example/android/persistence/LiveDataTestUtil.java
object LiveDataTestUtil {
/**
* Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
* Once we got a notification via onChanged, we stop observing.
*/
#Throws(InterruptedException::class)
fun <T> getValue(liveData: LiveData<T>): T? {
val data = arrayOfNulls<Any>(1)
val latch = CountDownLatch(1)
val observer: Observer<T?> = object : Observer<T?> {
override fun onChanged(o: T?) {
data[0] = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
#Suppress("UNCHECKED_CAST")
return data[0] as T?
}
}
(At the moment the feature of A/S for automigration of Java to Kotlin doesn't quite work correctly for the Google class)
An example for part "Update (JUnit 4)" in Programmer001's answer. (Tuned with official doc on purpose)
Database item data class and table definition:
#Entity(tableName = "item")
data class Item (
var name: String,
#PrimaryKey(autoGenerate = true) var id: Int = 0
)
Test class:
package ...
import ...
#RunWith(AndroidJUnit4::class)
class DBInstrumentedTest1 {
#get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var db: DB
#Before
private fun createDb(): DB {
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
DB::class.java
).build()
}
#After
#Throws(IOException::class)
fun closeDb() {
db.close()
}
#Test
#Throws(Exception::class)
fun coroutine_livedata_db_tests_work() {
val itemDao = db.getItemDao()
val item = Item(name = "First", id = 1)
runBlocking(Dispatchers.Default) { itemDao.insert(Item(item.name)) }
itemDao.getItemByName(item.name).asLiveData().observeForever {
assertThat(it, equalTo(item))
}
}
}

Categories

Resources