Room + Kotlin Flow not emitting result - kotlin

i'm trying to fetch some data from api, and them store on room database, so the main data source is roomDatabase.
my repository code looks like:
suspend fun fetchData(): Flow<Response<List<Foo>>> {
val shouldRequestData = dao.getFoo().isEmpty()
return if (shouldRequestData) {
getFoo()
} else getLocalFoo()
}
override suspend fun getFoo(): Flow<Response<List<Foo>>> {
return ....... request done normally... inserting normally on database (and showing
on database inspector)
}
override suspend fun getLocalFoo(): Flow<Response<List<Foo>>> = flow {
dao.getFoo().transform<List<FooLocal>, Response<List<Foo>>> {
Response.Success(
it.map {
it.toDomainModel()
}
)
}
}
on Dao:
#Query("SELECT * FROM localdb")
fun getFoo(): Flow<List<Foo>>
and then collecting it normally on viewmodel...
The problem is: the data is not appearing.. how could i solve this? The non-flow version works :/
I already searched for this problem, but nothing seems to work.

Solved by putting this on getLocalFoo() ->
val result: Flow<Response<List<Foo>>> =
Transformations.map(dao.getFoo()) {
Response.Success(it?.map {
it.asDomainModel()
} ?: emptyList()
}.asFlow()
return result

I have found a solution to investing so much time.
Solution: Same Dao Object should be used when we insert details into the room database and get information from DB.
If you are using dagger hilt then
#Singleton annotation will work.
I hope this will solve your problem.

Related

Emit data to kotlin's flow from regular java function

I have an external interface which I cannot change:
interface ExternalApi {
fun onDataReceived(data: String)
}
I need to start consuming data and send it to flow. Data order is a necessity. I'd like to have a cold flow, but I couldn't find a version of cold flow with emit function, so I used hot flow + replay set to Max value as a workaround. Here was my first try:
class FlowProblem {
val flow: MutableSharedFlow<String> = MutableSharedFlow(replay = Int.MAX_VALUE)
fun startConsuming() {
object : ExternalApi {
override fun onDataReceived(data: String) {
flow.emit(data)
}
}
}
}
Unfortunately it doesn't work as emit function is a suspended function. However this is an external interface and I cannot add suspend modifier. I tried to also do something like this:
override fun onDataReceived(data: String) {
val coroutineScope = CoroutineScope(Job())
coroutineScope.launch {
flow.emit(data)
}
}
but for me it's kind a silly to create new coroutine only in order to move data to flow. I'm also wondering about data order.
What should I do? Maybe flow/channel is not suitable here and I should pick something another?
Thanks IR42, callbackFlow was exactly what I needed.

Combine a Flow and a non Flow api response Kotlin

I currently have a piece of logic as follows:
interface anotherRepository {
fun getThings(): Flow<List<String>>
}
interface repository {
suspend fun getSomeThings(): AsyncResult<SomeThings>
}
when (val result = repository.getSomeThings()) {
is AsyncResult.Success -> {
anotherRepository.getThings().collectLatest {
// update the state
}
else -> { }
}
}
The problem I am having is that, if repository.getSomeThings has been triggered multiple times before, anotherRepository.getThings is getting triggered for the amount of all the pre-loaded values from repository.getSomeThings. I was wondering what is the proper way to use these repositories, one a suspend function, the other a Flow together. The equivalent behaviour that is combineLatest{} in Rx.
Thank you.
There are a couple of ways to solve your problem. One way is just to call
repository.getSomeThings() in the collectLatest block and cache last result:
var lastResult: AsyncResult<SomeThings>? = null
anotherRepository.getThings().collectLatest {
if (lastResult == null) {
lastResult = repository.getSomeThings()
}
// use lastResult and List<String>
}
Another approach is to create a Flow, which will be calling repository.getSomeThings() function, and combine two Flows:
combine(
anotherRepository.getThings(),
flow {emit(repository.getSomeThings())}
) { result1: List<String>, result2: AsyncResult<SomeThings> ->
...
}

Alternative solution to injecting dispatchers to make the code testable

I run into a problem during writing tests for a viewModel. The problem occurred when I was trying to verify LiveData that is updated with channelFlow flow on Dispatchers.IO.
I created a simple project to show the issue.
There is a data provider class that is providing 10 numbers:
As it is, the numbers variable in the test is empty and the test fails. I know it is a problem with coroutine dispatchers.
val numbersFlow: Flow<Int> = channelFlow {
var i = 0
while (i < 10) {
delay(100)
send(i)
i++
}
}.flowOn(Dispatchers.IO)
a simple viewModel that is collecting data:
class NumbersViewModel: ViewModel() {
private val _numbers: MutableLiveData<IntArray> = MutableLiveData(IntArray(0))
val numbers: LiveData<IntArray> = _numbers
val dataProvider = NumbersProvider()
fun startCollecting() {
viewModelScope.launch(Dispatchers.Main) {
dataProvider.numbersFlow
.onStart { println("start") }
.onCompletion { println("end") }
.catch { exception -> println(exception.message.orEmpty())}
.collect { data -> onDataRead(data) }
}
}
fun onDataRead(data: Int) {
_numbers.value = _numbers.value?.plus(data)
}
}
and the test:
class NumbersViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
private lateinit var viewModel: NumbersViewModel
#Before
fun setUp() {
viewModel = NumbersViewModel()
}
#Test
fun `provider_provides_10_values`() {
viewModel.startCollecting()
mainCoroutineRule.advanceTimeBy(2000)
val numbers = viewModel.numbers.value
assertThat(numbers?.size).isEqualTo(10)
}
}
There is a common solution with changing the main dispatcher for test usage but... is there any good solution for dealing with the IO one?
I found a solution with injecting dispatchers everywhere - similarly to how I would inject NumbersProvider using Hilt in a real app - and that enables injecting our test dispatcher when we need it. It works but now I have to inject dispatchers everywhere in the code and I don't really like that if it only serves to solve the testing problem
I tried another solution and created a Singleton which makes all the standard dispatchers available in the production code and which I can configure for tests (by setting every dispatcher to the test one). I like how the resulting source code looks more - there is no additional code in viewModels and data providers but there is this singleton and everyone shouting 'Don't use singletons'
Is there any better option to correctly test code with coroutines?

