A pluggable sealed API result type for modeling Retrofit responses.

Related tags

Kotlin EitherNet
Overview

EitherNet

A pluggable sealed API result type for modeling Retrofit responses.

Usage

By default, Retrofit uses exceptions to propagate any errors. This library leverages Kotlin sealed types to better model these responses with a type-safe single point of return and no exception handling needed!

The core type for this is ApiResult, where T is the success type and E is a possible error type.

ApiResult has two sealed subtypes: Success and Failure. Success is typed to T with no error type and Failure is typed to E with no success type. Failure in turn is represented by four sealed subtypes of its own: Failure.NetworkFailure, Failure.ApiFailure, Failure.HttpFailure, and Failure.UnknownFailure. This allows for simple handling of results through a consistent, non-exceptional flow via sealed when branches.

when (val result = myApi.someEndpoint()) {
  is Success -> doSomethingWith(result.response)
  is Failure -> when (result) {
    is NetworkFailure -> showError(result.error)
    is HttpFailure -> showError(result.code)
    is ApiFailure -> showError(result.error)
    is UnknownFailure -> showError(result.error)
  }
}

Usually, user code for this could just simply show a generic error message for a Failure case, but the sealed subtypes also allow for more specific error messaging or pluggability of error types.

Simply change your endpoint return type to the typed ApiResult and include our call adapter and delegating converter factory.

} val api = Retrofit.Builder() .addConverterFactory(ApiResultConverterFactory) .addCallAdapterFactory(ApiResultCallAdapterFactory) .build() .create() ">
interface TestApi {
  @GET("/")
  suspend fun getData(): ApiResult<SuccessResponse, ErrorResponse>
}

val api = Retrofit.Builder()
  .addConverterFactory(ApiResultConverterFactory)
  .addCallAdapterFactory(ApiResultCallAdapterFactory)
  .build()
  .create<TestApi>()

If you don't have custom error return types, simply use Nothing for the error type.

Decoding Error Bodies

If you want to decode error types in HttpFailures, annotate your endpoint with @DecodeErrorBody:

} ">
interface TestApi {
  @DecodeErrorBody
  @GET("/")
  suspend fun getData(): ApiResult<SuccessResponse, ErrorResponse>
}

Now a 4xx or 5xx response will try to decode its error body (if any) as ErrorResponse. If you want to contextually decode the error body based on the status code, you can retrieve a @StatusCode annotation from annotations in a custom Retrofit Converter.

// In your own converter factory.
override fun responseBodyConverter(
  type: Type,
  annotations: Array<out Annotation>,
  retrofit: Retrofit
): Converter<ResponseBody, *>? {
  val (statusCode, nextAnnotations) = annotations.statusCode()
    ?: return null
  val errorType = when (statusCode.value) {
    401 -> Unauthorized::class.java
    404 -> NotFound::class.java
    // ...
  }
  val errorDelegate = retrofit.nextResponseBodyConverter<Any>(this, errorType.toType(), nextAnnotations)
  return MyCustomBodyConverter(errorDelegate)
}

Note that error bodies with a content length of 0 will be skipped.

Plugability

A common pattern for some APIs is to return a polymorphic 200 response where the data needs to be dynamically parsed. Consider this example:

{
  "ok": true,
  "data": {
    ...
  }
}

The same API may return this structure in an error event

{
  "ok": false,
  "error_message": "Please try again."
}

This is hard to model with a single concrete type, but easy to handle with ApiResult. Simply throw an ApiException with the decoded error type in a custom Retrofit Converter and it will be automatically surfaced as a Failure.ApiFailure type with that error instance.

// In your own converter factory. class ErrorConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array, retrofit: Retrofit ): Converter? { // This returns a `@ResultType` instance that can be used to get the error type via toType() val (errorType, nextAnnotations) = annotations.errorType() ?: return null return ResponseBodyConverter(errorType.toType()) } class ResponseBodyConverter( private val errorType: Type ) : Converter { override fun convert(value: ResponseBody): String { if (value.isErrorType()) { val errorResponse = ... throw ApiException(errorResponse) } else { return SuccessResponse(...) } } } } ">
@GET("/")
suspend fun getData(): ApiResult<SuccessResponse, ErrorResponse>

