slackhq/EitherNet

Unable to create converter for class com.slack.eithernet.ApiResult when using Nothing with Moshi

egek92 opened this issue · 10 comments

@WorkerThread
    suspend fun fetchPokemonList(
        page: Int,
        onSuccess: () -> Unit,
        onError: (String) -> Unit
    ) = flow {
        var pokemons = pokemonDao.getPokemonList(page)
        if (pokemons.isEmpty()) {
            when(val result = apiClient.fetchPokemonList(page = page)) {
                is ApiResult.Success -> {
                    pokemons = result.response.results
                    pokemons.forEach { pokemon -> pokemon.page = page }
                    pokemonDao.insertPokemonList(pokemons)
                    emit(pokemons)
                    onSuccess()
                }
                is ApiResult.Failure -> onError("Error!")

            }
        } else {
            emit(pokemons)
            onSuccess()
        }
    }.flowOn(Dispatchers.IO)`

ApiClient.kt

suspend fun fetchPokemonList(
        page: Int
    ) = apiService.fetchPokemonList(
        limit = PAGING_SIZE,
        offset = page * PAGING_SIZE
    )

ApiService.kt

@GET("pokemon")
    suspend fun fetchPokemonList(
        @Query("limit") limit: Int = 20,
        @Query("offset") offset: Int = 0
    ): ApiResult<PokemonResponse, Nothing>
@Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BuildConfig.BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .addConverterFactory(ApiResultConverterFactory)
            .addCallAdapterFactory(ApiResultCallAdapterFactory)
            .build()
    }

    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }

    @Provides
    @Singleton
    fun provideApiClient(apiService: ApiService): ApiClient {
        return ApiClient(apiService)
    }`

Am I missing something?

What's the stacktrace?

2020-11-19 19:41:33.965 12628-12628/com.ege.test E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.ege.test, PID: 12628
java.lang.IllegalArgumentException: Unable to create converter for class com.slack.eithernet.ApiResult
for method ApiService.fetchPokemonList
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 $Proxy3.fetchPokemonList(Unknown Source)
at com.ege.exercise.network.ApiClient.fetchPokemonList(ApiClient.kt:15)
at com.ege.exercise.repository.MainRepository$fetchPokemonList$2.invokeSuspend(MainRepository.kt:29)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.lang.IllegalArgumentException: Cannot serialize abstract class com.slack.eithernet.ApiResult
at com.squareup.moshi.ClassJsonAdapter$1.create(ClassJsonAdapter.java:80)
at com.squareup.moshi.Moshi.adapter(Moshi.java:138)
at com.squareup.moshi.Moshi.adapter(Moshi.java:98)
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 $Proxy3.fetchPokemonList(Unknown Source) 
at com.ege.exercise.network.ApiClient.fetchPokemonList(ApiClient.kt:15) 
at com.ege.exercise.repository.MainRepository$fetchPokemonList$2.invokeSuspend(MainRepository.kt:29) 
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56) 
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) 

@ZacSweers Moshi is trying to convert Nothing in which case we need to use either Unit or an Error response like:

sealed class ErrorResponse : Parcelable {

    @Parcelize
    object Unknown : ErrorResponse()

    @Parcelize
    object FetchPokemonsFailed : ErrorResponse()

    @Parcelize
    object FetchPokemonInfoFailed : ErrorResponse()
}

but idea behind this library is reducing boilerplate code so maybe we can do an improvement?

What happens if you move ApiResultConverterFactory above the moshi converter?

same issue

I can easily bypass this by using an Empty sealed class like sealed class ErrorResponse
instead of Nothing , not sure if there will be any consequences tho.

I need to look more. EitherNet should be using Nothing as a "ignore me nothing to see here" type, similar to how Void would be used in java

Apologies for the radio silence. I'm going to try to address this before 1.0

The right solution in this case is to use Unit

saket commented

Can EitherNet throw a nicer error suggesting developers to use Unit instead of Nothing?

Sure! Wanna send a PR?