Skip to content

Updated the safe function for more ergonomic code#1675

Merged
LagradOst merged 1 commit into
masterfrom
newsafeapi
Apr 29, 2025
Merged

Updated the safe function for more ergonomic code#1675
LagradOst merged 1 commit into
masterfrom
newsafeapi

Conversation

@fire-light42

Copy link
Copy Markdown
Collaborator

normalSafeApiCall and suspendSafeApiCall is way too long, and is really a misstep name wise as they are used more than safeApiCall. I propose that we simply rename them to safe and safeAsync respectively. Any thoughts @KingLucius @phisher98 @IndusAryan @int3debug @LagradOst @Blatzar as this api is used quite frequently.

@KingLucius

Copy link
Copy Markdown
Contributor

Agree 👌

@IndusAryan

IndusAryan commented Apr 28, 2025

Copy link
Copy Markdown
Contributor

I think the issue lies in usage, we use safeapicall as try catch wrapper more unlike calling an api, in my apps I use safeapicall as real api caller that wraps resource states which go through load success idle error and a trycatch util for other tasks. we can also use runCatching btw.

suspend inline fun <T> safeAPICall(
    crossinline responseFunction: suspend () -> T
): ApiState<T> {
    //ApiState.Loading
    return try {
        val response = responseFunction.invoke()
        ApiState.Success(response)
    } catch (t: Exception) {
        logError(t)
        //Log.e("SafeAPICall", "Error: ${t.localizedMessage}", t.cause)
        when (t) {
            is HttpException -> {
                val body = t.response()?.errorBody()
                when (t.response()?.code()) {
                    in 500..599 -> { // Internal Server Error range
                        //showSnackBar("Internal Server Error", color = R.color.red_line)
                        ApiState.Failure(
                            errorCode = t.response()?.code(),
                            errorString = getErrorMessage(body),
                            errorResponse = t.response()
                        )
                    }

                    /*403 -> { // FORBIDDEN (Account De-activated)
                        try {
                            (activityRef?.get() as MainActivity).navigateToDashboard()
                            Log.d("SafeComponent", "safeAPICall: 403 OCCURRED, Calling activityMain fun navigateToDashboard")
                        } catch (t: Throwable) {
                            logError(t, "ACCOUNT DEACTIVATION LOGIC")
                        }

                        ApiState.Failure(
                            errorCode = t.response()?.code(),
                            errorString = getErrorMessage(body),
                            errorResponse = t.response()
                        )
                    }*/

                    else -> {
                        ApiState.Failure(
                            errorCode = t.response()?.code(),
                            errorString = getErrorMessage(body),
                            errorResponse = t.response()
                        )
                    }
                }
            }

            is UnknownHostException -> {
                ApiState.Failure(
                    isNetworkError = true,
                    errorString = "Cannot connect to server, try again later."
                )
            }

            is SocketTimeoutException, is InterruptedIOException -> ApiState.Failure(
                isNetworkError = true,
                errorString = "Connection Timeout, Please try again later."
            )

            is IOException -> ApiState.Failure(
                isNetworkError = true,
                errorString = "Network error"
            )

            else -> ApiState.Failure(
                errorString = "Unknown error",
                errorResponse = t.message
            )
        }
    }
}

fun <T> tryCatch(
    tryThis: () -> T,
    tag: String? = tryThis.javaClass.simpleName,
    tagMessage: String? = tryThis.javaClass.simpleName,
    errorHandler: ((Throwable) -> Unit)? = null,
    finally: (() -> T)? = null
): Result<T> {
    return try {
        Log.d(tag, "Try Catching $tagMessage")
        Result.success(tryThis())
    } catch (throwable: Throwable) {
        errorHandler?.invoke(throwable) ?: logError(throwable, "$tag" )
        Result.failure(throwable)
    }
    finally {
        finally
    }
}

// Async try & catch
suspend fun <T> tryCatchAndSuspend(
    apiCall: suspend () -> T,
    tag: String = apiCall.javaClass.simpleName,
    errorHandler: ((Throwable) -> Unit)? = null,
): Result<T> {
    return try {
        Log.d(tag, "Trying & Suspending ${apiCall.javaClass.simpleName}")
        Result.success(apiCall())
    } catch (throwable: Throwable) {
        errorHandler?.invoke(throwable)
        logError(throwable, tag)
        Result.failure(throwable)
    }
}`````

@IndusAryan

Copy link
Copy Markdown
Contributor

so we can name them trycatch and trycatchasync

@LagradOst

Copy link
Copy Markdown
Contributor

so we can name them trycatch and trycatchasync

The difference is that we do not actually catch and do anything with the result, unlike functions like runCatching, so I think naming them after anything related to catch will be a misnomer.

@LagradOst LagradOst merged commit 4217a71 into master Apr 29, 2025
2 checks passed
@fire-light42 fire-light42 deleted the newsafeapi branch May 6, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants