我想获取一个API请求并将请求的数据保存到DB。还想返回数据(即写入数据库)。我知道,这在RxJava中是可能的,但是现在我在Kotlin协程中编写,当前使用Fuel而不是Retrofit(但相差并不大)。我读过How to use Fuel with a Kotlin coroutine,但听不懂。
如何编写协程和方法?
更新
说,我们有一个Java and Retrofit,RxJava。然后我们可以编写代码。
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) {
....
}
...
}
地区:
data class Region(
val id: Int,
val name: String,
val countryId: Int)
网络:
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())
}
}
答案 0 :(得分:1)
让我们逐层查看它。
首先,据我所知,您的RegionResponse
和Region
完全适合此用例,因此我们完全不会碰它们。
您的网络层是用Java编写的,因此我们假定它始终期望同步行为,并且也不会碰它。
所以,我们从回购开始:
fun getRegion(countryId: Int) = async {
val regionFromDb = db.getRegion(countryId)
if (regionFromDb == null) {
return apiMapper.map(api.regions).
filter({ it.countryId == countryId }).
first().
also {
db.updateRegions(it)
}
}
return dbMapper.map(regionFromDb)
}
请记住,我没有您的代码,所以细节可能有所不同。但是协程的一般想法是,用async()
启动它们,以防它们需要返回结果,然后编写代码,就像您处在不需要担心自己的完美世界中一样并发。
现在到交互器:
class RegionInteractor(
private val repo: RegionRepository,
private val prefsRepository: PrefsRepository) {
fun getRegion() = withContext(Schedulers.io().asCoroutineDispatcher()) {
val countryId = prefsRepository.countryId
return repo.getRegion(countryId).await()
}
}
您需要一些东西来将异步代码转换回同步代码。为此,您需要某种线程池来执行。在这里,我们使用Rx中的线程池,但是如果您想使用其他池,也可以。
答案 1 :(得分:1)
研究了How to use Fuel with a Kotlin coroutine,Fuel coroutines和https://github.com/kittinunf/Fuel/(寻找awaitStringResponse
)之后,我提出了另一个解决方案。假设您拥有带有协同程序1.0.0和Fuel 1.16.0的Kotlin 1.3。
我们必须避免异步请求和回调,并使其同步(每个请求都在协程中)。说,我们要通过代码显示国家/地区名称。
// POST-request to a server with country id.
fun getCountry(countryId: Int): Request =
"map/country/"
.httpPost(listOf("country_id" to countryId))
.addJsonHeader()
// Adding headers to the request, if needed.
private fun Request.addJsonHeader(): Request =
header("Content-Type" to "application/json",
"Accept" to "application/json")
它提供了一个JSON:
{
"country": {
"name": "France"
}
}
要解码JSON响应,我们必须编写一个模型类:
data class CountryResponse(
val country: Country,
val errors: ErrorsResponse?
) {
data class Country(
val name: String
)
// If the server prints errors.
data class ErrorsResponse(val message: String?)
// Needed for awaitObjectResponse, awaitObject, etc.
class Deserializer : ResponseDeserializable<CountryResponse> {
override fun deserialize(content: String) =
Gson().fromJson(content, CountryResponse::class.java)
}
}
然后,我们应该创建一个UseCase或Interactor来同步接收结果:
suspend fun getCountry(countryId: Int): Result<CountryResponse, FuelError> =
api.getCountry(countryId).awaitObjectResponse(CountryResponse.Deserializer()).third
我使用third
访问响应数据。但是,如果您希望检查HTTP错误代码!= 200,请删除third
,然后再获取所有三个变量(作为Triple
变量)。
现在您可以编写一种打印国家名称的方法。
private fun showLocation(
useCase: UseCaseImpl,
countryId: Int,
regionId: Int,
cityId: Int
) {
GlobalScope.launch(Dispatchers.IO) {
// Titles of country, region, city.
var country: String? = null
var region: String? = null
var city: String? = null
val countryTask = GlobalScope.async {
val result = useCase.getCountry(countryId)
// Receive a name of the country if it exists.
result.fold({ response -> country = response.country.name }
, { fuelError -> fuelError.message })
}
}
val regionTask = GlobalScope.async {
val result = useCase.getRegion(regionId)
result.fold({ response -> region = response.region?.name }
, { fuelError -> fuelError.message })
}
val cityTask = GlobalScope.async {
val result = useCase.getCity(cityId)
result.fold({ response -> city = response.city?.name }
, { fuelError -> fuelError.message })
}
// Wait for three requests to execute.
countryTask.await()
regionTask.await()
cityTask.await()
// Now update UI.
GlobalScope.launch(Dispatchers.Main) {
updateLocation(country, region, city)
}
}
}
在build.gradle
中:
ext {
fuelVersion = "1.16.0"
}
dependencies {
...
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
// Fuel.
//for JVM
implementation "com.github.kittinunf.fuel:fuel:${fuelVersion}"
//for Android
implementation "com.github.kittinunf.fuel:fuel-android:${fuelVersion}"
//for Gson support
implementation "com.github.kittinunf.fuel:fuel-gson:${fuelVersion}"
//for Coroutines
implementation "com.github.kittinunf.fuel:fuel-coroutines:${fuelVersion}"
// Gson.
implementation 'com.google.code.gson:gson:2.8.5'
}
如果您想使用coroutines
和Retrofit
,请阅读https://medium.com/exploring-android/android-networking-with-coroutines-and-retrofit-a2f20dd40a83(或俄语的https://habr.com/post/428994/)。
答案 2 :(得分:1)
您应该能够大大简化您的代码。声明类似于以下内容的用例:
class UseCaseImpl {
suspend fun getCountry(countryId: Int): Country =
api.getCountry(countryId).awaitObject(CountryResponse.Deserializer()).country
suspend fun getRegion(regionId: Int): Region =
api.getRegion(regionId).awaitObject(RegionResponse.Deserializer()).region
suspend fun getCity(countryId: Int): City=
api.getCity(countryId).awaitObject(CityResponse.Deserializer()).city
}
现在您可以像这样编写showLocation
函数了:
private fun showLocation(
useCase: UseCaseImpl,
countryId: Int,
regionId: Int,
cityId: Int
) {
GlobalScope.launch(Dispatchers.Main) {
val countryTask = async { useCase.getCountry(countryId) }
val regionTask = async { useCase.getRegion(regionId) }
val cityTask = async { useCase.getCity(cityId) }
updateLocation(countryTask.await(), regionTask.await(), cityTask.await())
}
}
您不需要在IO
调度程序中启动,因为您的网络请求是非阻塞的。
我还必须注意,您不应在GlobalScope
中启动。定义适当的协程范围,以使其生命周期与Android活动或其他活动的生命周期保持一致。