使用 Corrutines 的 Kotlin retrofit2 连接
Posted
技术标签:
【中文标题】使用 Corrutines 的 Kotlin retrofit2 连接【英文标题】:Kotlin retrofit2 connection using corrutines 【发布时间】:2021-12-23 02:21:08 【问题描述】:我正在 Kotlin 中开发一个应用程序,它使用 Retrofit2 库与 PokeApi 连接。
我曾尝试使用 corrutines 来做到这一点,但我开始获得的响应为 null,而在使用 corrutines 之前,通过异步调用。
调用 API 的 dataProvider 的代码如下:
DataProvider.kt
/**
* Metodo que permite obtener un LiveData con la informacion del siguiente pokemon por id, que será la posible evolución.
*/
fun viewPokemonEvolution(id: Long): LiveData<PokemonFormResponse>?
var remotePokemonEvolutionFormData : LiveData<PokemonFormResponse>? = null
var call: Response<LiveData<PokemonFormResponse>>
var data: LiveData<PokemonFormResponse>?
CoroutineScope(Dispatchers.Main).launch
call = remoteDataSource.downloadPokemonViewedData(id)
data = call.body()
if(call.isSuccessful)
remotePokemonEvolutionFormData = data
return remotePokemonEvolutionFormData!!
我的 API 类
interface PokemonApi
@GET("pokemon-form/id")
suspend fun getPokemonInfo(@Path("id") idPokemon: Long): Response<LiveData<PokemonFormResponse>>
@GET("pokemon/name")
suspend fun getPokemonExtendedInfo(@Path("name") pokemonName: String): Response<LiveData<PokemonExtendedInfoResponse>>
我的数据类
PokemoFormResponse.kt
data class PokemonFormResponse(
@SerializedName("form_name")
val formName: String,
@SerializedName("form_names")
val formNames: List<Any>,
@SerializedName("form_order")
val formOrder: Int,
@SerializedName("id")
val id: Int,
@SerializedName("is_battle_only")
val isBattleOnly: Boolean,
@SerializedName("is_default")
val isDefault: Boolean,
@SerializedName("is_mega")
val isMega: Boolean,
@SerializedName("name")
val name: String,
@SerializedName("names")
val names: List<Any>,
@SerializedName("order")
val order: Int,
@SerializedName("pokemon")
val pokemon: PokemonUrl,
@SerializedName("sprites")
val sprites: Sprites,
@SerializedName("version_group")
val versionGroup: VersionGroup
)
fun idFilledWithZero(): String
return String.format("%03d", id)
我的远程数据源
IRemoteDataSource.kt
interface IRemoteDataSource
suspend fun downloadPokemonViewedData(id: Long): Response<LiveData<PokemonFormResponse>>
suspend fun downloadPokemonCatchedData(name: String): Response<LiveData<PokemonExtendedInfoResponse>>
interface ILocalDataSource
fun getPokemonList(): LiveData<List<Pokemon>>
fun getPokemonById(idPokemon: Long): LiveData<Pokemon>
suspend fun insertPokemon(pokemon: Pokemon)
以及在LocalDataSource中调用房间的DAO:
@Dao
interface PokemonDao
@Query("SELECT * from listaPokemon")
fun getAll(): LiveData<List<Pokemon>>
@Insert(onConflict = REPLACE)
fun insert(pokemon:Pokemon)
@Insert(onConflict = REPLACE)
fun insertAll(vararg pokemons: Pokemon)
@Query("SELECT * from listaPokemon WHERE id = :pokemonId")
fun getById(pokemonId: Long): LiveData<Pokemon>
我希望你能提出一些修复实现的方法,因为这样更好的方法是使用 corrutines 而不是异步调用。
我添加了一个debugg的捕获显示所有属性为null
希望您能帮上忙,如果是这样,请提前致谢!
[编辑]
添加远程数据源`
class RemoteDataSource : IRemoteDataSource
val BASE_URL = "https://pokeapi.co/api/v2/"
val TIMEOUT: Long = 30
var apiServices: PokemonApi
init
val httpClient : OkHttpClient.Builder = OkHttpClient.Builder()
httpClient.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
httpClient.readTimeout(TIMEOUT, TimeUnit.SECONDS)
httpClient.writeTimeout(TIMEOUT, TimeUnit.SECONDS)
val retrofit: Retrofit = Retrofit.Builder()
//Se utiliza el networkIO como ejecutor de Retrofit
.callbackExecutor(AppExecutors.networkIO)
.client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
apiServices = retrofit.create(PokemonApi::class.java)
/**
* Función tipo utilizando retrofit para obtener datos desde una api remota
*
* Simplicación de las funciones mediante las corrutinas
*/
override suspend fun downloadPokemonViewedData(id: Long): Response<LiveData<PokemonFormResponse>> = withContext(Dispatchers.Main)
apiServices.getPokemonInfo(
id
)
override suspend fun downloadPokemonCatchedData(name: String): Response<LiveData<PokemonExtendedInfoResponse>> = withContext(Dispatchers.Main)
apiServices.getPokemonExtendedInfo(
name
)
还有界面
IRemoteDatasource.kt
interface IRemoteDataSource
suspend fun downloadPokemonViewedData(id: Long): Response<LiveData<PokemonFormResponse>>
suspend fun downloadPokemonCatchedData(name: String): Response<LiveData<PokemonExtendedInfoResponse>>
【问题讨论】:
你在哪里有 GET 请求?IRemoteDataSource
是做什么的?
对不起,我已经加了,IRemoteDataSource是RemoteDataSource的接口,只是stablish方法,必须实现
suspend fun getPokemonInfo(@Path("id") idPokemon: Long): Response<LiveData<PokemonFormResponse>>
为什么要返回包裹在 LiveData
中的原始回复,然后又返回包裹在 Response
中的回复。为什么不直接返回PokemonFormResponse
?
主要目标是我需要使用 LiveData 来管理视图模型,而响应包装是因为我将其视为封装调用 API 的最佳方式
您可以从 api 返回 PokemonFormResponse 并将其转换为 ViewModel 或 Data Provider 中的实时数据(或流)。 This 文章可能会有所帮助。我认为您无法从这样的改造中获得实时数据(我猜至少没有额外的转换器工厂)
【参考方案1】:
在尝试实施@Arpit Shukla 解决方案后,我遇到了调用 viewModelDetail loadPokemonInfo(id) 和 loadPokemonEvolution(id) 的结果的问题,因为它们都返回 LiveData(Unit) 类型,而不仅仅是单位与更改前一样。
所以我像这样在 DetailFragment.kt 上调用这些方法的结果,但不起作用
PokemonDetailFragment.kt
....
fun observeViewModel()
Log.d("info", "Entra en observeViewModel")
pokemonListViewModel?.selectedPokemonIdFromVM?.observe(viewLifecycleOwner, Observer selectedId ->
selectedId?.let pokemonSelectedId ->
pokemonDetailViewModel?.loadPokemonInfo(pokemonSelectedId)
//Al seleccionar el pokemeon actualizamos tambien el evlution del ViewModelDetail
pokemonDetailViewModel?.loadPokemonEvolution(pokemonSelectedId)
)
?:
pokemonIdFromBundle?.let
pokemonDetailViewModel?.loadPokemonInfo(it)
pokemonDetailViewModel?.loadPokemonEvolution(it)
.....
所以我尝试观察响应,所以它是 LiveData,但我不知道我应该对回调做什么。
我的其他班级如下
PokemonApi.kt
interface PokemonApi
@GET("pokemon-form/id")
suspend fun getPokemonInfo(@Path("id") idPokemon: Long): PokemonFormResponse
@GET("pokemon/name")
suspend fun getPokemonExtendedInfo(@Path("name") pokemonName: String): PokemonExtendedInfoResponse
IRemoteDataSource.kt
interface IRemoteDataSource
suspend fun downloadPokemonViewedData(id: Long): PokemonFormResponse
suspend fun downloadPokemonCatchedData(name: String): PokemonExtendedInfoResponse
RemoteDataSource.kt
class RemoteDataSource : IRemoteDataSource
val BASE_URL = "https://pokeapi.co/api/v2/"
val TIMEOUT: Long = 30
var apiServices: PokemonApi
init
val httpClient : OkHttpClient.Builder = OkHttpClient.Builder()
httpClient.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
httpClient.readTimeout(TIMEOUT, TimeUnit.SECONDS)
httpClient.writeTimeout(TIMEOUT, TimeUnit.SECONDS)
val retrofit: Retrofit = Retrofit.Builder()
//Se utiliza el networkIO como ejecutor de Retrofit
.callbackExecutor(AppExecutors.networkIO)
.client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
apiServices = retrofit.create(PokemonApi::class.java)
/**
* Función tipo utilizando retrofit para obtener datos desde una api remota
*
* Simplicación de las funciones mediante las corrutinas
*/
override suspend fun downloadPokemonViewedData(id: Long): PokemonFormResponse = withContext(Dispatchers.Main)
apiServices.getPokemonInfo(
id
)
override suspend fun downloadPokemonCatchedData(name: String): PokemonExtendedInfoResponse = withContext(Dispatchers.Default)
apiServices.getPokemonExtendedInfo(
name
)
关于本地数据库。
口袋妖怪道
@Dao
interface PokemonDao
@Query("SELECT * from listaPokemon")
fun getAll(): List<Pokemon>
@Insert(onConflict = REPLACE)
fun insert(pokemon:Pokemon)
@Insert(onConflict = REPLACE)
fun insertAll(vararg pokemons: Pokemon)
@Query("SELECT * from listaPokemon WHERE id = :pokemonId")
fun getById(pokemonId: Long): Pokemon
AppDatabase.kt
//Definición de la DB ROOM y sus entities
@Database(entities = arrayOf(Pokemon::class), version = 1)
abstract class AppDatabase : RoomDatabase()
//Singleton de la DB
companion object
private var instance: AppDatabase? = null
fun getInstance(context: Context):AppDatabase?
if (instance == null)
synchronized(AppDatabase::class)
//datos del objeto sql
instance = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "pokedexcanner.db")
.fallbackToDestructiveMigration().build()
return instance
//obtención de los DAOs de la DB
abstract fun getPokemonDao(): PokemonDao
ILocalDataSource.kt
interface ILocalDataSource
fun getPokemonList(): List<Pokemon>
fun getPokemonById(idPokemon: Long): Pokemon
suspend fun insertPokemon(pokemon: Pokemon)
LocalDataSource.kt
class LocalDataSource : ILocalDataSource
lateinit var pokemonDao: PokemonDao
constructor(context: Context)
val database = AppDatabase.getInstance(context)
database?.let
pokemonDao = database.getPokemonDao()
override fun getPokemonList(): List<Pokemon>
return pokemonDao.getAll()
override fun getPokemonById(idPokemon: Long): Pokemon
return pokemonDao.getById(idPokemon)
override suspend fun insertPokemon(pokemon: Pokemon)
pokemonDao.insert(pokemon)
实体方面:
口袋妖怪.kt
@Entity(tableName = "listaPokemon")
data class Pokemon (@PrimaryKey var id: Long?,
@ColumnInfo(name = "name") var nombre: String,
@ColumnInfo(name = "image") var imagen: String?,
@ColumnInfo(name = "height") var altura: Float?,
@ColumnInfo(name = "weight") var peso: Float?
)
fun idFilledWithZero(): String
return String.format("%03d", id)
constructor():this(null,"?",null,null,null)
` PokemonFormResponse.kt
data class PokemonFormResponse(
@SerializedName("form_name")
val formName: String,
@SerializedName("form_names")
val formNames: List<Any>,
@SerializedName("form_order")
val formOrder: Int,
@SerializedName("id")
val id: Int,
@SerializedName("is_battle_only")
val isBattleOnly: Boolean,
@SerializedName("is_default")
val isDefault: Boolean,
@SerializedName("is_mega")
val isMega: Boolean,
@SerializedName("name")
val name: String,
@SerializedName("names")
val names: List<Any>,
@SerializedName("order")
val order: Int,
@SerializedName("pokemon")
val pokemon: PokemonUrl,
@SerializedName("sprites")
val sprites: Sprites,
@SerializedName("version_group")
val versionGroup: VersionGroup
)
fun idFilledWithZero(): String
return String.format("%03d", id)
PokemonExtendedInfoResponse.kt
data class PokemonExtendedInfoResponse(
@SerializedName("abilities")
val abilities: List<Ability>,
@SerializedName("base_experience")
val baseExperience: Int,
@SerializedName("forms")
val forms: List<Form>,
@SerializedName("game_indices")
val gameIndices: List<GameIndice>,
@SerializedName("height")
val height: Float,
@SerializedName("held_items")
val heldItems: List<HeldItem>,
@SerializedName("id")
val id: Int,
@SerializedName("is_default")
val isDefault: Boolean,
@SerializedName("location_area_encounters")
val locationAreaEncounters: String,
@SerializedName("moves")
val moves: List<Move>,
@SerializedName("name")
val name: String,
@SerializedName("order")
val order: Int,
@SerializedName("species")
val species: Species,
@SerializedName("sprites")
val sprites: SpritesX,
@SerializedName("stats")
val stats: List<Stat>,
@SerializedName("types")
val types: List<Type>,
@SerializedName("weight")
val weight: Float
)
因此,如果您知道一些处理这种情况的方法,请提前感谢!
【讨论】:
以上是关于使用 Corrutines 的 Kotlin retrofit2 连接的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Kotlin 中将函数接收器类型与 SAM 接口一起使用
【Android Kotlin】关于使用Http来获取数据的基本套路