I want to get an API request and save request's data to a DB. Also want to return the data (that is written to DB). I know, this is possible in RxJava, but now I write in Kotlin coroutines, currently use Fuel instead of Retrofit (but a difference is not so large). I read How to use Fuel with a Kotlin coroutine, but don't understand it.
How to write a coroutine and methods?
UPDATE
Say, we have a Java and Retrofit, RxJava. Then we can write a code.
RegionResponse:
@AutoValue
public abstract class RegionResponse {
@SerializedName("id")
public abstract Integer id;
@SerializedName("name")
public abstract String name;
@SerializedName("countryId")
public abstract Integer countryId();
public static RegionResponse create(int id, String name, int countryId) {
....
}
...
}
Region:
data class Region(
val id: Int,
val name: String,
val countryId: Int)
Network:
public Single<List<RegionResponse>> getRegions() {
return api.getRegions();
// @GET("/regions")
// Single<List<RegionResponse>> getRegions();
}
RegionRepository:
fun getRegion(countryId: Int): Single<Region> {
val dbSource = db.getRegion(countryId)
val lazyApiSource = Single.defer { api.regions }
.flattenAsFlowable { it }
.map { apiMapper.map(it) }
.toList()
.doOnSuccess { db.updateRegions(it) }
.flattenAsFlowable { it }
.filter({ it.countryId == countryId })
.singleOrError()
return dbSource
.map { dbMapper.map(it) }
.switchIfEmpty(lazyApiSource)
}
RegionInteractor:
class RegionInteractor(
private val repo: RegionRepository,
private val prefsRepository: PrefsRepository) {
fun getRegion(): Single<Region> {
return Single.fromCallable { prefsRepository.countryId }
.flatMap { repo.getRegion(it) }
.subscribeOn(Schedulers.io())
}
}
You should be able to significantly simplify your code. Declare your use case similar to the following:
Now you can write your
showLocation
function like this:You have no need to launch in the
IO
dispatcher because your network requests are non-blocking.I must also note that you shouldn't launch in the
GlobalScope
. Define a proper coroutine scope that aligns its lifetime with the lifetime of the Android activity or whatever else its parent is.After researching How to use Fuel with a Kotlin coroutine, Fuel coroutines and https://github.com/kittinunf/Fuel/ (looked for
awaitStringResponse
), I made another solution. Assume that you have Kotlin 1.3 with coroutines 1.0.0 and Fuel 1.16.0.We have to avoid asynhronous requests with callbacks and make synchronous (every request in it's coroutine). Say, we want to show a country name by it's code.
It gives a JSON:
To decode the JSON response we have to write a model class:
Then we should create a UseCase or Interactor to receive a result synchronously:
I use
third
to access response data. But if you wish to check for a HTTP error code != 200, removethird
and later get all three variables (asTriple
variable).Now you can write a method to print the country name.
In
build.gradle
:If you want to work with
coroutines
andRetrofit
, please, read https://medium.com/exploring-android/android-networking-with-coroutines-and-retrofit-a2f20dd40a83 (or https://habr.com/post/428994/ in Russian).Let's look at it layer by layer.
First, your
RegionResponse
andRegion
are totally fine for this use case, as far as I can see, so we won't touch them at all.Your network layer is written in Java, so we'll assume it always expects synchronous behavior, and won't touch it either.
So, we start with the repo:
Remember that I don't have your code, so maybe the details will differ a bit. But the general idea with coroutines, is that you launch them with
async()
in case they need to return the result, and then write your code as if you were in the perfect world where you don't need to concern yourself with concurrency.Now to the interactor:
You need something to convert from asynchronous code back to synchronous one. And for that you need some kind of thread pool to execute on. Here we use thread pool from Rx, but if you want to use some other pool, so do.