// In your own converter factory.
class ErrorConverterFactory : Converter.Factory() {
  override fun responseBodyConverter(
    type: Type,
    annotations: Array<out Annotation>,
    retrofit: Retrofit
  ): Converter<ResponseBody, *>? {
    // This returns a `@ResultType` instance that can be used to get the error type via toType()
    val (errorType, nextAnnotations) = annotations.errorType() ?: return null
    return ResponseBodyConverter(errorType.toType())
  }

  class ResponseBodyConverter(
    private val errorType: Type
  ) : Converter {
    override fun convert(value: ResponseBody): String {
      if (value.isErrorType()) {
        val errorResponse = ...
        throw ApiException(errorResponse)
      } else {
        return SuccessResponse(...)
      }
    }
  }
}

Installation

Maven Central

") } ">
dependencies {
  implementation("com.slack.eithernet:eithernet:")
}

Snapshots of the development version are available in Sonatype's snapshots repository.

License

Copyright 2020 Slack Technologies, Inc.

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

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.
Comments
  • Unable to create converter for class com.slack.eithernet.ApiResult when using Nothing with Moshi

    Unable to create converter for class com.slack.eithernet.ApiResult when using Nothing with Moshi

    @WorkerThread
        suspend fun fetchPokemonList(
            page: Int,
            onSuccess: () -> Unit,
            onError: (String) -> Unit
        ) = flow {
            var pokemons = pokemonDao.getPokemonList(page)
            if (pokemons.isEmpty()) {
                when(val result = apiClient.fetchPokemonList(page = page)) {
                    is ApiResult.Success -> {
                        pokemons = result.response.results
                        pokemons.forEach { pokemon -> pokemon.page = page }
                        pokemonDao.insertPokemonList(pokemons)
                        emit(pokemons)
                        onSuccess()
                    }
                    is ApiResult.Failure -> onError("Error!")
    
                }
            } else {
                emit(pokemons)
                onSuccess()
            }
        }.flowOn(Dispatchers.IO)`
    
    

    ApiClient.kt

    suspend fun fetchPokemonList(
            page: Int
        ) = apiService.fetchPokemonList(
            limit = PAGING_SIZE,
            offset = page * PAGING_SIZE
        )
    

    ApiService.kt

    @GET("pokemon")
        suspend fun fetchPokemonList(
            @Query("limit") limit: Int = 20,
            @Query("offset") offset: Int = 0
        ): ApiResult<PokemonResponse, Nothing>
    
    @Provides
        @Singleton
        fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
            return Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(BuildConfig.BASE_URL)
                .addConverterFactory(MoshiConverterFactory.create())
                .addConverterFactory(ApiResultConverterFactory)
                .addCallAdapterFactory(ApiResultCallAdapterFactory)
                .build()
        }
    
        @Provides
        @Singleton
        fun provideApiService(retrofit: Retrofit): ApiService {
            return retrofit.create(ApiService::class.java)
        }
    
        @Provides
        @Singleton
        fun provideApiClient(apiService: ApiService): ApiClient {
            return ApiClient(apiService)
        }`
    

    Am I missing something?

    bug 
    opened by egek92 8
  • Support interop with other call adapters

    Support interop with other call adapters

    My sense was that this should Just Work™️ with other adapters, but upon further inspection we seem to be running up against a Java reflection limitation with regard to nested generic return types.

    public Single<ApiResult<String, String>> foo();
    

    Retrofit only passes on an ApiResult class and not the full ApiResult<String, String> type. Upon further digging, this seems to be coming from Java itself - the Method.getGenericReturnType() returns a Single<ApiResult>.

    Not sure we can really work around this, will dig around a bit. I suspect a proper fix, if any, would require fixing in Retrofit itself.

    enhancement 
    opened by ZacSweers 6
  • KotlinNullPointerException when api call returns 204 No Content with null response

    KotlinNullPointerException when api call returns 204 No Content with null response

    Describe the bug

    A clear and concise description of what the bug is.

    When I make an API call that returns a 204 No Content and the response in null I get

    kotlin.KotlinNullPointerException: Response from com.mediciland.datacollector.android.data.network.block.BlockApis.getBlock was null but response body type was declared as non-null
            at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:43)
            at com.slack.eithernet.ApiResultCallAdapterFactory$ApiResultCallAdapter$adapt$1$enqueue$1.onResponse(ApiResult.kt:245)
            at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
            at com.google.firebase.perf.network.InstrumentOkHttpEnqueueCallback.onResponse(InstrumentOkHttpEnqueueCallback.java:69)
            at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
            at java.lang.Thread.run(Thread.java:764)
    

    Requirements (place an x in each of the [ ])

    • [x] I've read and understood the Contributing guidelines and have done my best effort to follow them.
    • [x] I've read and agree to the Code of Conduct.
    • [x] I've searched for any related issues and avoided creating a duplicate issue.

    To Reproduce

    Steps to reproduce the behavior: The retrofit call is defined as

      @DecodeErrorBody
      @GET("block/{blockId}")
      suspend fun get(@Path("blockId") blockId: Long): ApiResult<BlockDto?, ErrorDto>
    

    Expected behavior

    As long as the the ApiResult response type is defined as nullable I would expect to not get a NPE

    wontfix 
    opened by solcott 4
  • Fix exception on annotation traversal when target is not present

    Fix exception on annotation traversal when target is not present

    Summary

    Resolves issues/50.

    As soon as we reach the end of nextAnnotations, we avoid an illegal array access. Since this is the last iteration, it's implied that resultType will be null.

    Another potential approach would be to check if the target annotation is in the original array first, and if so, we could create a new filtered copy, but I assume this is not desired so we can keep running a single array traversal.

    Requirements (place an x in each [ ])

    opened by flavioarfaria 3
  • [BUG] test fixtures do not seem to be published

    [BUG] test fixtures do not seem to be published

    Describe the bug

    I tried to add the test fixtures to my gradle project:

    testImplementation testFixtures("com.slack.eithernet:eithernet:1.2.0")
    

    But my test sources don't seem to pick up the test-fixture dependencies.

    When I run dependencyInsight i get:

    :dependencyInsight
    com.slack.eithernet:eithernet:1.2.0 FAILED
       Failures:
          - Could not resolve com.slack.eithernet:eithernet:1.2.0.
              - Unable to find a variant of com.slack.eithernet:eithernet:1.2.0 providing the requested capability com.slack.eithernet:eithernet-test-fixtures:
                   - Variant apiElements provides com.slack.eithernet:eithernet:1.2.0
                   - Variant runtimeElements provides com.slack.eithernet:eithernet:1.2.0
                   - Variant testFixturesApiElements provides com.slack.eithernet:EitherNet-test-fixtures:1.2.0
                   - Variant testFixturesRuntimeElements provides com.slack.eithernet:EitherNet-test-fixtures:1.2.0
    
    
    

    Which, according to gradle docs seem to be cause by missing gradle module metadata.

    I'm no gradle expert, so perhaps I'm doing something wrong.

    Requirements (place an x in each of the [ ])**

    • [x] I've read and understood the Contributing guidelines and have done my best effort to follow them.
    • [x] I've read and agree to the Code of Conduct.
    • [x] I've searched for any related issues and avoided creating a duplicate issue.

    Reproducible in:

    {project_name} version: 1.2.0

    bug 
    opened by Wrywulf 3
  • use plus operator from kotlin stdlib

    use plus operator from kotlin stdlib

    Summary

    Some gentle code-gardening to replace the hand-rolled array-copy-plus-add with the standard array + item syntax.

    Requirements (place an x in each [ ])

    opened by JvmName 3
  • Added utility function to create a successful ApiResult

    Added utility function to create a successful ApiResult

    Summary

    You can create an ApiResult with the syntax ApiResult.httpFailure(error), but there is no utility function to create an ApiResult from a success value, this PR adds this utility function.

    Requirements (place an x in each [ ])

    opened by danieldisu 3
  • (how) should we handle error bodies in 4xx responses?

    (how) should we handle error bodies in 4xx responses?

    Does this fit in ApiFailure or HttpFailure somewhere?

    Some ideas:

    A. Reuse ApiFailure

    In this event, upon 4xx responses we would attempt to request a delegate converter to decode the error body and report in an ApiFailure with a code.

    Awkward because it combines ApiFailure and HttpFailure

    B. Add an error to HttpFailure

    Simple, but potentially awkward because we then add a type to HttpFailure too. If we do this, I think we should reuse the same error type as ApiError

    Other questions

    • How do we enable this?
      • parameter to the call adapter factory?
      • @DecodeErrorBody opt-in annotation?
    • What if there are multiple error types? Some APIs return different errors for different status codes
      • Could we pass the error code via annotation in the converter lookup, similar to how we use @ResultType
    enhancement 
    opened by ZacSweers 3
  • Add proguard rule to keep ApiResult

    Add proguard rule to keep ApiResult

    Summary

    Add proguard rule to keep ApiResult. Fixes #26.

    Requirements (place an x in each [ ])

    opened by okamayana-tinyspeck 2
  • [FEATURE] Support declarative error model binding

    [FEATURE] Support declarative error model binding

    Description

    At @tonalfitness, we use microservices. Many endpoints across different services use HTTP error codes with meaningful bodies. Turns out the body for a given status code might be totally different for two endpoints. Because of that, the advice provided here doesn't really work for us because the status code itself is not enough to know what type to return.

    Here's what would be great:

    interface MyApi {
        @DecodeErrorBody
        @POST("/some/path")
        suspend fun someEndpoint(): ApiResult<SuccessModel, GenericErrorModel>
    }
    
    sealed class GenericErrorModel {
        @HttpError(400, 403)
        @JsonClass(generateAdapter = true)
        data class SpecificErrorModel1(
            @Json(name = "intProperty") val intProperty: Int,
        ) : GenericErrorModel()
    
        @HttpError(500)
        @JsonClass(generateAdapter = true)
        data class SpecificErrorModel2(
            @Json(name = "stringArrayProperty") val stringProperty: String,
            @Json(name = "booleanProperty") val booleanProperty: Boolean,
        ) : GenericErrorModel()
    }
    

    The approach above is possible by extracting the response type inside a custom converter factory and then checking for a potential sealed error type. The actual response status code could be matched against annotated subtypes to figure out what model to deserialize the response to.

    Correctness—like forgetting to annotate a subtype, or having more than one subtype bound to the same status code—could be enforced at compile time if either code generation or reflection is used (with a lint rule in this case).

    Not sure if this is a too specific use case of if it's something that also makes sense to a broader audience that uses EitherNet.

    Requirements (place an x in each of the [ ])

    • [x] I've read and understood the Contributing guidelines and have done my best effort to follow them.
    • [x] I've read and agree to the Code of Conduct.
    • [x] I've searched for any related issues and avoided creating a duplicate issue.
    enhancement 
    opened by flavioarfaria 1
  • [BUG] Exception when looking for an annotation that doesn't exist

    [BUG] Exception when looking for an annotation that doesn't exist

    Describe the bug

    When the target annotation is missing, we get an exception trying to access an array position that is out of bounds at line 84.

    https://github.com/slackhq/EitherNet/blob/5d1c8ec064ab4e9d645b394e3bd07364bbe8001a/src/main/java/com/slack/eithernet/Annotations.kt#L73-L95

    The reason is because nextAnnotations is smaller than the original array by 1 and the loop code is optimistic about finding the target annotation in the original array. When this doesn't happen, the executed code path merely tries to copy the original array into nextAnnotations, overflowing it at the end.

    Requirements (place an x in each of the [ ])**

    • [x] I've read and understood the Contributing guidelines and have done my best effort to follow them.
    • [x] I've read and agree to the Code of Conduct.
    • [x] I've searched for any related issues and avoided creating a duplicate issue.

    To Reproduce

    Steps to reproduce the behavior:

    1. Write your own Converter.Factory and plug it to Retrofit after ApiResultConverterFactory;
    2. Call annotations.statusCode() inside responseBodyConverter();
    3. Get the exception thrown.

    Expected behavior

    Instead of getting an exception, we should either get:

    • null to (theseAnnotations as Array<Annotation>) which means we couldn't single out the target annotation (preferable IMO);
    • Simply null.

    Reproducible in:

    Project version: 1.2.1

    OS version(s): Any

    Additional context

    If this is recognized as a bug, I'm happy to push a PR for review.

    bug 
    opened by flavioarfaria 1
  • [BUG] v1.3.0 published with invalid Gradle Module Metadata

    [BUG] v1.3.0 published with invalid Gradle Module Metadata

    Describe the bug

    The GMM for v1.3.0 does not contains invalid capabilities entries for the testFixtures variants so Gradle fails to resolve the dependency even if you are not relying on the test fixtures.

    Capabilities in v1.2.1

    Details
    // curl -sL https://repo.maven.apache.org/maven2/com/slack/eithernet/eithernet/1.2.1/eithernet-1.2.1.module | jq '.variants[].capabilities? | select( . != null )'
    [
      {
        "group": "com.slack.eithernet",
        "name": "eithernet-test-fixtures",
        "version": "1.2.1"
      }
    ]
    [
      {
        "group": "com.slack.eithernet",
        "name": "eithernet-test-fixtures",
        "version": "1.2.1"
      }
    ]
    

    Capabilities in v1.3.0

    Details
    // curl -sL https://repo.maven.apache.org/maven2/com/slack/eithernet/eithernet/1.3.0/eithernet-1.3.0.module | jq '.variants[].capabilities? | select( . != null )'
    [
      {
        "group": "",
        "name": "eithernet-test-fixtures",
        "version": "unspecified"
      }
    ]
    [
      {
        "group": "",
        "name": "eithernet-test-fixtures",
        "version": "unspecified"
      }
    ]
    [
      {
        "group": "",
        "name": "eithernet-test-fixtures",
        "version": "unspecified"
      }
    ]
    

    Build failure log

    Details
    * What went wrong:
    Execution failed for task ':api:compileDebugKotlin'.
    > Error while evaluating property 'filteredArgumentsMap' of task ':api:compileDebugKotlin'.
       > Could not resolve all files for configuration ':api:debugCompileClasspath'.
          > Could not resolve com.slack.eithernet:eithernet:1.3.0.
            Required by:
                project :api
             > Could not resolve com.slack.eithernet:eithernet:1.3.0.
                > Could not parse module metadata https://repo.maven.apache.org/maven2/com/slack/eithernet/eithernet/1.3.0/eithernet-1.3.0.module
                   > missing 'group' at /variants[2]/capabilities[0]
    

    Requirements

    • [x] I've read and understood the Contributing guidelines and have done my best effort to follow them.
    • [x] I've read and agree to the Code of Conduct.
    • [x] I've searched for any related issues and avoided creating a duplicate issue.

    To Reproduce

    Add the EitherNet 1.3.0 dependency to a project and attempt to build it

    Expected behavior

    Gradle resolves the dependency and builds the project

    Reproducible in:

    Project version: 1.3.0

    Additional context

    I noticed this in a regular Renovate PR to my app so you can repro it there but it has a rather massive dependency tree so it's probably easier to use a fresh skeleton project .

    bug 
    opened by msfjarvis 0
