Skip to content

sanmiAde/Microya-Kotlin

 
 

Repository files navigation

UsageIssuesContributingLicense

Microya

This is the kotlin version of Microya. A network abstraction layer.

Usage

Step 1: Defining your Endpoints

Create an Api sealed class with all supported endpoints as data class with the request parameters/data specified as parameters.

In order to allow Microya-Kotlin serialize your request classes, you need to declare a custom SerializersModule and register your classes as polymorphic subclasses of Any.

object PostmanSerializer {
    private val module = SerializersModule {
        polymorphic(Any::class) {
            subclass(FooBar::class)
        }
    }

    val requestJsonFormatter = Json {
        serializersModule = module
    }
}

For example, when writing a client for the Postman Echo:

sealed class PostmanEchoEndpoint : Endpoint() {
    data class Index(val sortedBy: String) : PostmanEchoEndpoint()
    data class Post(val fooBar: FooBar) : PostmanEchoEndpoint()
    data class Get(val fooBarID: String) : PostmanEchoEndpoint()
    data class Patch(val fooBarID: String, val fooBar: FooBar) : PostmanEchoEndpoint()
    object Delete : PostmanEchoEndpoint()
}

Step 2: Making your Api Endpoint compliant by overriding variables in the Endpoint abstract class.

sealed class PostmanEchoEndpoint : Endpoint() {
    data class Index(val sortedBy: String) : PostmanEchoEndpoint()
    data class Post(val fooBar: FooBar) : PostmanEchoEndpoint()
    data class Get(val fooBarID: String) : PostmanEchoEndpoint()
    data class Patch(val fooBarID: String, val fooBar: FooBar) : PostmanEchoEndpoint()
    object Delete : PostmanEchoEndpoint()

    override val subpath: String
        get() = when (this) {
            is Get -> "get/${fooBarID}"
            is Index -> "get"
            is Patch -> "patch/${fooBarID}"
            is Post -> "post"
            Delete -> "delete"
        }

    override val method: HttpMethod
        get() = when (this) {
            is Get -> HttpMethod.Get
            is Index -> HttpMethod.Get
            is Patch -> HttpMethod.Patch(fooBar)
            is Post -> HttpMethod.Post(fooBar)
            Delete -> HttpMethod.Delete
        }
    override val headers: Map<String, String>
        get() = mapOf(
            "Content-Type" to "application/json",
            "Accept" to "application/json",
            "Accept-Language" to Locale.getDefault().language
        )

    override val queryParameters: Map<String, String>
        get() = when (this) {
            is Index -> mapOf("sortedBy" to sortedBy)
            else -> emptyMap()
        }
    override val mockedResponse: MockedResponse?
        get() = null
}

Step 3a: Calling your API endpoint with the Result type

Call an API endpoint providing a response type and an also an error type.

performRequest<PostmanEchoResponse, PostmanEchoError>(PostmanEchoEndpoint.Index(sortedBy = "updatedAt"))

Here's a full example of a call you could make with Mircoya-Kotlin:

val provider = ApiProvider.Builder().baseUrl("https://postman-echo.com")
                   .client(OkHttpClient())
                   .plugins(listOf(HttpAuthPlugin(HttpAuthPlugin.Scheme.BASIC, "abc123")
                   .requestJsonFormatter(PostmanSerializer.requestJsonFormatter)
                   .responseJsonFormatter(PostmanSerializer.responseJsonFormatter)
                   .build()

provider.performRequest<PostmanEchoResponse, PostmanEchoError>(
                         PostmanEchoEndpoint.Index(sortedBy = "updatedAt")
                     ).onSuccess { postmanEchoResponse: PostmanEchoResponse ->
                         // use the already decoded result
                     }.onFailure { jsonApiException: JsonApiException ->
                         // error handling
                     }

Note that you can use a throwing get() function instead of onSuccess and onFailure callbacks or a when statement.

val result = sampleApiProvider.performRequest<PostmanEchoResponse, PostmanEchoError>(PostmanEchoEndpoint.Index(sortedBy = "updatedAt")).get()

You can also use more functional methods like map(), andThen(), mapEither(), mapError().

Microya-Kotlin returns a JsonApiException when the request is unsuccessful. You can call .getBody() after type casting the jsonApiException to a ClientError

Step 3b: Multipart File Upload

Microya-Kotlin supports file uploads using multipart requests. To perform multipart request with Microya-kotlin, you'll need to use the performUploadRequest function. For example, here's how to upload an image to Imgur with Microya-Kotlin. Model you request class. The request class takes a list of FileDataPart. These are file parts to be included in multipart request. Take note of the @Transient annotation. This means the fileDataParts won't be serialized. The fileDataParts would be included in a multipart request by Microya-Kotlin.

@Serializable
data class UploadImageRequest(
    val title: String,
    val description: String,
    @Transient
    val fileDataParts: List<FileDataPart> = emptyList()
)

Next step is modeling the ImgurEndpoint. The only difference between endpoint with file uploads and normal endpoints is the fileDataParts variable. Endpoints with uploads override this variable.

sealed class ImgurEndpoint : Endpoint() {
    data class UploadImageEndpoint(val body: UploadImageRequest) : ImgurEndpoint()
    override val subpath: String
        get() = when (this) {
            is UploadImageEndpoint -> "3/image"
        }
    override val method: HttpMethod
        get() = when (this) {
            is UploadImageEndpoint -> HttpMethod.Post(body)
        }
    override val headers: Map<String, String>
        get() = emptyMap()
    override val queryParameters: Map<String, String>
        get() = emptyMap()
    override val mockedResponse: MockedResponse?
        get() = null
    override val fileDataParts: List<FileDataPart>
        get() = when (this) {
            is UploadImageEndpoint -> body.fileDataParts
        }
}

Make the uploadRequest using performUploadRequest.

  val result: ImgurResponse<ImgurSuccessData> =
                uploadFileSampleApiProvider.performUploadRequest<ImgurResponse<ImgurSuccessData>, ImgurResponse<ImgurErrorData>>(
                                 uploadEndpoint
                             ).get()!!

Plugins

The builder of ApiProvider accepts a list of Plugin objects. You can implement your own plugins or use one of the existing ones in the Plugins directory. Here's are the callbacks a custom Plugin subclass can override:

    // Called to modify a request before sending.
    fun modifyRequest(request: Request, endpoint: Endpoint): Request

    // Called immediately before a request is sent.
    fun beforeRequest(request: Request)

    // Called after a response has been received & decoded, but before calling the completion handler.
    fun <T> afterRequest(response: Response, typedResult: T? = null, endpoint: Endpoint)
Toggle me to see a full custom plugin example Here's a possible implementation of a RequestResponseLoggerPlugin that logs using `Log.d()`
object RequestResponseLoggerPlugin : Plugin {
    override fun <T> afterRequest(response: Response, typedResult: T?, endpoint: Endpoint) {
        Log.d("Network Logger:", response.toString())

    }

    override fun beforeRequest(request: Request) {
        Log.d("Network Logger:", request.toString())
    }

    override fun modifyRequest(request: Request, endpoint: Endpoint): Request = request
}

Testing

Microya-Kotlin supports mocking responses in your tests. To do that, just initialize a different ApiProvider in your tests and specify with a given delay and scheduler as the mockingBehavior parameter.

Now, instead of making actual calls, Microya-Kotlin will respond with the provided mockedResponse computed property in your Endpoint type.

ApiProvider.Builder().baseUrl("https://postman-echo.com")
    .client(OkHttpClient())
    .plugins(listOf(HttpAuthPlugin(HttpAuthPlugin.Scheme.BASIC, "abc123"))
    .requestJsonFormatter(PostmanSerializer.requestJsonFormatter)
    .responseJsonFormatter(PostmanSerializer.responseJsonFormatter)
    .mockingBehaviour(MockingBehaviour(Duration.ZERO))
    .build()

You can also define custom mockedResponse with in your mockingBehavior

ApiProvider.Builder().baseUrl("https://postman-echo.com")
    .client(OkHttpClient())
    .plugins(listOf(HttpAuthPlugin(HttpAuthPlugin.Scheme.BASIC, "abc123")))
    .requestJsonFormatter(PostmanSerializer.requestJsonFormatter)
    .responseJsonFormatter(PostmanSerializer.responseJsonFormatter)
    .mockingBehaviour(MockingBehaviour(Duration.ZERO) { endpoint: Endpoint ->
        when (endpoint) {
            is PostmanEchoEndpoint.Index -> {
                endpoint.mockResponseObject(200, EmptyBodyResponse())
            }
            is PostmanEchoEndpoint.Post -> {
                null
            }
            is PostmanEchoEndpoint.Get -> {
                endpoint.mockResponseObject(200, FooBar("Sakata", "Gintoki"))
            }

            is PostmanEchoEndpoint.Patch -> {
                endpoint.mockResponseObject(200, FooBar("test", "1234"))
            }
            else -> throw  IllegalArgumentException("Endpoint doesn't exist.")
        }
    })
    .build()

Contributing

See the file CONTRIBUTING.md.

License

This library is released under the MIT License. See LICENSE for details.

About

Kotlin version of Microya

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Kotlin 76.1%
  • Swift 21.0%
  • Shell 1.6%
  • Ruby 1.3%