Skip to content

Instantly share code, notes, and snippets.

@shibbirweb
Created October 26, 2022 10:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shibbirweb/ebb313f0c0e2a38cbf1dfff78f7a0448 to your computer and use it in GitHub Desktop.
Save shibbirweb/ebb313f0c0e2a38cbf1dfff78f7a0448 to your computer and use it in GitHub Desktop.
Android - Kotlin: Laravel Validation Error Parser (Retrofit with Moshi)
package com.data.repositories.authentication
import com.data.apis.Api
import com.data.apis.endpoints.authentication.AuthenticationApi
import com.data.apis.models.generic.LaravelValidationErrors
import com.data.apis.models.requests.auth.LoginRequest
import com.data.apis.models.requests.auth.LoginRequestErrors
import com.data.apis.models.responses.auth.LoginResponse
import com.data.helpers.Errors.*
import com.data.helpers.Resource
import com.utilities.parsers.LaravelErrorParser
class AuthenticationRepository {
private val TAG by lazy { this::class.simpleName }
private val apiServiceWithoutAuthentication by lazy {
Api.serviceWithoutAuthorization(
AuthenticationApi::class.java
)
}
// login
suspend fun login(loginRequest: LoginRequest): Resource<LoginResponse, LaravelValidationErrors<LoginRequestErrors>> =
try {
val response = apiServiceWithoutAuthentication.login(loginRequest)
when (val responseCode = response.code()) {
in 200..299 -> {
Resource.Success(response.body()!!)
}
422 -> {
val validationError =
LaravelErrorParser(response.errorBody()!!).laravelValidationErrors<LoginRequestErrors>()
Resource.Error(ValidationError(validationError!!, responseCode))
}
else -> {
Resource.Error(
ServerError(
"Something went wrong. Try again later.",
responseCode
)
)
}
}
} catch (e: Exception) {
e.printStackTrace()
Resource.Error(
error = ClientError(
"Something went wrong. Try again later."
)
)
}
}
package com.utilities.parsers
import com.data.apis.models.generic.BaseLaravelErrorsObject
import com.data.apis.models.generic.LaravelValidationErrors
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.ResponseBody
class LaravelErrorParser(private val errorResponse: ResponseBody) {
private val TAG by lazy { this::class.simpleName }
val _moshi: Moshi by lazy {
Moshi
.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
}
val _errorSource by lazy { errorResponse.source() }
// parsed response
val parsedResponse: Map<*, *> by lazy {
val jsonAdapter = _moshi.adapter(Map::class.java).lenient()
jsonAdapter.fromJson(errorResponse.source()) as Map<*, *>
}
/**
* Regular error message
*/
val message by lazy {
parsedResponse["message"].toString()
}
/**
* Validation errors
*/
val validationErrors by lazy {
try {
parsedResponse["errors"] as Map<String, List<String>>
} catch (e: Exception) {
null
}
}
/**
* Specific validation errors
*
* @param key: String Key name
* @return List of string errors or null
*/
fun validationErrorsOf(key: String): List<String>? {
return try {
validationErrors?.get(key) as List<String>
} catch (e: Exception) {
null
}
}
override fun toString(): String {
return validationErrors.toString()
}
/**
* Validation errors
*/
inline fun <reified T> validationErrors(): T? {
return try {
val jsonAdapter = _moshi.adapter(T::class.java).lenient()
jsonAdapter.fromJson(_errorSource)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
/**
* Laravel validation errors
*/
inline fun <reified T : BaseLaravelErrorsObject> laravelValidationErrors(): LaravelValidationErrors<T>? {
return try {
val type =
Types.newParameterizedType(LaravelValidationErrors::class.java, T::class.java)
val jsonAdapter = _moshi.adapter<LaravelValidationErrors<T>>(type).lenient()
jsonAdapter.fromJson(_errorSource)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
package com.data.apis.models.generic
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import kotlin.reflect.full.memberProperties
@JsonClass(generateAdapter = true)
data class LaravelValidationErrors<out T : BaseLaravelErrorsObject>(
@Json(name = "message")
val message: String? = null,
@Json(name = "errors")
val errors: T? = null,
)
// base laravel error object
abstract class BaseLaravelErrorsObject {
fun errorMessages(): String {
var errorMessage = ""
this::class.memberProperties.forEach {
val errorProperty = it.getter.call(this) as List<String>?
if (errorProperty != null) {
if (errorMessage.isNotEmpty()) {
errorMessage = errorMessage.plus("\n")
}
errorMessage = errorMessage.plus(errorProperty.joinToString("\n"))
}
}
return errorMessage
}
}
package com.activities.authenticationActivity.fragments.loginPasswordFragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.data.apis.models.requests.auth.LoginRequest
import com.data.helpers.Resource
import com.data.repositories.authentication.AuthenticationRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class LoginPasswordFragmentViewModel : ViewModel() {
private val TAG by lazy { this::class.simpleName }
private val authenticationRepository by lazy { AuthenticationRepository() }
private val _uiState = MutableStateFlow(UiState())
val uiState = _uiState as StateFlow<UiState>
fun login(loginRequest: LoginRequest, onSuccess: () -> Unit) {
viewModelScope.launch {
_uiState.value = UiState(true)
when (val resource = authenticationRepository.login(loginRequest)) {
is Resource.Error -> {
_uiState.value =
UiState(
errorMessage = resource.error?.validationError?.errors?.errorMessages()
)
}
is Resource.Success -> {
// TODO save token
onSuccess.invoke()
}
}
// _uiState.value = UiState(false)
}
}
// state
data class UiState(
val loading: Boolean = false,
val errorMessage: String? = null
)
}
package com.data.apis.models.requests.auth
import com.data.apis.models.generic.BaseLaravelErrorsObject
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class LoginRequest(
@Json(name = "username")
val username: String, // shibbirweb@gmail.com
@Json(name = "password")
val password: String, // YOUR_PASSWORD
@Json(name = "device_name")
val deviceName: String, // Postman
)
@JsonClass(generateAdapter = true)
data class LoginRequestErrors(
@Json(name = "username")
val username: List<String>?, // shibbirweb@gmail.com
@Json(name = "password")
val password: List<String>?, // YOUR_PASSWORD
@Json(name = "device_name")
val deviceName: List<String>?, // Postman
) : BaseLaravelErrorsObject()
package com.data.helpers
sealed class Resource<T, Y>(
val data: T? = null,
val error: Errors<Y>? = null
) {
class Success<T, Y>(data: T) : Resource<T, Y>(data)
class Error<T, Y>(error: Errors<Y>) : Resource<T, Y>(error = error)
}
sealed class Errors<K>(
val validationError: K? = null,
val message: String? = null,
val code: Int? = null,
) {
class ValidationError<K>(validationError: K, code: Int = 422) :
Errors<K>(validationError = validationError, code = code)
class ServerError<K>(message: String, code: Int = 500) :
Errors<K>(message = message, code = code)
class ClientError<K>(message: String, code: Int = 400) : Errors<K>(message = message)
// more errors as necessary
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment