Code Monkey home page Code Monkey logo

usekase's Introduction

CircleCI License Download

UseKase

Provides default UseCase implementations for the Clean Architecture

Description

UseKase is a Kotlin library which provides a set of default implementations of UseCases. Most of the UseCases depend on heavily on RxJava2.

I found my self copying these implementations in new projects again and again. Even if these contain only some lines of code it was just annoying. So I decided to create this library which provides these for me.

Because all Kotlin classes are final by default I created for each UseCase a typealias to the correct interface. This make the UseCase in Presenters testable. Because it was as annoying as copying and pasting the UseCase implementations I created an annotation processor which creates the typealias at runtime for me. You can find the code for that in the usekase-processor module.

How to use it

Get the dependencies

UseKase is available via jcenter:

repositories {
  jcenter()
}

dependencies {
  implementation("guru.stefma.cleancomponents:usekase:$useKaseVersion")

  // Alternatively (but highly recommended): usekase-processor
  kapt("guru.stefma.cleancomponents:usekase-processor:$useKaseVersion")
}

Configurate the sourceSet

First you have to add the generated source dir to your sourceSet. You can do this by adding the following into your build.gradle file:

// Add the generated source as sourceSet
android.applicationVariants.all { variant ->
    variant.addJavaSourceFoldersToModel()
    def kotlinGenerated = file("$buildDir/generated/source/kaptKotlin/${variant.name}")
    variant.addJavaSourceFoldersToModel(kotlinGenerated)
}

For pure Kotlin projects the syntax is a little bit different. See this issue for more.

Create a UseCase

You can just implement one of the provided default UseCase implementations and add the @UseCase annotation to it:

@UseCase
GetUserUseCase(
  override val executionScheduler: Scheduler = Schedulers.io(),
  override val postExecutionScheduler: Scheduler = AndroidSchedulers.mainThread(),
  private val api: BackendApi
) : SingleUseCase<User, String> {

  override fun buildUseCase(params: String): Single<User> {
    return api.getUserByUserId(params)
  }

}

If the UseKase-Processor is applied it will generated the following typealias:

typealias GetUser = SingleUseCase<User, String>

All available UseCase implementations are:

  • UseCase<R, P>
  • RxUseCase<R, P>
  • SingleUseCase<R, P>
  • FlowableUseCase<R, P>
  • ObservableUseCase<R, P>
  • MaybeUseCase<R, P>
  • CompletableUseCase<P>

Kotlin coroutine version

The usekase-coroutines module offers a base use case class CoroutineUseCase that you can extend:

@UseCase
class GetUserCoroutineUseCase(
    override val callbackDispatcher: CoroutineContext = Dispatchers.IO
) : CoroutineUseCase<User, GetUserCoroutineUseCase.Params>() {

    override suspend fun execute(params: Params): Result<User> {
        return Result.success(User(params.userId, "Thorsten", MALE, Random().nextInt(1000).toString()))
    }

    data class Params(val userId: String)
}

You have to execute it from a CoroutineScope like this:

GlobalScope.launch {
  getUserCoroutine(
    onFailure = { },
    onError = { }
  )
}

However, the module currently uses the experimental Result class as a return type. You need to enable this first by putting this in your build.gradle.kts:

tasks.withType(KotlinCompile::class.java).configureEach {
    kotlinOptions.apply {
        jvmTarget = "1.8"
        freeCompilerArgs = listOf("-Xallow-result-return-type")
    }
}

usekase's People

Contributors

earlofego avatar stefma avatar syex avatar

Watchers

 avatar  avatar  avatar

usekase's Issues

Document generics in base use case classes

Often when I'm adding a new use case and extend e.g. SingleUseCase<R, P> I'm wondering what is the input and what is the output, is it R or P? There's no documentation for those two. Maybe it would be even cooler to rename R and P to Input and Output.

Then I'm thinking probably first you name the input, and second the output, but it's the other way around what is even more confusing.

Provide @Repository annotation

Provide a repository annotation which create a interface for your class and put it as implementation for that class via reflection. ..
Or so... โ˜บ

Think about if it make sense...

Respect `internal`

If a UseCase is declared as internal the generated typealias is public.
That leads to conflicts:

error : 'public' typealias exposes 'internal' in expanded type argument containing declaration

Add generated file documentation

Currently the file (which contains the generated type alias) contains only the typealias.

Add some "documentation" at the top of the file which says that it is Autogenerated by UseKase and it should not be modified...

Issue with the kaptKotlin directory

See 7d5038f

The kaptKotlin directoy (in build/generated/source/) is not set as a sourceSet.
But the kapt directory (also in build/generated/source/) is set as a sourceSet.

