Skip to content

Commit

Permalink
Readme Doc. Updated and async tests added.
Browse files Browse the repository at this point in the history
  • Loading branch information
AndroidPoet committed Dec 30, 2023
1 parent 6adc80f commit 67dd654
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 37 deletions.
137 changes: 110 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,22 @@ handling successes and errors clearer, simplifying error control in Ktor apps


<p align="center">
<img src="https://github.com/AndroidPoet/KtorBoost/assets/13647384/7f99beb3-10a4-4795-a8d0-d70403a2555a"
"/>
<img src="https://github.com/AndroidPoet/KtorBoost/assets/13647384/7f99beb3-10a4-4795-a8d0-d70403a2555a" style="max-width:100%;height:auto;">
</p>

## Download

[![Maven Central](https://img.shields.io/maven-central/v/io.github.androidpoet/ktor-boost.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.androidpoet%22%20AND%20a:%22ktor-boost%22)

### Without this line, you will receive an error response in the 'success' field.

```kotlin
val client = HttpClient() {
expectSuccess = true
}

```

### Gradle

Add the dependency below to your **module**'s `build.gradle` file:
Expand All @@ -54,7 +62,49 @@ sourceSets {

## Usage

## Before
```kotlin

// normal get,post,put...methods will be replaced by getResult,postResult,putResult...

val result = httpClient.getResult<String>("sample_get_url")

val result = httpClient.postResult<String>("sample_post_url")

val result = httpClient.putResult<String>("sample_put_url")

val result = httpClient.deleteResult<String>("sample_delete_url")

val result = httpClient.patchResult<String>("sample_patch_url")

val result = httpClient.headResult<String>("sample_head_url")

val result = httpClient.optionsResult<String>("sample_options_url")


// for async and await methods use these

val result = httpClient.getResultAsync<String>("sample_get_url")

val result = httpClient.postResultAsync<String>("sample_post_url")

val result = httpClient.putResultAsync<String>("sample_put_url")

val result = httpClient.deleteResultAsync<String>("sample_delete_url")

val result = httpClient.patchResultAsync<String>("sample_patch_url")

val result = httpClient.headResultAsync<String>("sample_head_url")

val result = httpClient.optionsResultAsync<String>("sample_options_url")


val deferredResult = httpClient.getResultAsync<String>("sample_get_url")
val result = deferredResult.await()


```

## Without KtorBoost

Initially, when fetching data from APIs, the code directly gets the response's content, leading to
repetitive use of the 'body()' method:
Expand All @@ -66,6 +116,13 @@ suspend fun getUserNames(): List<String> {
}


//for async oprations

suspend fun getAsyncUserNames(): Deferred<List<String>> {
return coroutineScope { async { httpClient.get("trendingMovies").body<String>() } }
}



```

Expand All @@ -84,23 +141,34 @@ viewModelScope.launch {
}
}

// Async Await

viewModelScope.launch {
try {
val deferredData = apiService.getAsyncUserNames()
deferredResult.await()
// handle data
} catch (e: Throwable) {
// handle error
}
}


```

## After
## With KtorBoost

With improvements, the API calls are now focused solely on returning values:

```kotlin
// api service claas function
suspend fun getUserNamesResult() = httpClient.getResult<List<String>>("trendingMovies")

suspend fun getUserNamesResult(): Result<List<String>> {
return httpClient.getResult<List<String>>("trendingMovies")
}

//or
//for async oprations

suspend fun getAsyncUserNames(): = httpClient.getResultAsync<List<String>>("trendingMovies")

suspend fun getUserNamesResult() = httpClient.getResult<List<String>>("trendingMovies")

```

Expand All @@ -118,17 +186,33 @@ viewModelScope.launch {
}
}


// Async Await

viewModelScope.launch {
val deferredResult = apiService.getAsyncUserNames()
val result = deferredResult.await()

result.onSuccess { data ->
// handle data
}.onFailure { error ->
// handle error
}

}

```

## Different variations and use cases

The solution offers multiple variations in response handling, enabling adaptability based on team
preferences and project needs:

If you prefer executing the response function as a suspend function, useful for nested suspend
function usage on success or error:

```kotlin

//If you prefer executing the response function as a suspend function, useful for nested suspend
//function usage on success or error:

viewModelScope.launch {
result.onSuccessSuspend { data ->
// handle data
Expand All @@ -139,9 +223,9 @@ viewModelScope.launch {

```

### For cases solely interested in success response, using getOrNull() function and handling errors manually:

```kotlin
// For cases solely interested in success response, using getOrNull() function and handling errors manually:

viewModelScope.launch {
if (result.isSuccess) {
val data = result.getOrNull()
Expand All @@ -153,9 +237,11 @@ viewModelScope.launch {

```

### Folding the response, a concise approach for clean and readable code:

```kotlin


// Folding the response, a concise approach for clean and readable code:

viewModelScope.launch {
result.fold(onSuccess = { data ->
// handle data
Expand All @@ -166,9 +252,11 @@ viewModelScope.launch {

```

### Fold also supports suspend version:

```kotlin


// Fold also supports suspend version:

viewModelScope.launch {
result.foldSuspend(
onSuccess = { data ->
Expand All @@ -193,18 +281,13 @@ Also, __[follow me](https://github.com/androidpoet)__ on GitHub for my next crea


# License

```xml
Copyright 2023 AndroidPoet (Ranbir Singh)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Licensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.
```
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ktor-client-mock="2.3.6"
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor-client" }
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor-client-mock" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }

ktor-desktop = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor-client" }
[plugins]
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
Expand Down
1 change: 1 addition & 0 deletions ktor-boost/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ kotlin {
}
val desktopMain by getting {
dependencies {
implementation(libs.ktor.desktop)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ktor-boost/src/commonMain/kotlin/KtorResultExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import io.ktor.client.request.put
suspend inline fun <reified T> HttpClient.getResult(
urlString: String,
noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { get(urlString, block).body() }
): Result<T> = runSafeSuspendCatching { get(urlString, block).body<T>() }

/**
* Performs an HTTP POST request synchronously and returns the result as a [Result] of type [T].
Expand Down
5 changes: 4 additions & 1 deletion ktor-boost/src/commonMain/kotlin/SuspendRunCatching.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import io.ktor.client.plugins.ResponseException
import kotlin.coroutines.cancellation.CancellationException

/**
Expand All @@ -13,7 +14,9 @@ public suspend inline fun <R> runSafeSuspendCatching(block: () -> R): Result<R>
Result.success(block())
} catch (c: CancellationException) {
throw c
} catch (e: Throwable) {
} catch (e: ResponseException) {
Result.failure(e)
} catch (t: Throwable) {
Result.failure(t)
}
}
124 changes: 124 additions & 0 deletions ktor-boost/src/commonTest/kotlin/HttpClientDeferredExtensionsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import io.ktor.client.HttpClient
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.respondError
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class HttpClientDeferredExtensionsTest {
private val mockEngine =
MockEngine { request ->
val responseContent =
when (request.method) {
HttpMethod.Get -> "get success"
HttpMethod.Post -> "post success"
HttpMethod.Put -> "put success"
HttpMethod.Delete -> "delete success"
HttpMethod.Patch -> "patch success"
HttpMethod.Head -> "head success"
HttpMethod.Options -> "options success"
else -> "" // Handle other methods if needed
}
respondError(HttpStatusCode.Conflict, content = responseContent)
}

private val httpClient = HttpClient(mockEngine)

@Test
fun `test getDeferredResult extension function`() {
runBlocking {
val deferredResult = httpClient.getResultAsync<String>("sample_get_url")
val result = deferredResult.await()
assertTrue(result.isSuccess)

var onSuccessCalled = true
result.onSuccess {
onSuccessCalled = false
}
assertEquals(expected = true, actual = onSuccessCalled)
result.onFailure {
// assertNotNull(it)
}

// val responseBody = result.getOrThrow()
// assertNotNull(responseBody)
// assertEquals("get success", responseBody)
}
}

@Test
fun `test postDeferredResult extension function`() {
runBlocking {
val deferredResult = httpClient.postResultAsync<String>("sample_post_url")
val result = deferredResult.await()
assertTrue(result.isSuccess)
val responseBody = result.getOrNull()
assertNotNull(responseBody)
assertEquals("post success", responseBody)
}
}

@Test
fun `test putDeferredResult extension function`() {
runBlocking {
val deferredResult = httpClient.putResultAsync<String>("sample_put_url")
val result = deferredResult.await()
assertTrue(result.isSuccess)
val responseBody = result.getOrNull()
assertNotNull(responseBody)
assertEquals("put success", responseBody)
}
}

@Test
fun `test deleteDeferredResult extension function`() {
runBlocking {
val deferredResult = httpClient.deleteResultAsync<String>("sample_delete_url")
val result = deferredResult.await()
assertTrue(result.isSuccess)
val responseBody = result.getOrNull()
assertNotNull(responseBody)
assertEquals("delete success", responseBody)
}
}

@Test
fun `test patchDeferredResult extension function`() {
runBlocking {
val deferredResult = httpClient.patchResultAsync<String>("sample_patch_url")
val result = deferredResult.await()
assertTrue(result.isSuccess)
val responseBody = result.getOrNull()
assertNotNull(responseBody)
assertEquals("patch success", responseBody)
}
}

@Test
fun `test headDeferredResult extension function`() {
runBlocking {
val deferredResult = httpClient.headResultAsync<String>("sample_head_url")
val result = deferredResult.await()
assertTrue(result.isSuccess)
val responseBody = result.getOrNull()
assertNotNull(responseBody)
assertEquals("head success", responseBody)
}
}

@Test
fun `test optionsDeferredResult extension function`() {
runBlocking {
val deferredResult = httpClient.optionsResultAsync<String>("sample_options_url")
val result = deferredResult.await()
assertTrue(result.isSuccess)
val responseBody = result.getOrNull()
assertNotNull(responseBody)
assertEquals("options success", responseBody)
}
}
}
Loading

0 comments on commit 67dd654

Please sign in to comment.