I'm trying to use Coroutines with a Room database in an Android project. I've found almost no documentation online, and I'm wondering if it is possible to return Deferred<> types in those methods. Something like this:
#Dao
interface MyObjectDAO {
#Query("SELECT * FROM myObject WHERE id_myObject = :idMyObject")
suspend fun readMyObjectAsync(idMyObject: Int): Deferred<MyObject>
}
I've tried this and I get Not sure how to convert a Cursor to this method's return type at compile time.
My dependencies are:
kapt 'androidx.room:room-compiler:2.1.0-alpha04'
implementation 'androidx.room:room-runtime:2.1.0-alpha04'
implementation 'androidx.room:room-coroutines:2.1.0-alpha04'
Your issue lies in that you're mixing the suspending converter and the Deferred converter. Use one or the other and your code will work as intended.
fun readMyObjectAsync(idMyObject: Int): Deferred<MyObject> - Best choice if you need to interface/be compatible with java code, since it doesn't require code transformations to actually function.
suspend fun readMyObjectAsync(idMyObject: Int): MyObject - If you're operating on pure kotlin this will allow better control through the context it is called in.
Related
I am trying to understand how retrofit implements the following suspend function:
#GET("api/me")
suspend fun getUser(): User
Is there anyway that I can access the locally generated implementation code?
Thanks!
According to docs,
Behind the scenes this behaves as if defined as fun user(...):
Call and then invoked with Call.enqueue. You can also return
Response for access to the response metadata.
Everything else lies inside the sources like this class. Btw, I'm just curious, why do you need to understand the exact implementation from the Retrofit side?
My shared module contains Repository class which has two functions that return a list of items wrapped in a custom class extending Flow called CFlow.
I took the code for CFlow from kotlinconf-app and here:
fun <T> Flow<T>.asCFlow(): CFlow<T> = CFlow(this)
class CFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object : Closeable {
override fun close() {
job.cancel()
}
}
}
}
Repository example functions:
fun getData1(): CFlow<List<Profile>>
fun getData2(): CFlow<List<String>>
When I try to call this functions in iOS swift code the return type of the functions get converted to CFlow<NSArray> and inside of watch function the type of array is Any.
This is weird because in both kotlinconf-app and here the return types of functions are preserved and there is no casting involved in their codebase.
Question: How can I make the type of CFlow to be known in Xcode iOS project?
Android Studio version: 4.1.1
Kotlin lang and plugin version: 1.4.21
Kotlin Multiplatform Mobile plugin version: 0.2.0
Xcode version: 12.2
right now the compiler can't understand nested generics. as a workaround for now, wrap your list inside a data class like this
data class ProfileResult(val data: List<Profile>)
fun getData1(): CFlow<ProfileResult>
it will give you a concrete type in ios
This is because there are no generics in Objective-C. Arrays are ordered collections of objects.
So using any generic collection type in Kotlin, will lose it's type when translated to NSArray
I believe you have three options here:
Wait for direct Kotlin - Swift interop (which is postponed currently)
Cast values in Swift
Don't use generics with collections. I'm personally not using the Flow wrapper currently and doing something like this:
fun observeItems(onChange: (List<Item>) -> Unit) {
items.onEach {
onChange(it)
}.launchIn(coroutineScope)
}
Exposing a dispose function to iOS
fun dispose() {
coroutineScope.cancel()
}
And consuming like this:
repo.observeItems { items in
...
}
But definitely this is more work and hopefully these interop issues will be solved along the way
There are apparently Kotlin coroutines extension functions for SqlDelight, but I don't know how to implement them since I can't find documentation.
I have a normal query that looks like this:
val allItems
get() = itemQueries.selectAll().mapToList()
Can I turn this into a suspend function?
There is currently (v1.2.1) no suspend function support for SqlDelight queries, however you can consume a Coroutines Flow object, which is even better. To do this you need to add the coroutines extension library in your app gradle:
dependencies {
implementation "com.squareup.sqldelight:coroutines-extensions:1.2.1"
}
Then turn your query into this:
val allItems: Flow<List<Item>> =
itemQueries.selectAll()
.asFlow()
.mapToList()
This flow emits the query result, and emits a new result every time the database changes for that query.
You can then .collect{} the results inside a coroutine scope.
For single-shot queries, you don't need the coroutine extension library. Instead, just do:
suspend fun getAllItems() = withContext(Dispatchers.IO) {
itemQueries.selectAll().mapToList()
}
The other answer is specific to when you want to react to changes in the database.
I'm trying to make some Unit tests for my business logic.
Data is read and written to Room database, so the logic depends on what's inside my database.
I can easily buildInMemoryDatabase and test all the logic, but using Instrumental tests which are slow and require a device to be connected.
I want to run Unit tests only where I replace my RoomRepository with some other implementation of Repository interface
class RoomRepository(
private val database: RoomDatabase //actual room database
): Repository {
override fun getFooByType(type: Int): Maybe<List<Item>> {
return database.fooDao()
.getFooByType(type)
.map { names ->
names.map { name -> Item(name) }
}
.subscribeOn(Schedulers.io())
}
}
Maybe there is a way to run Room sqlite on host machine?
Maybe there is another solution?
Create a wrapper class named "Repository" around the RoomDatabase and have methods to expose the Dao objects. By that way, we can easily mock the repository class like below
Open class Repository(private val roomDatabase:RoomDatabase){
open fun productsDao():ProductsDao = roomDatabase.productDao()
open fun clientsDao():ClientsDao = roomDatabase.clientsDao()
//additional repository logic here if you want
}
Now, in tests, this class can be easily mocked like
val repositoryMock = mock(Repository::class.java)
val productsDaoMock = mock(ProductsDao::class.java)
when(repositoryMock.productsDao()).thenReturn(productsDaoMock)
when(productsDaoMock.getProducts()).thenReturn(listof("ball","pen")
So, inject and use the repository class instead of RoomDatabase class in all the places of your project so that the repository and all the Dao can be easily mocked
You usually access the database through the #Dao interfaces. These can be mocked easily.
The daos are returned from abstract methods of your actual RoomDatabase, so this could be mocked easily as well.
Just instantiate your RoomRepository with the mocks and setup these properly.
Please refer to this article.
https://developer.android.com/training/data-storage/room/testing-db
It's Unit test and not slowly like UI Test as you think.
The recommended approach for testing your database implementation is writing a JUnit test that runs on an Android device. Because these tests don't require creating an activity, they should be faster to execute than your UI tests.
As part of working on the development of a new API, I am learning to use Kotlin. Initially I want the Kotlin API to be used within a Java (Android) project, but in the long term I hope to adopt Kotlin entirely.
As part of improving the implementation of a long-running process, I want to use coroutines. Specifically, a channel producer from the kotlinx.coroutines package.
For example:
fun exampleProducer() = produce {
send("Hello")
delay(1000)
send("World")
}
What is the best way to consume this in Java? I am okay with adding temporary 'helper' functions to Kotlin and/or Java.
The easiest way to interop channels with Java is via Reactive Streams. Both Rx and Project Reactor are supported out-of-the-box. For example, add kotlinx-coroutines-rx2 to your dependicies and you'll be able to use rxFlowable builder:
fun exampleFlowable() = rxFlowable<String> {
send("Hello")
delay(1000)
send("World")
}
This function returns an instance of Flowable, which is specifically designed for ease-of-use from Java, for example, you can do in Java:
exampleFlowable().subscribe(t -> System.out.print(t));
Currently, assuming Java 8 is used and lambdas are available, I rely on a helper function defined in Kotlin which allows passing a callback to consume incoming results.
The helper method in Kotlin:
fun exampleProducerCallback( callback: (String) -> Unit ) = runBlocking {
exampleProducer().consumeEach { callback( it ) }
}
This is then consumed in Java as:
ApiKt.exampleProducerCallback( text -> {
System.out.print( text );
return Unit.INSTANCE; // Needed since there is no void in Kotlin.
} );
Explanation on why return Unit.INSTANCE is needed can be found in this answer.