Skip to content

Commit

Permalink
Release/v1.4.0 (#322)
Browse files Browse the repository at this point in the history
* (release) v1.4.0
  • Loading branch information
Foso authored May 27, 2023
1 parent a66eae9 commit e91dc7c
Show file tree
Hide file tree
Showing 97 changed files with 2,160 additions and 677 deletions.
46 changes: 45 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,51 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Security

### Bumped
1.4.0 - 2023-05-27
========================================

### Added
* #85 Added a Response class that can be used as a wrapper around the API Response, the converter for it is automatically applied. thx to @vovahost, @DATL4G

e.g.

```kotlin title=""
interface ExampleApi{
suspend fun getUser(): Response<User>
}

val user = userKtorfit.create<ExampleApi>().getUser()

if(user.isSuccessful){
user.body()
}else{
user.errorBody()
}
```

* Ktorfit is now using converters factories to apply the converters, similar to Retrofit
see more here https://foso.github.io/Ktorfit/converters/converters/
* TypeData now has a field "typeInfo" can be used to convert the Ktor HttpResponse body to the wanted type
* CallConverterFactory for replacement of CallResponseConverter
* FlowConverterFactory for replacement of FlowResponseConverter

* Added support for targets:
macosArm64, tvosArm64, tvosX64, tvosSimulatorArm64, watchosSimulatorArm64 #315

### Changed
- Upgrade dependencies: Kotlin 1.8.21

### Deprecated
* ResponseConverter, use Converter.ResponseConverter instead
* SuspendResponseConverter, use Converter.SuspendResponseConverter instead
* RequestConverter, use Converter.RequestParameterConverter instead
* See also: https://foso.github.io/Ktorfit/converters/migration/

### Removed

### Fixed

### Security

1.3.0 - 2023-05-14
========================================
Expand Down
2 changes: 1 addition & 1 deletion docs/assets/badges/platforms.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions docs/converters/converters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Converters are used to convert the HTTPResponse or parameters.

They are added inside of a Converter.Factory which will then be added to the Ktorfit builder with the **converterfactories()** function.

### Converter Types
* [ResponseConverters](./responseconverter.md)
* [SuspendResponseConverter](./suspendresponseconverter.md)
* [RequestParameterConverter](./requestparameterconverter.md)

### Existing converter factories
* CallConverterFactory
* FlowConverterFactoy
109 changes: 109 additions & 0 deletions docs/converters/example1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
Let's say you want to get an user from an API and the response you get looks like below:

```kotlin title="API response"
{
"success": true,
"user":
{
"id": 1,
"name": "Jens Klingenberg"
}
}
```

But you are only interested in the "user" object, and you want to look your interface function something like this:

```kotlin title="Example function"
@GET("/user")
suspend fun getUser(): User
```

First you need the Kotlin classes to which your JSON data is mapped to:
!!! note "This example assumes that you are Kotlin Serialization"

```kotlin
@kotlinx.serialization.Serializable
data class Envelope(val success: Boolean, val user: User)

@kotlinx.serialization.Serializable
data class User(val id: Int, val name: String)
```


Now you need a converter that can convert the HTTPResponse and return a user object.
Create a class that extends Converter.Factory

```kotlin
class UserFactory : Converter.Factory {

}
```

Because in this case **User** is the return type of a suspend function, you need to create a **SuspendResponseConverter**. Override **suspendResponseConverter()**

```kotlin
class UserFactory : Converter.Factory {
override fun suspendResponseConverter(
typeData: TypeData,
ktorfit: Ktorfit
): Converter.SuspendResponseConverter<HttpResponse, *>? {

}
}
```

Inside **suspendResponseConverter** you can decide if you want to return a converter. In our case we a converter for the
type User.
We can check that case with the typeData that we get as a parameter.

```kotlin
override fun suspendResponseConverter(
typeData: TypeData,
ktorfit: Ktorfit
): Converter.SuspendResponseConverter<HttpResponse, *>? {
if (typeData.typeInfo.type == User::class) {
...
}
return null
}
```

Next we create the SuspendResponseConverter:
```kotlin
if (typeData.typeInfo.type == User::class) {
return object : Converter.SuspendResponseConverter<HttpResponse, Any> {
override suspend fun convert(response: HttpResponse): Any {
...
}
}
}

```
Inside of **convert** we get the HttpResponse and we want to return a User object.

Now we could do the following:

When we know that this converter will always be used for a API that wraps the User inside an Envelope class, we can directly transform the body to an envelope object and just return the user object.

```kotlin
override suspend fun convert(response: HttpResponse): Any {
val envelope = response.body<Envelope>()
return envelope.user
}
```

or we can create a TypeData of Envelope and use **nextSuspendResponseConverter()** to look up the next converter that can convert the response

```kotlin
override suspend fun convert(response: HttpResponse): Any {
val typeData = TypeData.createTypeData("com.example.model.Envelope", typeInfo<Envelope>())
val envelope = ktorfit.nextSuspendResponseConverter(null, typeData)?.convert(response) as? Envelope
return envelope.user
}
```

Finally, add your converter factory to the Ktorfit Builder

```kotlin
Ktorfit.Builder().converterFactories(UserFactory()).baseUrl("foo").build()
```
116 changes: 116 additions & 0 deletions docs/converters/migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
* SuspendResponseConverter -> Converter.SuspendResponseConverter

```kotlin title="SuspendResponseConverter"
override suspend fun <RequestType> wrapSuspendResponse(
typeData: TypeData,
requestFunction: suspend () -> Pair<TypeInfo, HttpResponse>,
ktorfit: Ktorfit
): Any {
return object : Call<RequestType> {
override fun onExecute(callBack: Callback<RequestType>) {

ktorfit.httpClient.launch {
val deferredResponse = async { requestFunction() }

val (data, response) = deferredResponse.await()

try {
val res = response.call.body(data)
callBack.onResponse(res as RequestType, response)
} catch (ex: Exception) {
callBack.onError(ex)
} } } } }

override fun supportedType(typeData: TypeData, isSuspend: Boolean): Boolean {
return typeData.qualifiedName == "de.jensklingenberg.ktorfit.Call"
}
```


```kotlin title="Equivalent with converter factory:"
public class CallConverterFactory : Converter.Factory {

override fun suspendResponseConverter(
typeData: TypeData,
ktorfit: Ktorfit
): Converter.SuspendResponseConverter<HttpResponse, *>? {
if (typeData.typeInfo.type == Call::class) {
return object: Converter.SuspendResponseConverter<HttpResponse, Call<Any?>> {
override suspend fun convert(response: HttpResponse): Call<Any?> {

return object : Call<Any?> {
override fun onExecute(callBack: Callback<Any?>) {
ktorfit.httpClient.launch {
try {
val data = response.call.body(typeData.typeArgs.first().typeInfo)
callBack.onResponse(data!!, response)
} catch (ex: Exception) {
callBack.onError(ex)
} } } } } }
}
return null
}
}
```

* ResponseConverter -> Converter.ResponseConverter

```kotlin title="ResponseConverter"
override fun <RequestType> wrapResponse(
typeData: TypeData,
requestFunction: suspend () -> Pair<TypeInfo, HttpResponse?>,
ktorfit: Ktorfit
): Any {
return object : Call<RequestType> {
override fun onExecute(callBack: Callback<RequestType>) {

ktorfit.httpClient.launch {
val deferredResponse = async { requestFunction() }

try {
val (info, response) = deferredResponse.await()
val data = response!!.body(info) as RequestType
callBack.onResponse(data, response)
} catch (ex: Exception) {
callBack.onError(ex)
}

}
}

}
}

override fun supportedType(typeData: TypeData, isSuspend: Boolean): Boolean {
return typeData.qualifiedName == "de.jensklingenberg.ktorfit.Call"
}
```

```kotlin title="Equivalent with converter factory:"
public class CallConverterFactory : Converter.Factory {
override fun responseConverter(
typeData: TypeData,
ktorfit: Ktorfit
): Converter.ResponseConverter<HttpResponse, *>? {
if (typeData.typeInfo.type == Call::class) {
return object : Converter.ResponseConverter<HttpResponse, Call<Any?>> {

override fun convert(getResponse: suspend () -> HttpResponse): Call<Any?> {
return object : Call<Any?> {
override fun onExecute(callBack: Callback<Any?>) {
ktorfit.httpClient.launch {
try {
val response = getResponse()

val data = response.call.body(typeData.typeArgs.first().typeInfo)

callBack.onResponse(data, response)
} catch (ex: Exception) {
println(ex)
callBack.onError(ex)
} } } } } }
}
return null
}
}
```
29 changes: 29 additions & 0 deletions docs/converters/requestparameterconverter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# RequestParameterConverter

```kotlin
@GET("posts/{postId}/comments")
suspend fun getCommentsById(@RequestType(Int::class) @Path("postId") postId: String): List<Comment>
```

You can set RequestType at a parameter with a type to which the parameter should be converted.

Then you need to implement a Converter factory with a RequestParameterConverter.

```kotlin
class StringToIntRequestConverterFactory : Converter.Factory {
override fun requestParameterConverter(
parameterType: KClass<*>,
requestType: KClass<*>
): Converter.RequestParameterConverter? {
return object : Converter.RequestParameterConverter {
override fun convert(data: Any): Any {
//convert the data
}
}
}
}
```

```kotlin
ktorfit.converterFactories(StringToIntRequestConverterFactory())
```
Loading

0 comments on commit e91dc7c

Please sign in to comment.