How to emit the result of a subscription

I have the following situation:
I am using the RxKotlin extensions for detecting clicks in the buttons of my activity
I am using Room for inserting records in a database
This is the code related in my activity:
button.clicks()
.flatMap {
val list = mutableListOf<Answer>()
val date = Date()
list.add(Answer("some placeholder info"))
list.add(Answer("Another placeholder info"))
Observable.fromArray(list)
}
.map {
upsertStatusQuestionsViewModel.insertMultipleAnswers(it)
}.subscribe {
// it here is an object Maybe<Answer>
}
And this is the code of the ViewModel:
fun insertMultipleAnswers(answers: List<Answer>) = database.answerDao()
.createMultiple(answers.toList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
I would like to show some information about the answer inserted in the database, for that reason, I need to get the Answer object in my subscription. However, I don't know which operator can I use for achieving that the it object in the subscription is of class Answer, instead of Maybe<Answer>.
Thanks a lot for your help!
If anyone stumbles with this, the solution is to parse the Maybe to an Observable, as Observable implements ObservableSource, so my code is now something like this:
upsert_status_questions_confirm.clicks()
.map {
val list = mutableListOf<Answer>()
list.add(Answer("Some placeholder"))
list.add(Answer("Another placeholder"))
list
}.flatMap {
upsertStatusQuestionsViewModel.insertMultipleAnswers(*it.toTypedArray())
}.subscribe({
// Success...
}, {
// Error...
})
And in the ViewModel:
fun insertMultipleAnswers(vararg answers: Answer) = database.answerDao()
.createMultiple(answers.toList())
.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

How do I use custom configuration in Ktor?

I'm digging the built-in configuration support, and want to use it (instead of just rolling my own alongside Ktor's), but I'm having a hard time figuring out how to do it in a clean way. I've got this, and it's working, but it's really ugly and I feel like there has to be a better way:
val myBatisConfig = MyBatisConfig(
environment.config.property("mybatis.url").getString(),
environment.config.property("mybatis.driver").getString(),
environment.config.property("mybatis.poolSize").getString().toInt())
installKoin(listOf(mybatisModule(myBatisConfig), appModule), logger = SLF4JLogger())
Thanks for any help!
Adding to the existing accepted answer. An implementation using ConfigFactory.load() could look like this (Without libs):
object Config {
#KtorExperimentalAPI
val config = HoconApplicationConfig(ConfigFactory.load())
#KtorExperimentalAPI
fun getProperty(key: String): String? = config.propertyOrNull(key)?.getString()
#KtorExperimentalAPI
fun requireProperty(key: String): String = getProperty(key)
?: throw IllegalStateException("Missing property $key")
}
So, theconfig class would become:
val myBatisConfig = MyBatisConfig(
requireProperty("mybatis.url"),
requireProperty("mybatis.driver"),
requireProperty("mybatis.poolSize").toInt())
Okay, I think I have a good, clean way of doing this now. The trick is to not bother going through the framework itself. You can get your entire configuration, as these cool HOCON files, extremely easily:
val config = ConfigFactory.load()
And then you can walk the tree yourself and build your objects, or use a project called config4k which will build your model classes for you. So, my setup above has added more configuration, but gotten much simpler and more maintainable:
installKoin(listOf(
mybatisModule(config.extract("mybatis")),
zendeskModule(config.extract("zendesk")),
appModule),
logger = SLF4JLogger())
Hope someone finds this useful!
You could also try this solution:
class MyService(val url: String)
fun KoinApplication.loadMyKoins(environment: ApplicationEnvironment): KoinApplication {
val myFirstModule = module {
single { MyService(environment.config.property("mybatis.url").getString()) }
}
val mySecondModule = module {}
return modules(listOf(myFirstModule, mySecondModule))
}
fun Application.main() {
install(DefaultHeaders)
install(Koin) {
loadMyKoins(environment)
SLF4JLogger()
}
routing {
val service by inject<MyService>()
get("/") {
call.respond("Hello world! This is my service url: ${service.url}")
}
}
}
fun main(args: Array<String>) {
embeddedServer(Netty, commandLineEnvironment(args)).start()
}