Skip to content

Commit

Permalink
[feat] Kotlin Result in API call
Browse files Browse the repository at this point in the history
  • Loading branch information
rustamsafarovrs committed Jun 25, 2024
1 parent 12eb001 commit 74e0665
Show file tree
Hide file tree
Showing 16 changed files with 113 additions and 218 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import java.io.File
import java.io.FileInputStream
import java.util.*
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
Expand Down Expand Up @@ -124,6 +123,7 @@ dependencies {
implementation(libs.okhttp.logging)
implementation(libs.retrofit)
implementation(libs.retrofit.moshi)
implementation(libs.retrofit.adapters.result)
implementation(libs.moshi)
implementation(libs.moshi.kotlin)
ksp(libs.moshi.kotlin.codegen)
Expand Down
9 changes: 4 additions & 5 deletions app/src/main/java/tj/rsdevteam/inmuslim/data/api/Api.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package tj.rsdevteam.inmuslim.data.api

import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
Expand All @@ -20,14 +19,14 @@ import tj.rsdevteam.inmuslim.data.models.network.UpdateMessagingIdResponse
interface Api {

@GET("GetRegions")
suspend fun getRegions(): Response<GetRegionsResponse>
suspend fun getRegions(): Result<GetRegionsResponse>

@POST("GetTiming")
suspend fun getTiming(@Body body: GetTimingBody): Response<GetTimingResponse>
suspend fun getTiming(@Body body: GetTimingBody): Result<GetTimingResponse>

@POST("RegisterUser")
suspend fun registerUser(@Body body: RegisterUserBody): Response<RegisterUserResponse>
suspend fun registerUser(@Body body: RegisterUserBody): Result<RegisterUserResponse>

@POST("UpdateMessagingId")
suspend fun updateMessagingId(@Body body: UpdateMessagingIdBody): Response<UpdateMessagingIdResponse>
suspend fun updateMessagingId(@Body body: UpdateMessagingIdBody): Result<UpdateMessagingIdResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tj.rsdevteam.inmuslim.data.exceptions

class ApiException(msg: String?) : Exception(msg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tj.rsdevteam.inmuslim.data.exceptions

class ConnectionTimeoutException : Exception("No internet connection")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tj.rsdevteam.inmuslim.data.exceptions

class UnknownException(error: String?) : Exception("Something went wrong...\n$error")
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tj.rsdevteam.inmuslim.data.models.network

import com.squareup.moshi.JsonClass

/**
* Created by Rustam Safarov on 6/25/24.
* github.com/rustamsafarovrs
*/

@JsonClass(generateAdapter = true)
data class MessageResponse(
val result: Int,
val msg: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,11 @@ package tj.rsdevteam.inmuslim.data.models.network
* github.com/rustamsafarovrs
*/

data class Resource<out T>(
val status: Status,
val data: T?,
val message: String?,
val exception: Exception? = null
) {
sealed class Resource<out T : Any>(open val data: T? = null) {

companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
class InProgress<T : Any>(data: T? = null) : Resource<T>(data)

fun <T> error(msg: String?, data: T?, exception: Exception?): Resource<T> {
return Resource(Status.ERROR, data, msg, exception)
}
class Success<T : Any>(override val data: T) : Resource<T>(data)

fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}

}

enum class Status {
LOADING,
SUCCESS,
ERROR
class Error<T : Any>(data: T? = null, val error: Throwable? = null) : Resource<T>(data)
}
Original file line number Diff line number Diff line change
@@ -1,98 +1,32 @@
package tj.rsdevteam.inmuslim.data.repositories

import com.squareup.moshi.Moshi
import okhttp3.ResponseBody
import retrofit2.HttpException
import tj.rsdevteam.inmuslim.data.constants.Constants
import tj.rsdevteam.inmuslim.data.models.network.ApiError
import tj.rsdevteam.inmuslim.data.exceptions.ApiException
import tj.rsdevteam.inmuslim.data.exceptions.ConnectionTimeoutException
import tj.rsdevteam.inmuslim.data.exceptions.UnknownException
import tj.rsdevteam.inmuslim.data.models.network.MessageResponse
import tj.rsdevteam.inmuslim.data.models.network.Resource
import java.io.IOException
import java.net.UnknownHostException

/**
* Created by Rustam Safarov on 8/13/23.
* github.com/rustamsafarovrs
*/

@Suppress("TooGenericExceptionCaught")
class ErrorHandler constructor(private val moshi: Moshi) {

fun <T> getError(e: Throwable): Resource<T> {
val exception = handleException(e)
return getErrorResource(exception)
}

fun <T> getError(httpStatusCode: Int, responseBody: ResponseBody?): Resource<T> {
val exception = handleHttpException(httpStatusCode, responseBody?.string())
return getErrorResource(exception)
}

fun <T> getError(httpStatusCode: Int, errorBody: ResponseBody?, body: Any?): Resource<T> {
if (errorBody != null) {
val exception = handleHttpException(httpStatusCode, errorBody.string())
return getErrorResource(exception)
class ErrorHandler {

fun <T : Any> getError(result: Result<*>): Resource<T> {
return if (result.getOrNull() is MessageResponse) {
Resource.Error(error = ApiException((result.getOrThrow() as MessageResponse).msg))
} else if (result.exceptionOrNull() is UnknownHostException) {
Resource.Error(error = ConnectionTimeoutException())
} else if (result.exceptionOrNull() is HttpException) {
Resource.Error(error = ApiException(result.exceptionOrNull()?.localizedMessage))
} else if (result.exceptionOrNull() is IOException) {
Resource.Error(error = ConnectionTimeoutException())
} else {
val tmp = moshi.adapter(Any::class.java)
val str: String = tmp.toJson(body)
val exception = handleHttpException(httpStatusCode, str)
return getErrorResource(exception)
}
}

private fun handleException(e: Throwable): Exception =
when (e) {
is HttpException -> {
handleHttpException(e.code(), e.response()?.errorBody()?.string())
}

is IOException -> {
ConnectionTimeoutException()
}

else -> {
e.printStackTrace()
UnknownException(e.message.toString())
}
Resource.Error(error = UnknownException(result.exceptionOrNull()?.localizedMessage))
}

private fun handleHttpException(code: Int, errorBody: String?): Exception =
try {
val error: ApiError = moshi.adapter(ApiError::class.java).fromJson(errorBody!!)!!
if (code == Constants.UNAUTHORIZED) {
SessionException()
} else {
ApiException(error)
}
} catch (e: Exception) {
// Logger.exception(e)
UnknownException(e.message.toString())
}

private fun <T> getErrorResource(e: Exception): Resource<T> {
var message: String? = null

when (e) {
is ApiException -> {
message = e.apiError.msg
}

is ConnectionTimeoutException -> {
message = "No internet connection"
}

is UnknownException -> {
message = "Something went wrong..."
}

is SessionException -> {
message = e.message.toString()
}
}
return Resource.error(msg = message, data = null, exception = e)

}
}

class SessionException : Exception("Unauthorized")
class UnknownException(error: String) : Exception("Something went wrong...\n$error")
class ConnectionTimeoutException : Exception("No internet connection")
class ApiException(val apiError: ApiError) : Exception(apiError.msg)
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,13 @@ class RegionRepository
fun saveRegionId(id: Long) = preferences.saveRegionId(id)

fun getRegions(): Flow<Resource<List<Region>>> = flow {
try {
emit(Resource.loading())
val response = api.getRegions()
delay(Constants.MIDDLE_DELAY)
if (response.isSuccessful && response.body()?.result == 0) {
emit(Resource.success(response.body()!!.regions))
} else {
emit(
errorHandler.getError(
response.code(),
response.errorBody(),
response.body()
)
)
}
} catch (e: Exception) {
emit(errorHandler.getError(e))
emit(Resource.InProgress())
val result = api.getRegions()
delay(Constants.MIDDLE_DELAY)
if (result.isSuccess && result.getOrNull()?.result == 0) {
emit(Resource.Success(result.getOrThrow().regions))
} else {
emit(errorHandler.getError(result))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,13 @@ class TimingRepository
) {

fun getTiming(): Flow<Resource<GetTimingResponse>> = flow {
try {
emit(Resource.loading())
val response = api.getTiming(GetTimingBody(regionId = preferences.getRegionId()))
delay(Constants.MIDDLE_DELAY)
if (response.isSuccessful && response.body()?.result == 0) {
emit(Resource.success(response.body()!!))
} else {
emit(
errorHandler.getError(
response.code(),
response.errorBody(),
response.body()
)
)
}
} catch (e: Exception) {
emit(errorHandler.getError(e))
emit(Resource.InProgress())
val result = api.getTiming(GetTimingBody(regionId = preferences.getRegionId()))
delay(Constants.MIDDLE_DELAY)
if (result.isSuccess && result.getOrNull()?.result == 0) {
emit(Resource.Success(result.getOrThrow()))
} else {
emit(errorHandler.getError(result))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,63 +30,42 @@ class UserRepository
) {

fun registerUser(body: RegisterUserBody): Flow<Resource<RegisterUserResponse>> = flow {
try {
emit(Resource.loading())
val response = api.registerUser(body)
if (response.isSuccessful && response.body()?.result == 0) {
preferences.saveUserId(response.body()!!.id)
emit(Resource.success(response.body()!!))
} else {
emit(
errorHandler.getError(
response.code(),
response.errorBody(),
response.body()
)
)
}
} catch (e: Exception) {
emit(errorHandler.getError(e))
emit(Resource.InProgress())
val result = api.registerUser(body)
if (result.isSuccess && result.getOrNull()?.result == 0) {
preferences.saveUserId(result.getOrThrow().id)
emit(Resource.Success(result.getOrThrow()))
} else {
emit(errorHandler.getError(result))
}
}

fun updateMessagingId(): Flow<Resource<UpdateMessagingIdResponse>> = flow {
try {
emit(Resource.loading())
var msgid = ""
withContext(Dispatchers.IO) {
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
msgid = token
}
}
val response = api.updateMessagingId(
UpdateMessagingIdBody(
id = preferences.getUserId(),
msgid = msgid
)
)
if (response.isSuccessful && response.body()?.result == 0) {
preferences.saveFirebaseToken(msgid)
emit(Resource.success(response.body()!!))
} else {
emit(
errorHandler.getError(
response.code(),
response.errorBody(),
response.body()
)
)
emit(Resource.InProgress())
var msgid = ""
withContext(Dispatchers.IO) {
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
msgid = token
}
} catch (e: Exception) {
emit(errorHandler.getError(e))
}
val result = api.updateMessagingId(UpdateMessagingIdBody(id = preferences.getUserId(), msgid = msgid))
if (result.isSuccess && result.getOrNull()?.result == 0) {
preferences.saveFirebaseToken(msgid)
emit(Resource.Success(result.getOrThrow()))
} else {
emit(errorHandler.getError(result))
}
}

fun needRegister(): Boolean {
return preferences.getUserId() == -1L
}

fun getUserId() = preferences.getUserId()
fun getUserId(): Long {
return preferences.getUserId()
}

fun getFirebaseToken() = preferences.getFirebaseToken()
fun getFirebaseToken(): String {
return preferences.getFirebaseToken()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tj.rsdevteam.inmuslim.di.modules

import android.content.Context
import com.skydoves.retrofit.adapters.result.ResultCallAdapterFactory
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
Expand Down Expand Up @@ -62,6 +63,7 @@ class AppModule {
fun provideRetrofit(moshi: Moshi, client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(ResultCallAdapterFactory.create())
.client(client)
.baseUrl(BuildVars.BASE_URL)
.build()
Expand Down
Loading

0 comments on commit 74e0665

Please sign in to comment.