Releases(1.3.0)
  • 1.3.0(Dec 30, 2022)

    • Update to Kotlin 1.8.0.
    • Fix: Fix exception on annotation traversal when target is not present
    • Fix: Publish test-fixtures artifact sources.
    • Update to JVM target 11.

    What's Changed

    • Fix exception on annotation traversal when target is not present by @flavioarfaria in https://github.com/slackhq/EitherNet/pull/51
    • Update gradle to 7.4.2.+ deps by @ZacSweers in https://github.com/slackhq/EitherNet/pull/52
    • Misc updates, mostly around Kotlin 1.7.20 by @ZacSweers in https://github.com/slackhq/EitherNet/pull/54
    • Use version catalogs + target java 11 by @ZacSweers in https://github.com/slackhq/EitherNet/pull/55
    • Update misc dependencies by @ZacSweers in https://github.com/slackhq/EitherNet/pull/57

    New Contributors

    • @flavioarfaria made their first contribution in https://github.com/slackhq/EitherNet/pull/51

    Full Changelog: https://github.com/slackhq/EitherNet/compare/1.2.1...1.3.0

    Source code(tar.gz)
    Source code(zip)
  • 1.2.1(Jan 23, 2022)

    • Update to Kotlin 1.6.10.
    • Promote test-fixtures APIs to stable.
    • Update kotlinx-coroutines to 1.6.0 (test-fixtures only)
    • Fix: test-fixtures artifact module metadata using the wrong artifact ID. They should correctly resolve when using Gradle's testFixtures(...) syntax.
    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Nov 16, 2021)

  • 1.1.0(Sep 22, 2021)

    • Update Kotlin to 1.5.31
    • New: This release introduces a new EitherNetController API for testing EitherNet APIs via Test Fixtures. This is similar to OkHttp’s MockWebServer, where results can be enqueued for specific endpoints.

    Simply create a new controller instance in your test using one of the newEitherNetController() functions.

    val controller = newEitherNetController<PandaApi>() // reified type
    

    Then you can access the underlying faked api property from it and pass that on to whatever’s being tested.

    // Take the api instance from the controller and pass it to whatever's being tested
    val provider = PandaDataProvider(controller.api)
    

    Finally, enqueue results for endpoints as needed.

    // Later in a test you can enqueue results for specific endpoints
    controller.enqueue(PandaApi::getPandas, ApiResult.success("Po"))
    

    You can also optionally pass in full suspend functions if you need dynamic behavior

    controller.enqueue(PandaApi::getPandas) {
      // This is a suspend function!
      delay(1000)
      ApiResult.success("Po")
    }
    

    See its section in our README for full more details.

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Sep 9, 2021)

    Stable release!

    • Fix: Embed proguard rules to keep relevant generics information on ApiResult. This is important for new versions of R8, which otherwise strips this information.
    • Fix: Require ApiResult type arguments to be non-null (i.e. T : Any).
    • New: Add a tags API for breadcrumbing information in ApiResult. We expose a few APIs through here, namely the original OkHttp Request or Response instances when relevant.
    • ApiResult subtypes are no longer data classes since many of their underlying properties don't reliably implement equals/hashCode/immutability.
    • The deprecated ApiResult.response property is now removed.

    Thanks to @okamayana for contributing to this release!

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-rc01(Jul 19, 2021)

    This is our first (and hopefully final!) 1.0 release candidate. Please report any issues or API surface area issues now or forever hold your peace.

    Note that we consider this stable for production use, this is mostly about API stability.

    • Breaking: ApiResult and ApiResult.Failure are both now sealed interface types rather than sealed classes. For most consumers this shouldn't be a source breaking change!
    • Breaking: ApiResult.Success constructor is now internal, please use the ApiResult.success(<value>) factory.
    • Test up to JDK 17.

    Updated dependencies

    Kotlin      1.5.21
    Coroutines  1.5.1
    Dokka       1.5.0
    

    Special thanks to @danieldisu and @JvmName for contributing to this release!

    Source code(tar.gz)
    Source code(zip)
Owner
Slack
On a mission to make your working life simpler, more pleasant and more productive.
Slack
Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing functionality and pluggable UI (Jetpack Compose, SwiftUI, JS React, etc.), inspired by Badoos RIBs fork of the Uber RIBs framework

Decompose Please see the project website for documentation and APIs. Decompose is a Kotlin Multiplatform library for breaking down your code into life

Arkadii Ivanov 819 Dec 29, 2022
Android app with minimal UI to run snowflake pluggable transports proxy, based on library IPtProxy

Simple Kotlin app for testing IPtProxy's snowflake proxy on Android Essentially a button for starting and stopping a Snowflake Proxy with the default

null 2 Jun 26, 2022
Clean MVVM with eliminating the usage of context from view models by introducing hilt for DI and sealed classes for displaying Errors in views using shared flows (one time event), and Stateflow for data

Clean ViewModel with Sealed Classes Following are the purposes of this repo Showing how you can remove the need of context in ViewModels. I. By using

Kashif Mehmood 22 Oct 26, 2022
Android App Module - Activity Result Contracts

Activity Result Contract with async & await (Asynchronous) - Android App Module App Features Activity Result Contract : ActivityResultContracts Permis

Alvin Setiawan 1 Feb 1, 2022
Type-safe time calculations in Kotlin, powered by generics.

