add simple converters to translate error response to an Either return type of retrofit service methods

authentification
maxime 1 year ago
parent 578c6f5ff5
commit 51f7c046ce

@ -12,7 +12,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.iqball.app" applicationId = "com.iqball.app"
minSdk = 21 minSdk = 28
targetSdk = 34 targetSdk = 34
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"

@ -9,6 +9,8 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.iqball.app.api.EitherBodyConverter
import com.iqball.app.api.EitherCallAdapterFactory import com.iqball.app.api.EitherCallAdapterFactory
import com.iqball.app.api.service.IQBallService import com.iqball.app.api.service.IQBallService
import com.iqball.app.page.RegisterPage import com.iqball.app.page.RegisterPage
@ -28,18 +30,10 @@ class MainActivity : ComponentActivity() {
val gson = Gson() val gson = Gson()
val retrofit = Retrofit.Builder() val retrofit = Retrofit.Builder()
.addConverterFactory(EitherBodyConverter.create())
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(EitherCallAdapterFactory.create(gson)) .addCallAdapterFactory(EitherCallAdapterFactory.create(gson))
.addConverterFactory(object : Converter.Factory() { .baseUrl("https://iqball.maxou.dev/api/dotnet-master/")
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
return super.responseBodyConverter(type, annotations, retrofit)
}
})
.baseUrl("http://grospc:5254")
.client( .client(
OkHttpClient.Builder() OkHttpClient.Builder()
.addInterceptor { it.proceed(it.request()) } .addInterceptor { it.proceed(it.request()) }

@ -0,0 +1,37 @@
package com.iqball.app.api
import arrow.core.Either
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class EitherBodyConverter : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
if (type !is ParameterizedType) {
return null
}
if (type.rawType != Call::class.java) {
return null
}
val eitherType = type.actualTypeArguments.first()
if (eitherType !is ParameterizedType || eitherType.rawType != Either::class.java) {
return null
}
val eitherRightType = eitherType.actualTypeArguments[1]
return retrofit.nextResponseBodyConverter<Any>(this, eitherRightType, annotations)
}
companion object {
fun create() = EitherBodyConverter()
}
}

@ -1,5 +1,8 @@
package com.iqball.app.api package com.iqball.app.api
import android.os.Build
import androidx.annotation.RequiresApi
import arrow.core.Either
import com.google.gson.Gson import com.google.gson.Gson
import com.skydoves.retrofit.adapters.arrow.EitherCallAdapterFactory import com.skydoves.retrofit.adapters.arrow.EitherCallAdapterFactory
import retrofit2.Call import retrofit2.Call
@ -13,13 +16,21 @@ class EitherCallAdapterFactory(private val gson: Gson) : CallAdapter.Factory() {
returnType: Type, returnType: Type,
annotations: Array<out Annotation>, annotations: Array<out Annotation>,
retrofit: Retrofit retrofit: Retrofit
): CallAdapter<*, *> { ): CallAdapter<*, *>? {
if (returnType !is ParameterizedType) {
return null
}
if (returnType.rawType != Call::class.java) {
return null
}
val eitherType = returnType.actualTypeArguments.first()
if (eitherType !is ParameterizedType || eitherType.rawType != Either::class.java) {
return null
}
return object : CallAdapter<Any, Any> { return object : CallAdapter<Any, Any> {
override fun responseType(): Type = returnType override fun responseType(): Type = returnType
override fun adapt(call: Call<Any>): EitherCall<Any, Any> { override fun adapt(call: Call<Any>): EitherCall<Any, Any> {
val callType = returnType as ParameterizedType
val eitherType = callType.actualTypeArguments.first() as ParameterizedType
return EitherCall(gson, eitherType, call) return EitherCall(gson, eitherType, call)
} }
} }

@ -3,26 +3,26 @@ package com.iqball.app.api.service
import arrow.core.Either import arrow.core.Either
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import retrofit2.Call
import retrofit2.HttpException
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST import retrofit2.http.POST
interface AuthService { interface AuthService {
@Serializable @Serializable
data class AuthResponse(val token: String, val expirationDate: LocalDateTime) data class AuthResponse(val token: String, val expirationDate: String)
@Serializable @Serializable
data class RegisterRequest(val username: String, val email: String, val password: String) data class RegisterRequest(val username: String, val email: String, val password: String)
@POST("/auth/register") @POST("auth/register")
suspend fun register(@Body req: RegisterRequest): APIResult<AuthResponse> suspend fun register(@Body req: RegisterRequest): APIResult<AuthResponse>
data class LoginRequest(val email: String, val password: String) data class LoginRequest(val email: String, val password: String)
@POST("/auth/token") @POST("auth/token")
suspend fun login(@Body req: LoginRequest): Call<AuthResponse> suspend fun login(@Body req: LoginRequest): APIResult<AuthResponse>
} }

@ -6,4 +6,4 @@ import retrofit2.Call
typealias ErrorResponseResult = Map<String, Array<String>> typealias ErrorResponseResult = Map<String, Array<String>>
typealias APIResult<R> = Either<ErrorResponseResult, R> typealias APIResult<R> = Either<ErrorResponseResult, R>
interface IQBallService : AuthService interface IQBallService : AuthService, UserService

@ -0,0 +1,11 @@
package com.iqball.app.api.service
import retrofit2.http.GET
import retrofit2.http.Header
interface UserService {
data class UserDataResponse(val teams: List<Any>, val tactics: List<Any>)
@GET("user-data")
suspend fun getUserData(@Header("Authorization") auth: String): APIResult<UserDataResponse>
}

@ -8,8 +8,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import arrow.core.Either import arrow.core.Either
import com.iqball.app.api.service.AuthService
import com.iqball.app.api.service.AuthService.RegisterRequest import com.iqball.app.api.service.AuthService.RegisterRequest
import com.iqball.app.api.service.IQBallService import com.iqball.app.api.service.IQBallService
import com.iqball.app.session.Authentication
import com.iqball.app.session.MutableSession
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -22,26 +25,30 @@ fun RegisterPage(service: IQBallService) {
var text by remember { mutableStateOf("No message !") } var text by remember { mutableStateOf("No message !") }
runBlocking { runBlocking {
val result = service.register(RegisterRequest("abcdefg", "a@m.com", "123456")) val result = service.login(AuthService.LoginRequest("maxime@mail.com", "123456"))
when (result) { when (result) {
is Either.Left -> println("Error : " + result.value) is Either.Left -> {
is Either.Right -> println("Success : " + result.value) println("Error : " + result.value)
text = result.toString()
}
is Either.Right -> {
val token = result.value.token
val userDataResponse = service.getUserData(token)
when (userDataResponse) {
is Either.Left -> println("Error User Data : " + userDataResponse.value)
is Either.Right -> println("Success User Data : " + userDataResponse.value)
}
text = userDataResponse.toString()
}
} }
println(result) println(result)
text = result.toString()
Log.i("%", result.toString()) Log.i("%", result.toString())
} }
Text(text = text) Text(text = text)
} }
suspend fun updateTextIn5Sec(setText: (String) -> Unit) = coroutineScope {
launch(Dispatchers.IO) {
delay(5000)
setText("test")
}
}

@ -0,0 +1,5 @@
package com.iqball.app.session
import kotlinx.datetime.LocalDateTime
data class Authentication(val token: String, val expirationDate: LocalDateTime)

@ -0,0 +1,5 @@
package com.iqball.app.session
interface MutableSession : Session {
override var auth: Authentication
}

@ -0,0 +1,5 @@
package com.iqball.app.session
interface Session {
val auth: Authentication
}
Loading…
Cancel
Save