使用 corrutines 的 Kotlin retrofit2 连接

Kotlin retrofit2 connection using corrutines

我正在使用 Kotlin 开发一个应用程序,它使用 Retrofit2 库与 PokeApi 连接。

我曾尝试使用 corrutines 来做到这一点,但在使用 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!!
    }

我的APIclass

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>>

}

我的数据class

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 中调用 room 的 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

希望能帮到你,如果是这样的话先谢谢了!

[编辑]

添加了 RemoteDataSource`


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>>
}

在尝试实施@Arpit Shukla 解决方案后,由于 return 和 LiveData(Unit ) 类型,而不是像更改之前那样只是单位。

所以我像这样在 DetailFragment.kt 上调用那些方法的结果,但不起作用

精灵宝可梦DetailFragment.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,但我不知道我应该在回调上做什么。

我的其他class如下

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)
    }

}

实体方面:

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
)

因此,如果您知道某种处理这种情况的方法,请提前致谢!