Time This library is made for you if you have ever written something like this: val duration = 10 * 1000 to represent a duration of 10 seconds(in mill

Kizito Nwose 958 Dec 10, 2022
Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel...

stateflow-flow-sharedflow-livedata Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel... StateFlow,

Raheem 5 Dec 21, 2022
Type-safe arguments for JetPack Navigation Compose using Kotlinx.Serialization

Navigation Compose Typed Compile-time type-safe arguments for JetPack Navigation Compose library. Based on KotlinX.Serialization. Major features: Comp

Kiwi.com 32 Jan 4, 2023
Movie app that receives popular movies and allows the user to search for the specific movie through the Rest API with help of retrofit library &MVVM architecture.

MovieClue Millions of movies, TV shows and people to discover. Explore now Movie app that recieves popular movies and allow the user to search for spe

Shubham Tomar 6 Mar 31, 2022
UserLoc - A API call using Retrofit to obtain list of users details and show on UI in recycler view and google map

UserLoc This uses a API call using Retrofit to obtain list of users details and

Rohit Kumar 0 Jun 22, 2022
Android News Reader app. Kotlin Coroutines, Retrofit and Realm

News Reader Android News Reader app Code that follows Packt Publishing Kotlin in Practice Video Course Example of Kotlin Coroutine usage, with Realm a

Marko Devcic 22 Oct 3, 2022
An example of a test task for creating a simple currency converter application for the Android platform. The app is developed using Kotlin, MVI, Dagger Hilt, Retrofit, Jetpack Compose.

Simple Currency Converter Simple Currency Converter Android App by Isaev Semyon An example of a test task for creating a simple currency converter app

Semyon Isaev 1 Nov 8, 2021
An tool to help developer to use Retrofit elegantly while using kotlinx.coroutines.

one An tool to help developer to use Retrofit elegantly while using kotlinx.coroutines. Feature Transform different data structs to one. {errorCode, d

ChengTao 30 Dec 27, 2022
base url not included, rxJava, Retrofit

NewsFeed NewsFeed is an android sample application built using kotlin, androidx artifacts, kotlin-extensions in MVP pattern. Libraries Used Dagger 2 D

Sagar Raval 0 Dec 29, 2021
base url not included, rxJava, Retrofit

NewsFeed NewsFeed is an android sample application built using kotlin, androidx artifacts, kotlin-extensions in MVP pattern. Libraries Used Dagger 2 D

Sagar Raval 0 Dec 29, 2021
An android application for generating random quotes for learning Room, Jetpack Navigation and Lifecycles, Retrofit

Random-Quote-Generator An android application for generating random quotes for learning Room, Jetpack Navigation and Lifecycles, Retrofit MAD Score Te

Prasoon 2 Oct 16, 2022
It is far easier to design a class to be thread-safe than to retrofit it for thread safety later

"It is far easier to design a class to be thread-safe than to retrofit it for thread safety later." (Brian Goetz - Java concurrency: Publisher: Addiso

Nguyễn Trường Thịnh 3 Oct 26, 2022
Includes jetpack compose, navigation, paging, hilt, retrofit, coil, coroutines, flow..

Nextflix-Composable A Clean Architecture App to show use of multi-module-architecture in a Jetpack Compose. The modules are as follow: app: Presentati

Talha Fakıoğlu 198 Jan 1, 2023
Educational App made with Retrofit, Coroutines, Navigation Component, Room, Dagger Hilt, Flow & Material Motion Animations.

TechHub TechHub is a sample educational app that provides courses for people who want to learn new skills in mostly tech-related areas. The goal of th

Jon Areas 32 Dec 20, 2022
Architecture With MVI using Kotlin, Coroutines, Retrofit and Unit test

Architecture With MVI using Kotlin, Coroutines, Retrofit and Unit test MVI (Model-View-Intent) streamlines the process of creating and developing appl

Ahmed Karam 4 Aug 18, 2022