I don't know it is a kapt issue or what.. So investiage why that is like it is.
(Would also be helpful if we can check how it is generated in pure java/kotlin projects).

AC:

  • Either remove the workaround
  • Or remove the todo because it works correctly...

Document "add generated source" to sourceSet

// Add the generated source (from UseKase) as sourceSet
android.applicationVariants.all { variant ->
    variant.addJavaSourceFoldersToModel()
    def kotlinGenerated = file("$buildDir/generated/source/kaptKotlin/${variant.name}")
    variant.addJavaSourceFoldersToModel(kotlinGenerated)
}

Better documentation

With the change of #37 (and therefore the refactoring to a "base usekase; usekase-rx and usekase-coroutines) we need a better documentation how to use this library...

Create gradle plugin?!

Think about if it make sense to create a gradle plugins which

  • applies the dependencies
  • provides a extension function which puts the processor arguments in

Better generate class tests

Currently the tests only checks if the generated class exist and is type of.
We should also check that the generics are the same..

Would also be good to test Java classes and/or Java generics

Support name, prefix and suffix in annotation

It should be possible to have something like

@UseCase(name = "MyAwesomeUseCase")
GetUserUserCase

This will create a
typealias MyAwesomeUseCase = ...
instead of the "default naming" (GetUser).

Additionally we should provide

@UseCase(prefix = "Generated") == GeneratedGetUserUseCase
@UseCase(suffix = "TypeAlias") == GetUserUseCaseTypeAlias

The prefix and suffix can be combined. But if the name is set it will be used as default...

kapt error when building

kapt build error with Kotlin 1.4.32, Gradle 7.0

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:kaptDebugKotlin'.
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:187)
	[...]
Caused by: org.jetbrains.kotlin.kapt3.base.util.KaptBaseError: Exception while annotation processing
	at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:87)
	at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing$default(annotationProcessing.kt:31)
	at org.jetbrains.kotlin.kapt3.base.Kapt.kapt(Kapt.kt:45)
	... 28 more
Caused by: kotlin.KotlinNullPointerException
	at me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata$data$2.invoke(KotlinMetadata.kt:55)
	at me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata$data$2.invoke(KotlinMetadata.kt:53)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata.getData(KotlinMetadata.kt)
	at guru.stefma.cleancomponents.processor.usecase.UseCaseProcessorKt.fullName(UseCaseProcessor.kt:90)
	at guru.stefma.cleancomponents.processor.usecase.UseCaseProcessorKt.access$fullName(UseCaseProcessor.kt:1)
	at guru.stefma.cleancomponents.processor.usecase.UseCaseProcessor.process(UseCaseProcessor.kt:31)
	at org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor.process(incrementalProcessors.kt:89)
	at org.jetbrains.kotlin.kapt3.base.ProcessorWrapper.process(annotationProcessing.kt:166)
	at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:802)
	at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:713)
	at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91)
	at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1043)
	at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1184)
	at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1170)
	at org.jetbrains.kotlin.kapt3.base.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:84)
	... 30 more

Add coroutines support

interface UseCase<Input : Params, Output> {

    fun buildUseCase(input: Input): Output

    fun execute(input: Input): Output = buildUseCase(input)

    interface Params {

        companion object {
            val EMPTY = EmptyParams()
        }
    }

    class EmptyParams : Params
}

interface SingleUseCase<Input : Params, Output> : UseCase<Input, Single<Output>> {

    override fun buildUseCase(input: Input): Single<Output> {
        return execute(input).observeOn(Schedulers.io())
    }
}

class DefaultSingleUseCase : SingleUseCase<Params, Boolean> {

    override fun execute(input: Params): Single<Boolean> {
        return Single.just(true)
    }
}

@SuppressLint("CheckResult")
class SingleUseCaseConsumer {

    init {
        DefaultSingleUseCase().execute(Params.EMPTY)
            .subscribe({}, {})
    }
}

interface CoroutinesUseCase<Output> : UseCase<EmptyParams, Deferred<Output>> {

    suspend fun buildUseCase(): Output

    suspend fun execute(): Output = buildUseCase()
}

class DefaultCoroutinesUseCase : CoroutinesUseCase<String> {

    override suspend fun buildUseCase(): String {
        return doStuff()
    }

    override fun buildUseCase(input: EmptyParams): Deferred<String> {
        return CompletableDeferred(doStuff())
    }

    private fun doStuff(): String = "MyString"
}

class CoroutinesUseCaseConsumer() {

    init {
        GlobalScope.launch {
            val string = DefaultCoroutinesUseCase().execute(EmptyParams()).await()
        }

        GlobalScope.launch {
            val string = DefaultCoroutinesUseCase().execute()
        }
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.