I just learn ktor-client and kotlinx.Serialization for my personal project, but then I found this error.
kotlinx.serialization.SerializationException: Serializer for class 'Result' is not found.Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
build.gradle.kts (project
plugins {
..
alias(libs.plugins.kotlin.serialization) apply false
}
build.gradle.kts (app)
plugins {
...
alias(libs.plugins.kotlin.serialization)
}
dependencies {
...
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.serialization)
...
}
Result.kt
sealed class Result<out Data, out Error> {
data class Success<out Data>(val data: Data) : Result<Data, Nothing>()
data class Failure<out Error>(val error: Error) : Result<Nothing, Error>()
inline fun <T> fold(
onSuccess: (Data) -> T,
onFailure: (Error) -> T
) = when (this) {
is Success -> onSuccess(data)
is Failure -> onFailure(error)
}
}
DTO
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class EmailListItemDto(
@SerialName("threadId")
val threadId: String? = null,
@SerialName("snippet")
val snippet: String? = null,
@SerialName("isPromotional")
val isPromotional: Boolean? = null,
@SerialName("payload")
val payload: Payload? = null,
@SerialName("subject")
val subject: String? = null,
@SerialName("isImportant")
val isImportant: Boolean? = null,
@SerialName("id")
val id: String? = null,
@SerialName("hasAttachments")
val hasAttachments: Boolean? = null,
@SerialName("timestamp")
val timestamp: Int? = null
)
@Serializable
data class Payload(
@SerialName("date")
val date: String? = null,
@SerialName("subject")
val subject: String? = null,
@SerialName("from")
val from: String? = null,
@SerialName("profileImage")
val profileImage: String? = null,
@SerialName("email")
val email: String? = null
)
Also I already tried to mark the Result and its Success and Failure class as @Serializable, but the error still there, and I tried to add these codes to proguard-rules.pro but nothing happen.
-keep class kotlinx.serialization.** { *; }
-keep class com.myPackage.Result { *; }
UPDATE
ApiService
class EmailApiServiceImpl(
private val httpClient: HttpClient
) : EmailApiService {
override suspend fun getEmailList(): Result<List<EmailListItemDto>, Failure> {
return httpClient.get("/email-list").body()
}
}
RepositoryImpl
class EmailRepositoryImpl @Inject constructor(
private val apiService: EmailApiService,
) : EmailRepository {
override suspend fun getEmailList(): Result<List<EmailListItemModel>, Failure> {
return safeApiCall<List<EmailListItemDto>, List<EmailListItemModel>>(
apiCall = { apiService.getEmailList() },
mapper = EmailListMapper()
)
}
}
safeApiCall
suspend inline fun <reified T, reified R> safeApiCall(
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
crossinline apiCall: suspend () -> Result<T, Failure>,
mapper: ResultMapper<T, R>
): Result<R, Failure> {
return withContext(ioDispatcher){
runCatching {
val result = apiCall()
result.fold(
onSuccess = {
Success(mapper.map(it))
},
onFailure = {
Result.Failure(it)
}
)
}.getOrElse { t ->
t.toResult()
}
}
}
Here is the updated description while I put the usage of the Result wrapper, also there's a code for safeApiCall that just fold it into success or failure, and for the mapper it's just usual mapper that map the data model to domain model.