In this demo app, I used a retrofit, Moshi, MVVM, dagger hilt, and kotlin coroutine, I got stuck in this exception, I tried to change the structure of suspended fun in PokemonApiService
delete it, change the return type from Flow<PokemonResponse>
to Flow<Response<PokemonResponse>>
but all this didn't work
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mml.pokemonkotlin, PID: 3282
java.lang.IllegalArgumentException: Unable to create converter for kotlinx.coroutines.flow.Flow<com.mml.pokemonkotlin.model.PokemonResponse>
for method PokemonApiService.getPokemons
at retrofit2.Utils.methodError(Utils.java:54)
at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:126)
at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:85)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:39)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:202)
at retrofit2.Retrofit$1.invoke(Retrofit.java:160)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy1.getPokemons(Unknown Source)
at com.mml.pokemonkotlin.data.RemoteDataSource.getPokemons(RemoteDataSource.kt:24)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel$getPokemonList$1.invokeSuspend(PokemonViewModel.kt:26)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel.getPokemonList(PokemonViewModel.kt:25)
at com.mml.pokemonkotlin.MainActivity.onCreate(MainActivity.kt:33)
at android.app.Activity.performCreate(Activity.java:7994)
at android.app.Activity.performCreate(Activity.java:7978)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.IllegalArgumentException: No JsonAdapter for kotlinx.coroutines.flow.Flow<com.mml.pokemonkotlin.model.PokemonResponse> (with no annotations)
at com.squareup.moshi.Moshi.adapter(Moshi.java:155)
at com.squareup.moshi.Moshi.adapter(Moshi.java:105)
at retrofit2.converter.moshi.MoshiConverterFactory.responseBodyConverter(MoshiConverterFactory.java:89)
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:362)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:345)
at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:124)
at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:85)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:39)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:202)
at retrofit2.Retrofit$1.invoke(Retrofit.java:160)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy1.getPokemons(Unknown Source)
at com.mml.pokemonkotlin.data.RemoteDataSource.getPokemons(RemoteDataSource.kt:24)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel$getPokemonList$1.invokeSuspend(PokemonViewModel.kt:26)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel.getPokemonList(PokemonViewModel.kt:25)
at com.mml.pokemonkotlin.MainActivity.onCreate(MainActivity.kt:33)
at android.app.Activity.performCreate(Activity.java:7994)
at android.app.Activity.performCreate(Activity.java:7978)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
BaseAppliction class
@HiltAndroidApp
class BaseApplication : Application() {
}
model classes
Pokemon.kt
@JsonClass(generateAdapter = true)
data class Pokemon(
@Json(name = "name")
var name: String,
@Json(name = "url")
var url: String
)
Pokemon response
@JsonClass(generateAdapter = true)
data class PokemonResponse(
@Json(name = "count")
var count: Int,
@Json(name = "next")
var next: String,
@Json(name = "previous")
var previous: String?,
@Json(name = "results")
var results: ArrayList<Pokemon>
)
PokemonApiService interface
interface PokemonApiService {
@GET("pokemon")
suspend fun getPokemons() : Flow<PokemonResponse>
}
Network Module
@InstallIn(SingletonComponent::class)
@Module
object NetworkModule {
@Singleton
@Provides
fun provideHttpClient(): OkHttpClient {
return OkHttpClient.Builder().readTimeout(
15, TimeUnit.SECONDS
).connectTimeout(15, TimeUnit.SECONDS).build()
}
@Singleton
@Provides
fun provideMoshi(): Moshi {
return Moshi.Builder().build()
}
@Singleton
@Provides
fun provideRetrofitInstance(
okHttpClient: OkHttpClient,
): Retrofit {
return Retrofit.Builder()
.baseUrl("https://pokeapi.co/api/v2/")
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create(provideMoshi()))
.build()
}
@Singleton
@Provides
fun provideApiService(retrofit: Retrofit): PokemonApiService {
return retrofit.create(PokemonApiService::class.java)
}
}
RemoteDataSource class
class RemoteDataSource @Inject constructor(private val pokemonApiService: PokemonApiService) {
private val TAG = "RemoteDataSource"
suspend fun getPokemons(): PokemonResponse? {
var response: PokemonResponse? = null
pokemonApiService.getPokemons()
.catch { Log.e(TAG, "getPokemons: ${it.message}") }
.onEmpty { Log.e(TAG, "empty response ") }
.collect {
response = it
}
return response
}
}
PokemonViewModel
private const val TAG = "PokemonViewModel"
@HiltViewModel
class PokemonViewModel @Inject constructor(private val remoteDataSource: RemoteDataSource) :
ViewModel() {
var pokemonsResponse: MutableLiveData<ArrayList<Pokemon>> = MutableLiveData()
fun getPokemonList(){
viewModelScope.launch {
pokemonsResponse.value = remoteDataSource.getPokemons()?.results
}
}
}
pokemon adapter
class PokemonAdapter : RecyclerView.Adapter<PokemonAdapter.PokemonViewHolder>() {
private var pokemonList = emptyList<Pokemon>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PokemonViewHolder {
val binding = PokemonItemBinding.inflate(
LayoutInflater.from(parent.context),
parent, false
)
return PokemonViewHolder(binding)
}
override fun onBindViewHolder(holder: PokemonViewHolder, position: Int) {
val currentPokemon = pokemonList[position]
holder.bind(currentPokemon)
}
fun setList(pokemonList: ArrayList<Pokemon>){
this.pokemonList = pokemonList
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return pokemonList.size
}
inner class PokemonViewHolder(private val binding: PokemonItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(pokemon: Pokemon) {
binding.pokemonNameTV.text = pokemon.name
}
}
}
and finally MainActivity.kt
private const val TAG = "MainActivity"
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var pokemonViewModel: PokemonViewModel
private lateinit var binding: ActivityMainBinding
private lateinit var pokemonAdapter: PokemonAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
pokemonViewModel = ViewModelProvider(this).get(PokemonViewModel::class.java)
pokemonAdapter = PokemonAdapter()
binding.pokemonRecyclerView.adapter = pokemonAdapter
pokemonViewModel.getPokemonList()
pokemonViewModel.pokemonsResponse.observe(this, {
if (it.isNullOrEmpty()) {
Log.e(TAG, "it is null")
} else {
pokemonAdapter.setList(it)
}
})
}
}
CodePudding user response:
As of now I'm not sure if Retrofit suports Flow
, I guess it doesn't. You can remove it from the returning type:
interface PokemonApiService {
@GET("pokemon")
suspend fun getPokemons() : PokemonResponse
}
CodePudding user response:
after doing some search I found this answer the solution for the second exception, I add this line to the dependencies
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"
I also changed the return type of getPokemons
fun from Flow
to PokemonResponse
,
finally, I changed results from ArrayList to List because the Moshi doesn't support it yet
@Json(name = "results")
var results: List<Pokemon>