From e0321577d060b81cdc564a4b7073078d04329064 Mon Sep 17 00:00:00 2001 From: Ondrej Kalman Date: Wed, 11 Mar 2026 13:14:51 +0100 Subject: [PATCH 1/5] Chore: update workflows to JDK 17 and target 6.x branch --- .github/workflows/publish_snapshot.yml | 2 +- build.gradle.kts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_snapshot.yml b/.github/workflows/publish_snapshot.yml index 85fbcfbc..4b54a2cb 100644 --- a/.github/workflows/publish_snapshot.yml +++ b/.github/workflows/publish_snapshot.yml @@ -24,7 +24,7 @@ jobs: shell: bash run: ./gradlew build - name: Build & publish snapshot - run: ./gradlew publish --no-daemon --no-parallel --stacktrace + run: ./gradlew publish --no-daemon --no-parallel --stacktrace -PVERSION_NAME=6.X.X-SNAPSHOT env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_REPOSITORY_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_REPOSITORY_PASSWORD }} diff --git a/build.gradle.kts b/build.gradle.kts index 1606b880..d618e1cb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,9 @@ allprojects { } subprojects { + group = ProjectSettings.group + version = findProperty("VERSION_NAME") as String? ?: ProjectSettings.version + apply(plugin = Deps.Plugins.ktlint) ktlint { From 218e401259e89358b289b5d1f4548ef54207625c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rudolf=20Hlad=C3=ADk?= Date: Fri, 13 Mar 2026 12:01:34 +0100 Subject: [PATCH 2/5] feat(cr-usecases): add context param-based use case execution --- cr-usecases-test/build.gradle.kts | 2 + .../crusecases/test/FlowUseCaseTests.kt | 1 + .../arkitekt/crusecases/test/UseCaseTests.kt | 1 + cr-usecases/build.gradle.kts | 5 + .../crusecases/CoroutineScopeOwner.kt | 302 +----------------- .../arkitekt/crusecases/FlowUseCaseConfig.kt | 91 ++++++ .../crusecases/FlowUseCaseExecution.kt | 114 +++++++ .../arkitekt/crusecases/UseCaseConfig.kt | 69 ++++ .../arkitekt/crusecases/UseCaseExecution.kt | 105 ++++++ .../decompose/coroutines/ValueStateFlow.kt | 2 + example/build.gradle.kts | 2 + .../CoroutinesResultViewModel.kt | 1 + .../arkitekt/sample/ui/form/FormViewModel.kt | 4 +- .../sample/ui/login/LoginViewModel.kt | 2 +- 14 files changed, 399 insertions(+), 302 deletions(-) create mode 100644 cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseConfig.kt create mode 100644 cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseExecution.kt create mode 100644 cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseConfig.kt create mode 100644 cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseExecution.kt diff --git a/cr-usecases-test/build.gradle.kts b/cr-usecases-test/build.gradle.kts index dbec9c79..823fada6 100644 --- a/cr-usecases-test/build.gradle.kts +++ b/cr-usecases-test/build.gradle.kts @@ -38,6 +38,8 @@ android { kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + + freeCompilerArgs.add("-Xcontext-parameters") } } diff --git a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt index c621068b..414c5e6b 100644 --- a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt +++ b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt @@ -1,6 +1,7 @@ package app.futured.arkitekt.crusecases.test import app.futured.arkitekt.crusecases.FlowUseCase +import app.futured.arkitekt.crusecases.execute import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow diff --git a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt index cd282020..ea2902b2 100644 --- a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt +++ b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt @@ -1,6 +1,7 @@ package app.futured.arkitekt.crusecases.test import app.futured.arkitekt.crusecases.UseCase +import app.futured.arkitekt.crusecases.execute import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/cr-usecases/build.gradle.kts b/cr-usecases/build.gradle.kts index 0b45422f..2be46d0e 100644 --- a/cr-usecases/build.gradle.kts +++ b/cr-usecases/build.gradle.kts @@ -19,6 +19,11 @@ kotlin { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } + + compilerOptions { + freeCompilerArgs.add("-Xcontext-parameters") + } + sourceSets { val commonMain by getting { dependencies { diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt index 8750ecbb..fb23271d 100644 --- a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt @@ -4,22 +4,14 @@ import app.futured.arkitekt.crusecases.error.UseCaseErrorHandler import app.futured.arkitekt.crusecases.utils.rootCause import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch /** * This interface gives your class ability to execute [UseCase] and [FlowUseCase] Coroutine use cases. * You may find handy to implement this interface in custom Presenters, ViewHolders etc. - * It is your responsibility to cancel [coroutineScope] when when all running tasks should be stopped. + * It is your responsibility to cancel [coroutineScope] when all running tasks should be stopped. */ interface CoroutineScopeOwner { @@ -35,216 +27,9 @@ interface CoroutineScopeOwner { fun getWorkerDispatcher() = Dispatchers.IO /** - * Asynchronously executes use case and saves it's Deferred. By default all previous - * pending executions are canceled, this can be changed by the [config]. - * This version is used for use cases without initial arguments. + * Launch suspend [block] in [coroutineScope]. * - * @param config [UseCaseConfig] used to process results of internal - * Coroutine and to set configuration options. - */ - fun UseCase.execute(config: UseCaseConfig.Builder.() -> Unit) = - execute(Unit, config) - - /** - * Asynchronously executes use case and saves it's Deferred. By default all previous - * pending executions are canceled, this can be changed by the [config]. - * This version gets initial arguments by [args]. - * - * In case that an error is thrown during the execution of [UseCase] then [UseCaseErrorHandler.globalOnErrorLogger] - * is called with the error as an argument. - * - * @param args Arguments used for initial use case initialization. - * @param config [UseCaseConfig] used to process results of internal - * Coroutine and to set configuration options. - */ - fun UseCase.execute( - args: ARGS, - config: UseCaseConfig.Builder.() -> Unit - ) { - val useCaseConfig = UseCaseConfig.Builder().run { - config.invoke(this) - return@run build() - } - if (useCaseConfig.disposePrevious) { - deferred?.cancel() - } - - useCaseConfig.onStart() - deferred = coroutineScope.async(getWorkerDispatcher(), CoroutineStart.LAZY) { - build(args) - }.also { - coroutineScope.launch(Dispatchers.Main) { - try { - useCaseConfig.onSuccess(it.await()) - } catch (cancellation: CancellationException) { - // do nothing - this is normal way of suspend function interruption - } catch (error: Throwable) { - UseCaseErrorHandler.globalOnErrorLogger(error) - useCaseConfig.onError.invoke(error) - } - } - } - } - - /** - * Synchronously executes use case and saves it's Deferred. By default all previous - * pending executions are canceled, this can be changed by the [cancelPrevious]. - * This version is used for use cases without initial arguments. - * - * @return [Result] that encapsulates either a successful result with [Success] or a failed result with [Error] - */ - suspend fun UseCase.execute(cancelPrevious: Boolean = true) = execute(Unit, cancelPrevious) - - /** - * Synchronously executes use case and saves it's Deferred. By default all previous - * pending executions are canceled, this can be changed by the [cancelPrevious]. - * This version gets initial arguments by [args]. - * - * [UseCaseErrorHandler.globalOnErrorLogger] is not used in this version of the execute method since it is - * recommended to call all execute methods with [Result] return type from [launchWithHandler] method where - * [UseCaseErrorHandler.globalOnErrorLogger] is used. - * - * @param args Arguments used for initial use case initialization. - * @return [Result] that encapsulates either a successful result with [Success] or a failed result with [Error] - */ - suspend fun UseCase.execute(args: ARGS, cancelPrevious: Boolean = true): Result { - if (cancelPrevious) { - deferred?.cancel() - } - return try { - val newDeferred = coroutineScope.async(getWorkerDispatcher(), CoroutineStart.LAZY) { - build(args) - }.also { deferred = it } - Success(newDeferred.await()) - } catch (exception: CancellationException) { - throw exception - } catch (exception: Throwable) { - Error(exception) - } - } - - /** - * Holds references to lambdas and some basic configuration - * used to process results of Coroutine use case. - * Use [UseCaseConfig.Builder] to construct this object. - */ - class UseCaseConfig private constructor( - val onStart: () -> Unit, - val onSuccess: (T) -> Unit, - val onError: (Throwable) -> Unit, - val disposePrevious: Boolean - ) { - /** - * Constructs references to lambdas and some basic configuration - * used to process results of Coroutine use case. - */ - class Builder { - private var onStart: (() -> Unit)? = null - private var onSuccess: ((T) -> Unit)? = null - private var onError: ((Throwable) -> Unit)? = null - private var disposePrevious = true - - /** - * Set lambda that is called right before - * the internal Coroutine is created - * @param onStart Lambda called right before Coroutine is - * created - */ - fun onStart(onStart: () -> Unit) { - this.onStart = onStart - } - - /** - * Set lambda that is called when internal Coroutine - * finished without exceptions - * @param onSuccess Lambda called when Coroutine finished - */ - fun onSuccess(onSuccess: (T) -> Unit) { - this.onSuccess = onSuccess - } - - /** - * Set lambda that is called when exception on - * internal Coroutine occurs - * @param onError Lambda called when exception occurs - */ - fun onError(onError: (Throwable) -> Unit) { - this.onError = onError - } - - /** - * Set whether currently active Job of internal Coroutine - * should be canceled when execute is called repeatedly. - * Default value is true. - * @param disposePrevious True if active Job of internal - * Coroutine should be canceled. Default value is true. - */ - fun disposePrevious(disposePrevious: Boolean) { - this.disposePrevious = disposePrevious - } - - fun build(): UseCaseConfig { - return UseCaseConfig( - onStart ?: { }, - onSuccess ?: { }, - onError ?: { throw it }, - disposePrevious - ) - } - } - } - - fun FlowUseCase.execute(config: FlowUseCaseConfig.Builder.() -> Unit) = - execute(Unit, config) - - /** - * Asynchronously executes use case and consumes data from flow on UI thread. - * By default all previous pending executions are canceled, this can be changed - * by [config]. When suspend function in use case finishes, onComplete is called - * on UI thread. This version is gets initial arguments by [args]. - * - * In case that an error is thrown during the execution of [FlowUseCase] then - * [UseCaseErrorHandler.globalOnErrorLogger] is called with the error as an argument. - * - * @param args Arguments used for initial use case initialization. - * @param config [FlowUseCaseConfig] used to process results of internal - * Flow and to set configuration options. - **/ - fun FlowUseCase.execute( - args: ARGS, - config: FlowUseCaseConfig.Builder.() -> Unit - ) { - val flowUseCaseConfig = FlowUseCaseConfig.Builder().run { - config.invoke(this) - return@run build() - } - - if (flowUseCaseConfig.disposePrevious) { - job?.cancel() - } - - job = build(args) - .flowOn(getWorkerDispatcher()) - .onStart { flowUseCaseConfig.onStart() } - .onEach { flowUseCaseConfig.onNext(it) } - .onCompletion { error -> - when { - error is CancellationException -> { - // ignore this exception - } - error != null -> { - UseCaseErrorHandler.globalOnErrorLogger(error) - flowUseCaseConfig.onError(error) - } - else -> flowUseCaseConfig.onComplete() - } - } - .catch { /* handled in onCompletion */ } - .launchIn(coroutineScope) - } - - /** - * Launch suspend [block] in [coroutineScope]. Encapsulates this call with try catch block and when an exception is thrown + * Encapsulates this call with try catch block and when an exception is thrown * then it is logged in [UseCaseErrorHandler.globalOnErrorLogger] and handled by [defaultErrorHandler]. * * If exception is [CancellationException] then [defaultErrorHandler] is not called and @@ -274,85 +59,4 @@ interface CoroutineScopeOwner { fun defaultErrorHandler(exception: Throwable) { throw exception } - - /** - * Holds references to lambdas and some basic configuration - * used to process results of Flow use case. - * Use [FlowUseCaseConfig.Builder] to construct this object. - */ - class FlowUseCaseConfig private constructor( - val onStart: () -> Unit, - val onNext: (T) -> Unit, - val onError: (Throwable) -> Unit, - val onComplete: () -> Unit, - val disposePrevious: Boolean - ) { - /** - * Constructs references to lambdas and some basic configuration - * used to process results of Flow use case. - */ - class Builder { - private var onStart: (() -> Unit)? = null - private var onNext: ((T) -> Unit)? = null - private var onError: ((Throwable) -> Unit)? = null - private var onComplete: (() -> Unit)? = null - private var disposePrevious = true - - /** - * Set lambda that is called right before - * internal Job of Flow is launched. - * @param onStart Lambda called right before Flow Job is launched. - */ - fun onStart(onStart: () -> Unit) { - this.onStart = onStart - } - - /** - * Set lambda that is called when internal Flow emits new value - * @param onNext Lambda called for every new emitted value - */ - fun onNext(onNext: (T) -> Unit) { - this.onNext = onNext - } - - /** - * Set lambda that is called when some exception on - * internal Flow occurs - * @param onError Lambda called when exception occurs - */ - fun onError(onError: (Throwable) -> Unit) { - this.onError = onError - } - - /** - * Set lambda that is called when internal Flow is completed - * without errors - * @param onComplete Lambda called when Flow is completed - * without errors - */ - fun onComplete(onComplete: () -> Unit) { - this.onComplete = onComplete - } - - /** - * Set whether currently running Job of internal Flow - * should be canceled when execute is called repeatedly. - * @param disposePrevious True if currently running - * Job of internal Flow should be canceled - */ - fun disposePrevious(disposePrevious: Boolean) { - this.disposePrevious = disposePrevious - } - - fun build(): FlowUseCaseConfig { - return FlowUseCaseConfig( - onStart ?: { }, - onNext ?: { }, - onError ?: { throw it }, - onComplete ?: { }, - disposePrevious - ) - } - } - } } diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseConfig.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseConfig.kt new file mode 100644 index 00000000..33099854 --- /dev/null +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseConfig.kt @@ -0,0 +1,91 @@ +package app.futured.arkitekt.crusecases + +/** + * Holds references to lambdas and some basic configuration + * used to process results of Flow use case. + * Use [FlowUseCaseConfig.Builder] to construct this object. + */ +class FlowUseCaseConfig private constructor( + val onStart: () -> Unit, + val onNext: (M) -> Unit, + val onError: (Throwable) -> Unit, + val onComplete: () -> Unit, + val disposePrevious: Boolean, + val onMap: ((T) -> M)? = null, +) { + /** + * Constructs references to lambdas and some basic configuration + * used to process results of Flow use case. + */ + class Builder { + private var onStart: (() -> Unit)? = null + private var onNext: ((M) -> Unit)? = null + private var onMap: ((T) -> M)? = null + private var onError: ((Throwable) -> Unit)? = null + private var onComplete: (() -> Unit)? = null + private var disposePrevious = true + + /** + * Set lambda that is called right before + * internal Job of Flow is launched. + * @param onStart Lambda called right before Flow Job is launched. + */ + fun onStart(onStart: () -> Unit) { + this.onStart = onStart + } + + /** + * Set lambda that is called when internal Flow emits new value + * @param onNext Lambda called for every new emitted value + */ + fun onNext(onNext: (M) -> Unit) { + this.onNext = onNext + } + + /** + * Set lambda that maps emitted values before [onNext] is called + * @param onMap Lambda called for mapping every emitted value + */ + fun onMap(onMap: (T) -> M) { + this.onMap = onMap + } + + /** + * Set lambda that is called when some exception on + * internal Flow occurs + * @param onError Lambda called when exception occurs + */ + fun onError(onError: (Throwable) -> Unit) { + this.onError = onError + } + + /** + * Set lambda that is called when internal Flow is completed + * without errors + * @param onComplete Lambda called when Flow is completed + * without errors + */ + fun onComplete(onComplete: () -> Unit) { + this.onComplete = onComplete + } + + /** + * Set whether currently running Job of internal Flow + * should be canceled when execute is called repeatedly. + * @param disposePrevious True if currently running + * Job of internal Flow should be canceled + */ + fun disposePrevious(disposePrevious: Boolean) { + this.disposePrevious = disposePrevious + } + + fun build(): FlowUseCaseConfig = FlowUseCaseConfig( + onStart ?: { }, + onNext ?: { }, + onError ?: { throw it }, + onComplete ?: { }, + disposePrevious, + onMap, + ) + } +} diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseExecution.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseExecution.kt new file mode 100644 index 00000000..f029ec34 --- /dev/null +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCaseExecution.kt @@ -0,0 +1,114 @@ +package app.futured.arkitekt.crusecases + +import app.futured.arkitekt.crusecases.error.UseCaseErrorHandler +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart + +context(coroutineScopeOwner: CoroutineScopeOwner) +fun FlowUseCase.execute(config: FlowUseCaseConfig.Builder.() -> Unit) = + execute(Unit, config) + +context(coroutineScopeOwner: CoroutineScopeOwner) +fun FlowUseCase.executeMapped(config: FlowUseCaseConfig.Builder.() -> Unit) = + executeMapped(Unit, config) + +/** + * Asynchronously executes use case and consumes data from flow on UI thread. + * By default all previous pending executions are canceled, this can be changed + * by [config]. When suspend function in use case finishes, onComplete is called + * on UI thread. This version gets initial arguments by [args]. + * + * In case that an error is thrown during the execution of [FlowUseCase] then + * [UseCaseErrorHandler.globalOnErrorLogger] is called with the error as an argument. + * + * @param args Arguments used for initial use case initialization. + * @param config [FlowUseCaseConfig] used to process results of internal + * Flow and to set configuration options. + **/ +context(coroutineScopeOwner: CoroutineScopeOwner) +fun FlowUseCase.execute( + args: ARGS, + config: FlowUseCaseConfig.Builder.() -> Unit, +) { + val flowUseCaseConfig = FlowUseCaseConfig.Builder().run { + config.invoke(this) + return@run build() + } + + if (flowUseCaseConfig.disposePrevious) { + job?.cancel() + } + + job = build(args) + .flowOn(coroutineScopeOwner.getWorkerDispatcher()) + .onStart { flowUseCaseConfig.onStart() } + .onEach { flowUseCaseConfig.onNext(it) } + .onCompletion { error -> + when { + error is CancellationException -> { + // ignore this exception + } + error != null -> { + UseCaseErrorHandler.globalOnErrorLogger(error) + flowUseCaseConfig.onError(error) + } + else -> flowUseCaseConfig.onComplete() + } + } + .catch { /* handled in onCompletion */ } + .launchIn(coroutineScopeOwner.coroutineScope) +} + +/** + * Asynchronously executes use case and consumes data from flow on UI thread, mapping each emitted value. + * By default all previous pending executions are canceled, this can be changed + * by [config]. When suspend function in use case finishes, onComplete is called + * on UI thread. This version gets initial arguments by [args]. + * + * In case that an error is thrown during the execution of [FlowUseCase] then + * [UseCaseErrorHandler.globalOnErrorLogger] is called with the error as an argument. + * + * @param args Arguments used for initial use case initialization. + * @param config [FlowUseCaseConfig] used to process results of internal + * Flow and to set configuration options. + **/ +context(coroutineScopeOwner: CoroutineScopeOwner) +fun FlowUseCase.executeMapped( + args: ARGS, + config: FlowUseCaseConfig.Builder.() -> Unit, +) { + val flowUseCaseConfig = FlowUseCaseConfig.Builder().run { + config.invoke(this) + return@run build() + } + + if (flowUseCaseConfig.disposePrevious) { + job?.cancel() + } + + job = build(args) + .flowOn(coroutineScopeOwner.getWorkerDispatcher()) + .onStart { flowUseCaseConfig.onStart() } + .mapNotNull { flowUseCaseConfig.onMap?.invoke(it) } + .onEach { flowUseCaseConfig.onNext(it) } + .onCompletion { error -> + when { + error is CancellationException -> { + // ignore this exception + } + error != null -> { + UseCaseErrorHandler.globalOnErrorLogger(error) + flowUseCaseConfig.onError(error) + } + else -> flowUseCaseConfig.onComplete() + } + } + .catch { /* handled in onCompletion */ } + .launchIn(coroutineScopeOwner.coroutineScope) +} diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseConfig.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseConfig.kt new file mode 100644 index 00000000..b87d8078 --- /dev/null +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseConfig.kt @@ -0,0 +1,69 @@ +package app.futured.arkitekt.crusecases + +/** + * Holds references to lambdas and some basic configuration + * used to process results of Coroutine use case. + * Use [UseCaseConfig.Builder] to construct this object. + */ +class UseCaseConfig private constructor( + val onStart: () -> Unit, + val onSuccess: (T) -> Unit, + val onError: (Throwable) -> Unit, + val disposePrevious: Boolean, +) { + /** + * Constructs references to lambdas and some basic configuration + * used to process results of Coroutine use case. + */ + class Builder { + private var onStart: (() -> Unit)? = null + private var onSuccess: ((T) -> Unit)? = null + private var onError: ((Throwable) -> Unit)? = null + private var disposePrevious = true + + /** + * Set lambda that is called right before + * the internal Coroutine is created + * @param onStart Lambda called right before Coroutine is created + */ + fun onStart(onStart: () -> Unit) { + this.onStart = onStart + } + + /** + * Set lambda that is called when internal Coroutine + * finished without exceptions + * @param onSuccess Lambda called when Coroutine finished + */ + fun onSuccess(onSuccess: (T) -> Unit) { + this.onSuccess = onSuccess + } + + /** + * Set lambda that is called when exception on + * internal Coroutine occurs + * @param onError Lambda called when exception occurs + */ + fun onError(onError: (Throwable) -> Unit) { + this.onError = onError + } + + /** + * Set whether currently active Job of internal Coroutine + * should be canceled when execute is called repeatedly. + * Default value is true. + * @param disposePrevious True if active Job of internal + * Coroutine should be canceled. Default value is true. + */ + fun disposePrevious(disposePrevious: Boolean) { + this.disposePrevious = disposePrevious + } + + fun build(): UseCaseConfig = UseCaseConfig( + onStart ?: { }, + onSuccess ?: { }, + onError ?: { throw it }, + disposePrevious, + ) + } +} diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseExecution.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseExecution.kt new file mode 100644 index 00000000..8a7c8205 --- /dev/null +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCaseExecution.kt @@ -0,0 +1,105 @@ +package app.futured.arkitekt.crusecases + +import app.futured.arkitekt.crusecases.error.UseCaseErrorHandler +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch + +/** + * Asynchronously executes use case and saves it's Deferred. By default, all previous + * pending executions are canceled, this can be changed by the [config]. + * This version is used for use cases without initial arguments. + * + * @param config [UseCaseConfig] used to process results of internal + * Coroutine and to set configuration options. + */ +context(coroutineScopeOwner: CoroutineScopeOwner) +fun UseCase.execute(config: UseCaseConfig.Builder.() -> Unit) = + execute(Unit, config) + +/** + * Asynchronously executes use case and saves it's Deferred. By default, all previous + * pending executions are canceled, this can be changed by the [config]. + * This version gets initial arguments by [args]. + * + * In case that an error is thrown during the execution of [UseCase] then [UseCaseErrorHandler.globalOnErrorLogger] + * is called with the error as an argument. + * + * @param args Arguments used for initial use case initialization. + * @param config [UseCaseConfig] used to process results of internal + * Coroutine and to set configuration options. + */ +context(coroutineScopeOwner: CoroutineScopeOwner) +fun UseCase.execute( + args: ARGS, + config: UseCaseConfig.Builder.() -> Unit, +) { + val useCaseConfig = UseCaseConfig.Builder().run { + config.invoke(this) + return@run build() + } + if (useCaseConfig.disposePrevious) { + deferred?.cancel() + } + + useCaseConfig.onStart() + deferred = coroutineScopeOwner.coroutineScope + .async(context = coroutineScopeOwner.getWorkerDispatcher(), start = CoroutineStart.LAZY) { + build(args) + } + .also { + coroutineScopeOwner.coroutineScope.launch(Dispatchers.Main) { + try { + useCaseConfig.onSuccess(it.await()) + } catch (cancellation: CancellationException) { + // do nothing - this is normal way of suspend function interruption + } catch (error: Throwable) { + UseCaseErrorHandler.globalOnErrorLogger(error) + useCaseConfig.onError(error) + } + } + } +} + +/** + * Synchronously executes use case and saves it's Deferred. By default all previous + * pending executions are canceled, this can be changed by the [cancelPrevious]. + * This version is used for use cases without initial arguments. + * + * @return [Result] that encapsulates either a successful result with [Success] or a failed result with [Error] + */ +context(coroutineScopeOwner: CoroutineScopeOwner) +suspend fun UseCase.execute(cancelPrevious: Boolean = true) = execute(Unit, cancelPrevious) + +/** + * Synchronously executes use case and saves it's Deferred. By default all previous + * pending executions are canceled, this can be changed by the [cancelPrevious]. + * This version gets initial arguments by [args]. + * + * [UseCaseErrorHandler.globalOnErrorLogger] is not used in this version of the execute method since it is + * recommended to call all execute methods with [Result] return type from [launchWithHandler] method where + * [UseCaseErrorHandler.globalOnErrorLogger] is used. + * + * @param args Arguments used for initial use case initialization. + * @return [Result] that encapsulates either a successful result with [Success] or a failed result with [Error] + */ +@Suppress("TooGenericExceptionCaught") +context(coroutineScopeOwner: CoroutineScopeOwner) +suspend fun UseCase.execute(args: ARGS, cancelPrevious: Boolean = true): Result { + if (cancelPrevious) { + deferred?.cancel() + } + return try { + val newDeferred = coroutineScopeOwner.coroutineScope + .async(coroutineScopeOwner.getWorkerDispatcher(), CoroutineStart.LAZY) { + build(args) + }.also { deferred = it } + Success(newDeferred.await()) + } catch (exception: CancellationException) { + throw exception + } catch (exception: Throwable) { + Error(exception) + } +} diff --git a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt index 69daf443..0beba764 100644 --- a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt +++ b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt @@ -1,6 +1,7 @@ package app.futured.arkitekt.decompose.coroutines import com.arkivanov.decompose.value.Value +import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow * @param T The type of the value. * @property decomposeValue The Decompose Value to be wrapped. */ +@OptIn(ExperimentalForInheritanceCoroutinesApi::class) internal class ValueStateFlow(private val decomposeValue: Value) : StateFlow { override val value: T diff --git a/example/build.gradle.kts b/example/build.gradle.kts index ed04a598..f92763d2 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -51,6 +51,8 @@ android { kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + + freeCompilerArgs.add("-Xcontext-parameters") } } diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultViewModel.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultViewModel.kt index 965e06eb..be753f62 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultViewModel.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultViewModel.kt @@ -1,6 +1,7 @@ package app.futured.arkitekt.sample.ui.coroutinesresult import app.futured.arkitekt.compose.BaseViewModel +import app.futured.arkitekt.crusecases.execute import app.futured.arkitekt.crusecases.getOrCancel import app.futured.arkitekt.crusecases.getOrElse import app.futured.arkitekt.crusecases.getOrThrow diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewModel.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewModel.kt index f54a1874..1021690f 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewModel.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewModel.kt @@ -1,6 +1,7 @@ package app.futured.arkitekt.sample.ui.form import app.futured.arkitekt.compose.BaseViewModel +import app.futured.arkitekt.crusecases.execute import app.futured.arkitekt.sample.domain.ObserveFormUseCase import app.futured.arkitekt.sample.domain.SaveFormUseCase import app.futured.arkitekt.sample.ui.compose.ExampleRoute @@ -8,7 +9,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject @HiltViewModel(assistedFactory = FormViewModel.Factory::class) class FormViewModel @AssistedInject constructor( @@ -34,7 +34,7 @@ class FormViewModel @AssistedInject constructor( fun onBack() = sendEvent(NavigateBackEvent) @AssistedFactory - interface Factory{ + interface Factory { fun create(route: ExampleRoute.Form): FormViewModel } } diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/login/LoginViewModel.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/login/LoginViewModel.kt index a5c21819..7abb4c44 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/login/LoginViewModel.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/login/LoginViewModel.kt @@ -1,7 +1,7 @@ package app.futured.arkitekt.sample.ui.login -import android.view.View import app.futured.arkitekt.compose.BaseViewModel +import app.futured.arkitekt.crusecases.execute import app.futured.arkitekt.sample.domain.GetStateUseCase import app.futured.arkitekt.sample.domain.ObserveUserFullNameUseCase import app.futured.arkitekt.sample.domain.SyncLoginUseCase From dfa967b95810e4dc3e5bc3b2c2be940018df4a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rudolf=20Hlad=C3=ADk?= Date: Thu, 19 Mar 2026 16:02:16 +0100 Subject: [PATCH 3/5] Fix lint --- compose/build.gradle.kts | 1 - .../main/java/app/futured/arkitekt/core/BaseCoreViewModel.kt | 2 +- .../app/futured/arkitekt/crusecases/test/UseCaseTests.kt | 2 +- .../app/futured/arkitekt/annotation/GenerateFactory.kt | 2 +- .../processor/PoetFactoryComponentGenerator.kt | 5 ++--- .../app/futured/arkitekt/decompose/ext/NavigationExt.kt | 1 - .../futured/arkitekt/sample/ui/compose/BackStackNavigator.kt | 5 ++--- .../app/futured/arkitekt/sample/ui/compose/ExampleApp.kt | 1 - .../app/futured/arkitekt/sample/ui/detail/DetailScreen.kt | 1 - .../app/futured/arkitekt/sample/ui/detail/DetailViewModel.kt | 2 -- .../java/app/futured/arkitekt/sample/ui/form/FormScreen.kt | 1 - .../app/futured/arkitekt/sample/ui/form/FormViewState.kt | 2 -- .../futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt | 2 -- 13 files changed, 7 insertions(+), 20 deletions(-) diff --git a/compose/build.gradle.kts b/compose/build.gradle.kts index 78465f47..c7cbdeca 100644 --- a/compose/build.gradle.kts +++ b/compose/build.gradle.kts @@ -81,5 +81,4 @@ dependencies { implementation(platform(Deps.Compose.bom)) implementation(Deps.Compose.runtime) - } diff --git a/core/src/main/java/app/futured/arkitekt/core/BaseCoreViewModel.kt b/core/src/main/java/app/futured/arkitekt/core/BaseCoreViewModel.kt index 01f84449..af1d34f8 100644 --- a/core/src/main/java/app/futured/arkitekt/core/BaseCoreViewModel.kt +++ b/core/src/main/java/app/futured/arkitekt/core/BaseCoreViewModel.kt @@ -20,4 +20,4 @@ abstract class BaseCoreViewModel : ViewModel() { fun sendEvent(event: Event) = viewModelScope.launch { eventChannel.send(event) } -} \ No newline at end of file +} diff --git a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt index ea2902b2..e875bdf3 100644 --- a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt +++ b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt @@ -47,7 +47,7 @@ class UseCaseTests { @After fun tearDown() { - // testCoroutineScopeOwner.coroutineScope.cleanupTestCoroutines() + // testCoroutineScopeOwner.coroutineScope.cleanupTestCoroutines() Dispatchers.resetMain() } diff --git a/decompose-annotation/src/commonMain/kotlin/app/futured/arkitekt/annotation/GenerateFactory.kt b/decompose-annotation/src/commonMain/kotlin/app/futured/arkitekt/annotation/GenerateFactory.kt index 47cf90c1..fe1871cd 100644 --- a/decompose-annotation/src/commonMain/kotlin/app/futured/arkitekt/annotation/GenerateFactory.kt +++ b/decompose-annotation/src/commonMain/kotlin/app/futured/arkitekt/annotation/GenerateFactory.kt @@ -2,4 +2,4 @@ package app.futured.arkitekt.annotation @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) -annotation class GenerateFactory \ No newline at end of file +annotation class GenerateFactory diff --git a/decompose-processor/src/jvmMain/kotlin/app/futured/arkitekt/factorygenerator/processor/PoetFactoryComponentGenerator.kt b/decompose-processor/src/jvmMain/kotlin/app/futured/arkitekt/factorygenerator/processor/PoetFactoryComponentGenerator.kt index e12ae97b..af36f987 100644 --- a/decompose-processor/src/jvmMain/kotlin/app/futured/arkitekt/factorygenerator/processor/PoetFactoryComponentGenerator.kt +++ b/decompose-processor/src/jvmMain/kotlin/app/futured/arkitekt/factorygenerator/processor/PoetFactoryComponentGenerator.kt @@ -122,10 +122,9 @@ object PoetFactoryComponentGenerator { .mapIndexed { index, ksValueParameter -> val paramName = ksValueParameter.name?.asString() ?: "param$index" val typeName = ksValueParameter.type.toTypeName() - paramName to typeName + paramName to typeName } - val returnType = ClassName( packageName = factoryComponentPackageName, simpleNames = listOf(baseName), @@ -172,4 +171,4 @@ object PoetFactoryComponentGenerator { private fun KSValueParameter.containsTypeName(name: String): Boolean = this.type.toTypeName().toString().contains(name) -} \ No newline at end of file +} diff --git a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt index 14065135..6b611252 100644 --- a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt +++ b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt @@ -2,7 +2,6 @@ package app.futured.arkitekt.decompose.ext import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.StackNavigator -import com.arkivanov.decompose.router.stack.bringToFront /** * The same as [StackNavigation.bringToFront] but does not recreate [configuration] if it's class is already on stack and diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt index b623dc0a..edec0172 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt @@ -3,12 +3,12 @@ package app.futured.arkitekt.sample.ui.compose import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey -interface BackStackNavigator { +interface BackStackNavigator { fun onBack() fun onNavigate(destination: T) } -class BackStackNavigatorImpl(private val backStack: NavBackStack): BackStackNavigator { +class BackStackNavigatorImpl(private val backStack: NavBackStack) : BackStackNavigator { override fun onBack() { backStack.removeLastOrNull() } @@ -16,4 +16,3 @@ class BackStackNavigatorImpl(private val backStack: NavBackStack): backStack.add(destination) } } - diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/ExampleApp.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/ExampleApp.kt index aef88024..7ed1df4c 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/ExampleApp.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/ExampleApp.kt @@ -53,7 +53,6 @@ fun ExampleApp() { BackStackNavigatorImpl(backStack) as BackStackNavigator } - MaterialTheme { Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues -> NavDisplay( diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailScreen.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailScreen.kt index 6eaed53a..5b00bee9 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailScreen.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailViewModel.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailViewModel.kt index caeacaad..311e3fb8 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailViewModel.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/detail/DetailViewModel.kt @@ -1,6 +1,5 @@ package app.futured.arkitekt.sample.ui.detail -import app.futured.arkitekt.core.BaseCoreViewModel import app.futured.arkitekt.compose.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @@ -10,7 +9,6 @@ class DetailViewModel @Inject constructor( override val viewState: DetailViewState ) : BaseViewModel() { - fun incrementNumber() { viewState.number.value = viewState.number.value + 1 } diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt index 7205f38f..ea233014 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewState.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewState.kt index 37362f0c..78fc90aa 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewState.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormViewState.kt @@ -14,5 +14,3 @@ class FormViewState @Inject constructor() : ViewState { } val storedContent = mutableStateOf("") } - - diff --git a/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt b/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt index 0e334d5e..9716d46f 100644 --- a/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt +++ b/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt @@ -5,7 +5,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.spyk import io.mockk.verify -import io.mockk.verifyOrder import org.junit.Before import org.junit.Test @@ -40,5 +39,4 @@ class DetailViewModelTest : ViewModelTest() { // THEN verify { viewModel.sendEvent(NavigateBackEvent) } } - } From 07c8c4cc4b413f2c23e2c546f2f02eea0165d617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rudolf=20Hlad=C3=ADk?= Date: Fri, 20 Mar 2026 12:57:19 +0100 Subject: [PATCH 4/5] Fix lint --- .../arkitekt/lint/MvvmIssueRegistry.kt | 10 +-- .../arkitekt/lint/WrongEventNameDetector.kt | 84 +++++++++++-------- .../lint/WrongEventNameDetectorTest.kt | 44 ++++------ build.gradle.kts | 30 +++---- buildSrc/src/main/kotlin/Versions.kt | 6 +- compose/build.gradle.kts | 2 +- .../futured/arkitekt/compose/BaseViewModel.kt | 4 +- core-test/build.gradle.kts | 2 +- .../core/test/internal/CoroutineScopeRule.kt | 10 +-- .../arkitekt/core/view/InstantTaskExecutor.kt | 23 +++-- .../arkitekt/core/viewmodel/ViewModelTest.kt | 3 +- core/build.gradle.kts | 2 +- cr-usecases-test/build.gradle.kts | 2 +- .../arkitekt/crusecases/test/UseCase.kt | 10 ++- .../crusecases/test/FlowUseCaseTests.kt | 1 - .../test/TestCoroutineScopeOwner.kt | 1 - .../arkitekt/crusecases/test/UseCaseTests.kt | 9 +- cr-usecases/build.gradle.kts | 4 +- .../crusecases/CoroutineScopeOwner.kt | 5 +- .../arkitekt/crusecases/FlowUseCase.kt | 1 - .../crusecases/error/UseCaseErrorHandler.kt | 1 - decompose-annotation/build.gradle.kts | 2 +- decompose-processor/build.gradle.kts | 2 +- decompose/build.gradle.kts | 6 +- .../decompose/coroutines/ValueStateFlow.kt | 5 +- .../arkitekt/decompose/ext/NavigationExt.kt | 5 +- .../decompose/navigation/ResultFlow.kt | 15 ++-- .../decompose/presentation/BaseComponent.kt | 18 ++-- detekt.yml | 2 + .../java/app/futured/arkitekt/sample/App.kt | 2 +- .../sample/injection/ApplicationComponent.kt | 6 +- .../ui/bottomsheet/BottomSheetScreen.kt | 7 +- .../sample/ui/compose/BackStackNavigator.kt | 6 +- .../ui/compose/BottomSheetSceneStrategy.kt | 8 +- .../CoroutinesResultScreen.kt | 2 +- .../arkitekt/sample/ui/form/FormScreen.kt | 14 ++-- .../arkitekt/sample/ui/main/MainActivity.kt | 1 - .../ui/bottomsheet/ExampleViewModelTest.kt | 1 - .../sample/ui/detail/DetailViewModelTest.kt | 1 - .../sample/ui/form/FormViewModelTest.kt | 24 +++--- .../ui/login/fragment/LoginViewModelTest.kt | 1 - settings.gradle.kts | 2 +- 42 files changed, 200 insertions(+), 184 deletions(-) diff --git a/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/MvvmIssueRegistry.kt b/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/MvvmIssueRegistry.kt index 5ed1794d..a90728a8 100644 --- a/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/MvvmIssueRegistry.kt +++ b/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/MvvmIssueRegistry.kt @@ -5,11 +5,11 @@ import com.android.tools.lint.detector.api.CURRENT_API import com.android.tools.lint.detector.api.Issue class MvvmIssueRegistry : IssueRegistry() { - - override val issues: List = listOf( - WrongEventNameDetector.ISSUE_MUSSING_SUFFIX, - WrongEventNameDetector.ISSUE_MISSPELL - ) + override val issues: List = + listOf( + WrongEventNameDetector.ISSUE_MUSSING_SUFFIX, + WrongEventNameDetector.ISSUE_MISSPELL, + ) override val api = CURRENT_API } diff --git a/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/WrongEventNameDetector.kt b/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/WrongEventNameDetector.kt index db324143..3aaf2717 100644 --- a/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/WrongEventNameDetector.kt +++ b/arkitekt-lint/src/main/java/app/futured/arkitekt/lint/WrongEventNameDetector.kt @@ -12,34 +12,39 @@ import org.jetbrains.uast.UClass import java.util.EnumSet import java.util.regex.Pattern -class WrongEventNameDetector : Detector(), Detector.UastScanner { - +class WrongEventNameDetector : + Detector(), + Detector.UastScanner { companion object { - val ISSUE_MUSSING_SUFFIX = Issue.create( - id = "MvvmEventNameMissingSuffix", - briefDescription = "Wrong event name", - explanation = "Event names should end with 'Event' suffix", - category = Category.CORRECTNESS, - priority = 5, - severity = Severity.WARNING, - implementation = Implementation( - WrongEventNameDetector::class.java, - EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) + val ISSUE_MUSSING_SUFFIX = + Issue.create( + id = "MvvmEventNameMissingSuffix", + briefDescription = "Wrong event name", + explanation = "Event names should end with 'Event' suffix", + category = Category.CORRECTNESS, + priority = 5, + severity = Severity.WARNING, + implementation = + Implementation( + WrongEventNameDetector::class.java, + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES), + ), ) - ) - val ISSUE_MISSPELL = Issue.create( - id = "MvvmEventNameMisspell", - briefDescription = "Misspelled event name", - explanation = "Event name looks misspelled", - category = Category.CORRECTNESS, - priority = 3, - severity = Severity.WARNING, - implementation = Implementation( - WrongEventNameDetector::class.java, - EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) + val ISSUE_MISSPELL = + Issue.create( + id = "MvvmEventNameMisspell", + briefDescription = "Misspelled event name", + explanation = "Event name looks misspelled", + category = Category.CORRECTNESS, + priority = 3, + severity = Severity.WARNING, + implementation = + Implementation( + WrongEventNameDetector::class.java, + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES), + ), ) - ) private const val MVVM_EVENT_QUALIFIED_NAME = "app.futured.arkitekt.core.event.Event" private val PATTERN_MISSPELL = Pattern.compile("Event[a-z]+") @@ -50,32 +55,37 @@ class WrongEventNameDetector : Detector(), Detector.UastScanner { override fun applicableSuperClasses() = listOf(MVVM_EVENT_QUALIFIED_NAME) - override fun visitClass(context: JavaContext, declaration: UClass) { + override fun visitClass( + context: JavaContext, + declaration: UClass, + ) { super.visitClass(context, declaration) val className = declaration.name val isMvvmLibraryEvent = context.evaluator.getQualifiedName(declaration) == MVVM_EVENT_QUALIFIED_NAME - val directlyExtendsMvvmEvent = declaration.javaPsi.superClass?.let { - context.evaluator.getQualifiedName(it) - } == MVVM_EVENT_QUALIFIED_NAME + val directlyExtendsMvvmEvent = + declaration.javaPsi.superClass?.let { + context.evaluator.getQualifiedName(it) + } == MVVM_EVENT_QUALIFIED_NAME val isEligibleForDetection = isMvvmLibraryEvent.not() && directlyExtendsMvvmEvent.not() if (className != null && isEligibleForDetection) { when { PATTERN_MISSPELL.matcher(className).find() -> { - val suggestedName = PATTERN_SUFFIX.matcher(className).let { - it.find() - it.group(1) - } + val suggestedName = + PATTERN_SUFFIX.matcher(className).let { + it.find() + it.group(1) + } context.report( issue = ISSUE_MISSPELL, scopeClass = declaration, location = context.getNameLocation(declaration), message = "Event name is misspelled. Suggested name: $suggestedName", - quickfixData = createQuickFix(className, suggestedName) + quickfixData = createQuickFix(className, suggestedName), ) } @@ -87,14 +97,18 @@ class WrongEventNameDetector : Detector(), Detector.UastScanner { scopeClass = declaration, location = context.getNameLocation(declaration), message = "Event names should end with 'Event' suffix. Suggested name: $suggestedName", - quickfixData = createQuickFix(className, suggestedName) + quickfixData = createQuickFix(className, suggestedName), ) } } } } - private fun createQuickFix(declarationName: String, replacement: String) = LintFix.create() + private fun createQuickFix( + declarationName: String, + replacement: String, + ) = LintFix + .create() .name("Replace with $replacement") .replace() .text(declarationName) diff --git a/arkitekt-lint/src/test/java/app/futured/arkitekt/lint/WrongEventNameDetectorTest.kt b/arkitekt-lint/src/test/java/app/futured/arkitekt/lint/WrongEventNameDetectorTest.kt index 8ccb5bf1..0e9cfc24 100644 --- a/arkitekt-lint/src/test/java/app/futured/arkitekt/lint/WrongEventNameDetectorTest.kt +++ b/arkitekt-lint/src/test/java/app/futured/arkitekt/lint/WrongEventNameDetectorTest.kt @@ -6,22 +6,18 @@ import com.android.tools.lint.detector.api.Issue import org.junit.Test class WrongEventNameDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = WrongEventNameDetector() - override fun getDetector(): Detector { - return WrongEventNameDetector() - } - - override fun getIssues(): MutableList { - return mutableListOf(WrongEventNameDetector.ISSUE_MUSSING_SUFFIX, WrongEventNameDetector.ISSUE_MISSPELL) - } + override fun getIssues(): MutableList = mutableListOf(WrongEventNameDetector.ISSUE_MUSSING_SUFFIX, WrongEventNameDetector.ISSUE_MISSPELL) - private val eventStub = kotlin( - """ + private val eventStub = + kotlin( + """ package app.futured.arkitekt.core.event abstract class Event - """ - ).indented() + """, + ).indented() @Test fun testMissingSuffixWarning() { @@ -41,10 +37,9 @@ class WrongEventNameDetectorTest : LintDetectorTest() { object ShowFormEvent : MainEvent() object ShowForm : MainEvent() - """ - ).indented() - ) - .allowMissingSdk() + """, + ).indented(), + ).allowMissingSdk() .issues(WrongEventNameDetector.ISSUE_MUSSING_SUFFIX) .run() .expectWarningCount(2) @@ -70,10 +65,9 @@ class WrongEventNameDetectorTest : LintDetectorTest() { object ShowFormEvents : MainEvent() object ShowEventDataFormEvents : MainEvent() - """ - ).indented() - ) - .allowMissingSdk() + """, + ).indented(), + ).allowMissingSdk() .issues(WrongEventNameDetector.ISSUE_MISSPELL) .run() .expectWarningCount(3) @@ -95,15 +89,13 @@ class WrongEventNameDetectorTest : LintDetectorTest() { object ShowFormEvent : MainEvent() object SendEventDataEvent : MainEvent() - """ - ).indented() - ) - .allowMissingSdk() + """, + ).indented(), + ).allowMissingSdk() .issues( WrongEventNameDetector.ISSUE_MUSSING_SUFFIX, - WrongEventNameDetector.ISSUE_MISSPELL - ) - .run() + WrongEventNameDetector.ISSUE_MISSPELL, + ).run() .expectClean() } } diff --git a/build.gradle.kts b/build.gradle.kts index d618e1cb..27b78de0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -62,7 +62,7 @@ subprojects { if (hasKey && hasPassword) { useInMemoryPgpKeys( project.properties["SIGNING_PRIVATE_KEY"].toString(), - project.properties["SIGNING_PASSWORD"].toString() + project.properties["SIGNING_PASSWORD"].toString(), ) } } @@ -73,19 +73,21 @@ subprojects { detekt { autoCorrect = false version = Versions.detekt - source = files( - "example/src/main/java", - "core/src/main/java", - "compose/src/main/java", - "core-test/src/main/java", - "cr-usecases/src/commonMain/kotlin", - "cr-usecases-test/src/main/java", - "decompose/src/commonMain/kotlin", - "decompose/src/androidMain/kotlin", - "decompose-annotation/src/commonMain/kotlin", - "decompose-processor/src/jvmMain/kotlin", - "arkitekt-lint/src/main/java" + source.setFrom( + files( + "example/src/main/java", + "core/src/main/java", + "compose/src/main/java", + "core-test/src/main/java", + "cr-usecases/src/commonMain/kotlin", + "cr-usecases-test/src/main/java", + "decompose/src/commonMain/kotlin", + "decompose/src/androidMain/kotlin", + "decompose-annotation/src/commonMain/kotlin", + "decompose-processor/src/jvmMain/kotlin", + "arkitekt-lint/src/main/java", + ) ) // filters = ".*/resources/.*,.*/build/.*" - config = files("detekt.yml") + config.setFrom(files("detekt.yml")) } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 2e9e56c0..b844dc13 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -3,9 +3,9 @@ object Versions { const val gradlePlugin = "8.13.0" // plugins - const val detekt = "1.20.0" - const val ktlint = "10.3.0" - const val ktlintExtension = "0.39.0" + const val detekt = "1.23.8" + const val ktlint = "14.0.1" + const val ktlintExtension = "1.8.0" // todo 1.7.0 const val mavenPublish = "0.34.0" const val dokka = "1.6.10" diff --git a/compose/build.gradle.kts b/compose/build.gradle.kts index c7cbdeca..70b2b95d 100644 --- a/compose/build.gradle.kts +++ b/compose/build.gradle.kts @@ -46,7 +46,7 @@ mavenPublishing { coordinates( groupId = ProjectSettings.group, artifactId = "compose", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) pom { name = "Arkitekt Compose" diff --git a/compose/src/main/java/app/futured/arkitekt/compose/BaseViewModel.kt b/compose/src/main/java/app/futured/arkitekt/compose/BaseViewModel.kt index 6272e011..542517ae 100644 --- a/compose/src/main/java/app/futured/arkitekt/compose/BaseViewModel.kt +++ b/compose/src/main/java/app/futured/arkitekt/compose/BaseViewModel.kt @@ -6,6 +6,8 @@ import app.futured.arkitekt.core.ViewState import app.futured.arkitekt.crusecases.CoroutineScopeOwner import kotlinx.coroutines.CoroutineScope -abstract class BaseViewModel() : BaseCoreViewModel(), CoroutineScopeOwner { +abstract class BaseViewModel : + BaseCoreViewModel(), + CoroutineScopeOwner { override val coroutineScope: CoroutineScope = viewModelScope } diff --git a/core-test/build.gradle.kts b/core-test/build.gradle.kts index d9eaeac8..0fd38fba 100644 --- a/core-test/build.gradle.kts +++ b/core-test/build.gradle.kts @@ -47,7 +47,7 @@ mavenPublishing { coordinates( groupId = ProjectSettings.group, artifactId = "core-test", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) pom { name = "Arkitekt Core Test" diff --git a/core-test/src/main/java/app/futured/arkitekt/core/test/internal/CoroutineScopeRule.kt b/core-test/src/main/java/app/futured/arkitekt/core/test/internal/CoroutineScopeRule.kt index 99fa8c4c..98acfab8 100644 --- a/core-test/src/main/java/app/futured/arkitekt/core/test/internal/CoroutineScopeRule.kt +++ b/core-test/src/main/java/app/futured/arkitekt/core/test/internal/CoroutineScopeRule.kt @@ -14,10 +14,8 @@ import org.junit.runners.model.Statement @ExperimentalCoroutinesApi class CoroutineScopeRule : TestRule { - @ExperimentalCoroutinesApi class TestCoroutineScopeOwner : CoroutineScopeOwner { - val testDispatcher = UnconfinedTestDispatcher() override val coroutineScope = TestScope(testDispatcher) @@ -25,8 +23,11 @@ class CoroutineScopeRule : TestRule { override fun getWorkerDispatcher(): CoroutineDispatcher = testDispatcher } - override fun apply(base: Statement, description: Description): Statement { - return object : Statement() { + override fun apply( + base: Statement, + description: Description, + ): Statement = + object : Statement() { @Throws(Throwable::class) override fun evaluate() { val scopeOwner = TestCoroutineScopeOwner() @@ -37,5 +38,4 @@ class CoroutineScopeRule : TestRule { Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher } } - } } diff --git a/core-test/src/main/java/app/futured/arkitekt/core/view/InstantTaskExecutor.kt b/core-test/src/main/java/app/futured/arkitekt/core/view/InstantTaskExecutor.kt index b82b5c5b..32a3df21 100644 --- a/core-test/src/main/java/app/futured/arkitekt/core/view/InstantTaskExecutor.kt +++ b/core-test/src/main/java/app/futured/arkitekt/core/view/InstantTaskExecutor.kt @@ -13,7 +13,6 @@ import androidx.arch.core.executor.TaskExecutor @PublishedApi @SuppressLint("RestrictedApi") internal class InstantTaskExecutor { - /** * Wrap invocation of [block] with immediate task execution */ @@ -24,19 +23,19 @@ internal class InstantTaskExecutor { } private fun startExecuteTaskImmediately() { - ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() { - override fun executeOnDiskIO(runnable: Runnable) { - runnable.run() - } + ArchTaskExecutor.getInstance().setDelegate( + object : TaskExecutor() { + override fun executeOnDiskIO(runnable: Runnable) { + runnable.run() + } - override fun postToMainThread(runnable: Runnable) { - runnable.run() - } + override fun postToMainThread(runnable: Runnable) { + runnable.run() + } - override fun isMainThread(): Boolean { - return true - } - }) + override fun isMainThread(): Boolean = true + }, + ) } private fun stopExecuteTaskImmediately() { diff --git a/core-test/src/main/java/app/futured/arkitekt/core/viewmodel/ViewModelTest.kt b/core-test/src/main/java/app/futured/arkitekt/core/viewmodel/ViewModelTest.kt index 694fcda0..7f73b7b0 100644 --- a/core-test/src/main/java/app/futured/arkitekt/core/viewmodel/ViewModelTest.kt +++ b/core-test/src/main/java/app/futured/arkitekt/core/viewmodel/ViewModelTest.kt @@ -35,7 +35,6 @@ import org.junit.Rule * } */ open class ViewModelTest { - /* class SampleViewModelTest : ViewModelTest() { @@ -60,7 +59,7 @@ open class ViewModelTest { ... } } - */ + */ /** * Swap background android executor with the one that executes task synchronously. diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 49cf05e4..486758f1 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -49,7 +49,7 @@ mavenPublishing { coordinates( groupId = ProjectSettings.group, artifactId = "core", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) pom { name = "Arkitekt Core" diff --git a/cr-usecases-test/build.gradle.kts b/cr-usecases-test/build.gradle.kts index 56b7220e..4aa7987c 100644 --- a/cr-usecases-test/build.gradle.kts +++ b/cr-usecases-test/build.gradle.kts @@ -48,7 +48,7 @@ mavenPublishing { coordinates( groupId = ProjectSettings.group, artifactId = "cr-usecases-test", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) pom { name = "Arkitekt CR UseCases Test" diff --git a/cr-usecases-test/src/main/java/app/futured/arkitekt/crusecases/test/UseCase.kt b/cr-usecases-test/src/main/java/app/futured/arkitekt/crusecases/test/UseCase.kt index b889821f..4a687d8b 100644 --- a/cr-usecases-test/src/main/java/app/futured/arkitekt/crusecases/test/UseCase.kt +++ b/cr-usecases-test/src/main/java/app/futured/arkitekt/crusecases/test/UseCase.kt @@ -16,7 +16,10 @@ import io.mockk.mockk * Usage: * mockUseCase.mockExecute(args = ...) { ... } */ -fun > USE_CASE.mockExecute(args: ARGS, returnBlock: () -> RETURN_VALUE) { +fun > USE_CASE.mockExecute( + args: ARGS, + returnBlock: () -> RETURN_VALUE, +) { mockDeferred() coEvery { this@mockExecute.build(args) } answers { returnBlock() } } @@ -44,7 +47,10 @@ inline fun > USE_CASE.mockExecuteNullable(args: ARGS?, crossinline returnBlock: () -> RETURN_VALUE) { +inline fun > USE_CASE.mockExecuteNullable( + args: ARGS?, + crossinline returnBlock: () -> RETURN_VALUE, +) { mockDeferred() coEvery { this@mockExecuteNullable.build(args) } answers { returnBlock() } } diff --git a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt index 414c5e6b..3746832e 100644 --- a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt +++ b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/FlowUseCaseTests.kt @@ -13,7 +13,6 @@ import org.junit.Test @ExperimentalCoroutinesApi class FlowUseCaseTests { - class TestUseCase : FlowUseCase() { override fun build(args: String): Flow { throw IllegalStateException("THIS SHOULD NOT BE CALLED") diff --git a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/TestCoroutineScopeOwner.kt b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/TestCoroutineScopeOwner.kt index addfd679..8ef717f4 100644 --- a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/TestCoroutineScopeOwner.kt +++ b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/TestCoroutineScopeOwner.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.test.TestScope @ExperimentalCoroutinesApi class TestCoroutineScopeOwner : CoroutineScopeOwner { - val testDispatcher = StandardTestDispatcher() override val coroutineScope = TestScope(testDispatcher) diff --git a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt index e875bdf3..77c410f2 100644 --- a/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt +++ b/cr-usecases-test/src/test/java/app/futured/arkitekt/crusecases/test/UseCaseTests.kt @@ -15,17 +15,12 @@ import org.junit.Test @ExperimentalCoroutinesApi class UseCaseTests { - class TestUseCase : UseCase() { - override suspend fun build(args: String): String { - throw IllegalStateException("THIS SHOULD NOT BE CALLED") - } + override suspend fun build(args: String): String = throw IllegalStateException("THIS SHOULD NOT BE CALLED") } class TestUseCaseNullable : UseCase() { - override suspend fun build(args: String?): String { - throw IllegalStateException("THIS SHOULD NOT BE CALLED") - } + override suspend fun build(args: String?): String = throw IllegalStateException("THIS SHOULD NOT BE CALLED") } private val mockUseCase: TestUseCase = mockk() diff --git a/cr-usecases/build.gradle.kts b/cr-usecases/build.gradle.kts index 7d269129..5b021957 100644 --- a/cr-usecases/build.gradle.kts +++ b/cr-usecases/build.gradle.kts @@ -43,14 +43,14 @@ mavenPublishing { coordinates( groupId = ProjectSettings.group, artifactId = "cr-usecases", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) configure( KotlinMultiplatform( javadocJar = JavadocJar.Empty(), sourcesJar = true, androidVariantsToPublish = listOf("debug", "release"), - ) + ), ) pom { name = "Arkitekt CR UseCases" diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt index fb23271d..d48ac391 100644 --- a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/CoroutineScopeOwner.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.launch * It is your responsibility to cancel [coroutineScope] when all running tasks should be stopped. */ interface CoroutineScopeOwner { - /** * [CoroutineScope] scope used to execute coroutine based use cases. It is your responsibility to cancel it when all running * tasks should be stopped @@ -56,7 +55,5 @@ interface CoroutineScopeOwner { * This method is called when coroutine launched with [launchWithHandler] throws an exception and * this exception isn't [CancellationException]. By default, it rethrows this exception. */ - fun defaultErrorHandler(exception: Throwable) { - throw exception - } + fun defaultErrorHandler(exception: Throwable): Unit = throw exception } diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt index 39ffa057..865d1022 100644 --- a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt @@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.Flow * Base [Flow] use case meant to use in [CoroutineScopeOwner] implementations */ abstract class FlowUseCase { - /** * [Job] used to hold and cancel existing run of this use case */ diff --git a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/error/UseCaseErrorHandler.kt b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/error/UseCaseErrorHandler.kt index 1e7841bd..6b61dfc8 100644 --- a/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/error/UseCaseErrorHandler.kt +++ b/cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/error/UseCaseErrorHandler.kt @@ -4,7 +4,6 @@ package app.futured.arkitekt.crusecases.error * This object is used for global handling and logging of errors that are thrown in the use case execution. */ object UseCaseErrorHandler { - /** * Lambda expression that is invoked every time when an error * is propagated to the subscriber. It should be used for diff --git a/decompose-annotation/build.gradle.kts b/decompose-annotation/build.gradle.kts index 3668d084..e3b13e09 100644 --- a/decompose-annotation/build.gradle.kts +++ b/decompose-annotation/build.gradle.kts @@ -16,7 +16,7 @@ mavenPublishing { coordinates( groupId = ProjectSettings.group, artifactId = "decompose-annotation", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) pom { name = "Arkitekt Decompose Annotation" diff --git a/decompose-processor/build.gradle.kts b/decompose-processor/build.gradle.kts index 7b80ba8a..b6732839 100644 --- a/decompose-processor/build.gradle.kts +++ b/decompose-processor/build.gradle.kts @@ -25,7 +25,7 @@ mavenPublishing { coordinates( groupId = ProjectSettings.group, artifactId = "decompose-processor", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) pom { name = "Arkitekt Decompose Processor" diff --git a/decompose/build.gradle.kts b/decompose/build.gradle.kts index 830928c4..024a22a3 100644 --- a/decompose/build.gradle.kts +++ b/decompose/build.gradle.kts @@ -53,13 +53,13 @@ mavenPublishing { KotlinMultiplatform( javadocJar = JavadocJar.Empty(), sourcesJar = true, - androidVariantsToPublish = listOf("debug", "release") - ) + androidVariantsToPublish = listOf("debug", "release"), + ), ) coordinates( groupId = ProjectSettings.group, artifactId = "decompose", - version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT" + version = project.findProperty("VERSION_NAME") as String? ?: "6.X.X-SNAPSHOT", ) pom { name = "Arkitekt Decompose" diff --git a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt index 0beba764..8d618eae 100644 --- a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt +++ b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/coroutines/ValueStateFlow.kt @@ -13,8 +13,9 @@ import kotlinx.coroutines.flow.StateFlow * @property decomposeValue The Decompose Value to be wrapped. */ @OptIn(ExperimentalForInheritanceCoroutinesApi::class) -internal class ValueStateFlow(private val decomposeValue: Value) : StateFlow { - +internal class ValueStateFlow( + private val decomposeValue: Value, +) : StateFlow { override val value: T get() = decomposeValue.value diff --git a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt index 6b611252..82c0fac2 100644 --- a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt +++ b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt @@ -7,7 +7,10 @@ import com.arkivanov.decompose.router.stack.StackNavigator * The same as [StackNavigation.bringToFront] but does not recreate [configuration] if it's class is already on stack and * the classes are not equal. */ -inline fun StackNavigator.switchTab(configuration: C, crossinline onComplete: () -> Unit = {}) { +inline fun StackNavigator.switchTab( + configuration: C, + crossinline onComplete: () -> Unit = {}, +) { navigate( transformer = { stack -> val existing = stack.find { it::class == configuration::class } diff --git a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/navigation/ResultFlow.kt b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/navigation/ResultFlow.kt index 7ee5baef..3e918e00 100644 --- a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/navigation/ResultFlow.kt +++ b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/navigation/ResultFlow.kt @@ -67,8 +67,9 @@ interface ResultFlow : Flow { * @param T The type of results that flow through this ResultFlow * @param backingFlow The underlying MutableSharedFlow used for result propagation */ -internal class ResultFlowImpl(private val backingFlow: MutableSharedFlow = MutableSharedFlow()) : ResultFlow { - +internal class ResultFlowImpl( + private val backingFlow: MutableSharedFlow = MutableSharedFlow(), +) : ResultFlow { override suspend fun collect(collector: FlowCollector) = backingFlow.collect(collector) override suspend fun sendResult(item: T) = backingFlow.emit(item) @@ -88,8 +89,9 @@ internal class ResultFlowImpl(private val backingFlow: MutableSharedFlow = * @param T The type of results that flow through the ResultFlow * @param dataSerializer The serializer for the data type T (used for descriptor only) */ -internal class ResultFlowSerializer(private val dataSerializer: KSerializer) : KSerializer> { - +internal class ResultFlowSerializer( + private val dataSerializer: KSerializer, +) : KSerializer> { override val descriptor: SerialDescriptor get() = dataSerializer.descriptor @@ -99,7 +101,10 @@ internal class ResultFlowSerializer(private val dataSerializer: KSerializer) = Unit + override fun serialize( + encoder: Encoder, + value: ResultFlow, + ) = Unit /** * Deserializes a ResultFlow by creating a new empty instance. diff --git a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/presentation/BaseComponent.kt b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/presentation/BaseComponent.kt index e4a49b37..bee0ff9a 100644 --- a/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/presentation/BaseComponent.kt +++ b/decompose/src/commonMain/kotlin/app/futured/arkitekt/decompose/presentation/BaseComponent.kt @@ -27,7 +27,6 @@ abstract class BaseComponent( componentContext: GenericComponentContext<*>, private val defaultState: VS, ) { - /** * An internal state of the component of type [VS]. */ @@ -39,9 +38,10 @@ abstract class BaseComponent( * The coroutine scope tied to the lifecycle of the component. * It is cancelled when the component is destroyed. */ - protected val componentCoroutineScope = MainScope().also { scope -> - componentContext.lifecycle.doOnDestroy { scope.cancel() } - } + protected val componentCoroutineScope = + MainScope().also { scope -> + componentContext.lifecycle.doOnDestroy { scope.cancel() } + } /** * Converts a [Flow] of component states to a [StateFlow]. @@ -49,8 +49,7 @@ abstract class BaseComponent( * @param started The [SharingStarted] strategy for the [StateFlow]. * @return A [StateFlow] emitting the values of the [Flow]. */ - protected fun Flow.asStateFlow(started: SharingStarted = SharingStarted.Lazily) = - stateIn(componentCoroutineScope, started, defaultState) + protected fun Flow.asStateFlow(started: SharingStarted = SharingStarted.Lazily) = stateIn(componentCoroutineScope, started, defaultState) // endregion @@ -64,9 +63,10 @@ abstract class BaseComponent( /** * Flow of UI events. */ - val events: Flow = uiEventChannel - .receiveAsFlow() - .shareIn(componentCoroutineScope, SharingStarted.Lazily) + val events: Flow = + uiEventChannel + .receiveAsFlow() + .shareIn(componentCoroutineScope, SharingStarted.Lazily) // endregion diff --git a/detekt.yml b/detekt.yml index df080e92..d912ed2e 100644 --- a/detekt.yml +++ b/detekt.yml @@ -96,6 +96,8 @@ naming: active: true FunctionNaming: active: true + ignoreAnnotated: + - Composable FunctionParameterNaming: active: true MatchingDeclarationName: diff --git a/example/src/main/java/app/futured/arkitekt/sample/App.kt b/example/src/main/java/app/futured/arkitekt/sample/App.kt index a728aeb0..1f1bc366 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/App.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/App.kt @@ -1,7 +1,7 @@ package app.futured.arkitekt.sample -import android.content.Context import android.app.Application +import android.content.Context import android.util.Log import androidx.multidex.MultiDex import app.futured.arkitekt.crusecases.error.UseCaseErrorHandler diff --git a/example/src/main/java/app/futured/arkitekt/sample/injection/ApplicationComponent.kt b/example/src/main/java/app/futured/arkitekt/sample/injection/ApplicationComponent.kt index 078211f2..58b2843a 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/injection/ApplicationComponent.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/injection/ApplicationComponent.kt @@ -9,16 +9,16 @@ import javax.inject.Singleton @Singleton @Component( modules = [ - ApplicationModule::class - ] + ApplicationModule::class, + ], ) interface ApplicationComponent { fun inject(app: App) + fun inject(activity: MainActivity) @Component.Builder interface Builder { - @BindsInstance fun application(app: App): Builder diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/bottomsheet/BottomSheetScreen.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/bottomsheet/BottomSheetScreen.kt index a3195885..cd78c4f2 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/bottomsheet/BottomSheetScreen.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/bottomsheet/BottomSheetScreen.kt @@ -30,9 +30,10 @@ fun BottomSheetScreen( } Column( - modifier = modifier - .fillMaxSize() - .padding(24.dp), + modifier = + modifier + .fillMaxSize() + .padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt index edec0172..3a77c70b 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BackStackNavigator.kt @@ -5,13 +5,17 @@ import androidx.navigation3.runtime.NavKey interface BackStackNavigator { fun onBack() + fun onNavigate(destination: T) } -class BackStackNavigatorImpl(private val backStack: NavBackStack) : BackStackNavigator { +class BackStackNavigatorImpl( + private val backStack: NavBackStack, +) : BackStackNavigator { override fun onBack() { backStack.removeLastOrNull() } + override fun onNavigate(destination: T) { backStack.add(destination) } diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BottomSheetSceneStrategy.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BottomSheetSceneStrategy.kt index 2fb93845..cf318724 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BottomSheetSceneStrategy.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/compose/BottomSheetSceneStrategy.kt @@ -20,7 +20,6 @@ internal class BottomSheetScene( private val modalBottomSheetProperties: ModalBottomSheetProperties, private val onBack: () -> Unit, ) : OverlayScene { - override val entries: List> = listOf(entry) override val content: @Composable (() -> Unit) = { @@ -41,7 +40,6 @@ internal class BottomSheetScene( */ @OptIn(ExperimentalMaterial3Api::class) class BottomSheetSceneStrategy : SceneStrategy { - override fun SceneStrategyScope.calculateScene(entries: List>): Scene? { val lastEntry = entries.lastOrNull() val bottomSheetProperties = lastEntry?.metadata?.get(BOTTOM_SHEET_KEY) as? ModalBottomSheetProperties @@ -53,7 +51,7 @@ class BottomSheetSceneStrategy : SceneStrategy { overlaidEntries = entries.dropLast(1), entry = lastEntry, modalBottomSheetProperties = properties, - onBack = onBack + onBack = onBack, ) } } @@ -67,9 +65,7 @@ class BottomSheetSceneStrategy : SceneStrategy { * [ModalBottomSheet]. */ @OptIn(ExperimentalMaterial3Api::class) - fun bottomSheet( - modalBottomSheetProperties: ModalBottomSheetProperties = ModalBottomSheetProperties() - ): Map = mapOf(BOTTOM_SHEET_KEY to modalBottomSheetProperties) + fun bottomSheet(modalBottomSheetProperties: ModalBottomSheetProperties = ModalBottomSheetProperties()): Map = mapOf(BOTTOM_SHEET_KEY to modalBottomSheetProperties) internal const val BOTTOM_SHEET_KEY = "bottomsheet" } diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultScreen.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultScreen.kt index 4818e5fb..b496c484 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultScreen.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/coroutinesresult/CoroutinesResultScreen.kt @@ -25,7 +25,7 @@ fun CoroutinesResultScreen( viewModel: CoroutinesResultViewModel = hiltViewModel(), ) { val contentState by viewModel.viewState.contentState.observeAsState( - CoroutinesResultViewState.State.IDLE + CoroutinesResultViewState.State.IDLE, ) val contentDescription by viewModel.viewState.contentStateDescription.observeAsState("") val safeOnBack = dropUnlessResumed { navigator.onBack() } diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt index ea233014..60d53d11 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/form/FormScreen.kt @@ -26,9 +26,10 @@ fun FormScreen( navigator: BackStackNavigator, modifier: Modifier = Modifier, route: ExampleRoute.Form, - viewModel: FormViewModel = hiltViewModel { - it.create(route) - }, + viewModel: FormViewModel = + hiltViewModel { + it.create(route) + }, ) { val context = LocalContext.current val login by viewModel.viewState.login @@ -43,9 +44,10 @@ fun FormScreen( } Column( - modifier = modifier - .fillMaxSize() - .padding(24.dp), + modifier = + modifier + .fillMaxSize() + .padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { OutlinedTextField( diff --git a/example/src/main/java/app/futured/arkitekt/sample/ui/main/MainActivity.kt b/example/src/main/java/app/futured/arkitekt/sample/ui/main/MainActivity.kt index a121e87b..f1bb7a4d 100644 --- a/example/src/main/java/app/futured/arkitekt/sample/ui/main/MainActivity.kt +++ b/example/src/main/java/app/futured/arkitekt/sample/ui/main/MainActivity.kt @@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { diff --git a/example/src/test/java/app/futured/arkitekt/sample/ui/bottomsheet/ExampleViewModelTest.kt b/example/src/test/java/app/futured/arkitekt/sample/ui/bottomsheet/ExampleViewModelTest.kt index d1d76c06..c484a7db 100644 --- a/example/src/test/java/app/futured/arkitekt/sample/ui/bottomsheet/ExampleViewModelTest.kt +++ b/example/src/test/java/app/futured/arkitekt/sample/ui/bottomsheet/ExampleViewModelTest.kt @@ -7,7 +7,6 @@ import org.junit.Before import org.junit.Test class ExampleViewModelTest : ViewModelTest() { - lateinit var viewState: ExampleViewState lateinit var viewModel: ExampleViewModel diff --git a/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt b/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt index 9716d46f..aa47e579 100644 --- a/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt +++ b/example/src/test/java/app/futured/arkitekt/sample/ui/detail/DetailViewModelTest.kt @@ -9,7 +9,6 @@ import org.junit.Before import org.junit.Test class DetailViewModelTest : ViewModelTest() { - lateinit var viewState: DetailViewState lateinit var viewModel: DetailViewModel diff --git a/example/src/test/java/app/futured/arkitekt/sample/ui/form/FormViewModelTest.kt b/example/src/test/java/app/futured/arkitekt/sample/ui/form/FormViewModelTest.kt index 8b84dcbe..87cb013d 100644 --- a/example/src/test/java/app/futured/arkitekt/sample/ui/form/FormViewModelTest.kt +++ b/example/src/test/java/app/futured/arkitekt/sample/ui/form/FormViewModelTest.kt @@ -21,7 +21,6 @@ import org.junit.Before import org.junit.Test class FormViewModelTest : ViewModelTest() { - val mockSaveFormUseCase: SaveFormUseCase = mockk() val mockObserveFormUseCase: ObserveFormUseCase = mockk() @@ -35,16 +34,21 @@ class FormViewModelTest : ViewModelTest() { viewModel = createViewModel() } - private fun createViewModel() = spyk( - FormViewModel(mockSaveFormUseCase, mockObserveFormUseCase, viewState, route = ExampleRoute.Form("form")), - recordPrivateCalls = true - ).also { - every { it.getWorkerDispatcher() } returns Dispatchers.Main - } + private fun createViewModel() = + spyk( + FormViewModel(mockSaveFormUseCase, mockObserveFormUseCase, viewState, route = ExampleRoute.Form("form")), + recordPrivateCalls = true, + ).also { + every { it.getWorkerDispatcher() } returns Dispatchers.Main + } - private fun awaitInit() = runBlocking { - viewModel.coroutineScope.coroutineContext[Job]!!.children.toList().forEach { it.join() } - } + private fun awaitInit() = + runBlocking { + viewModel.coroutineScope.coroutineContext[Job]!! + .children + .toList() + .forEach { it.join() } + } @Test fun `when onSubmit is called then form is saved and ShowToastEvent is send`() { diff --git a/example/src/test/java/app/futured/arkitekt/sample/ui/login/fragment/LoginViewModelTest.kt b/example/src/test/java/app/futured/arkitekt/sample/ui/login/fragment/LoginViewModelTest.kt index 6a06f289..a997650f 100644 --- a/example/src/test/java/app/futured/arkitekt/sample/ui/login/fragment/LoginViewModelTest.kt +++ b/example/src/test/java/app/futured/arkitekt/sample/ui/login/fragment/LoginViewModelTest.kt @@ -24,7 +24,6 @@ import org.junit.Before import org.junit.Test class LoginViewModelTest : ViewModelTest() { - val mockLoginCompletabler: SyncLoginUseCase = mockk() val mockObserveUserFullNameUseCase: ObserveUserFullNameUseCase = mockk() val mockGetStateUseCase: GetStateUseCase = mockk() diff --git a/settings.gradle.kts b/settings.gradle.kts index 62267c98..76bbfad3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,5 +11,5 @@ include( ":cr-usecases", ":arkitekt-lint", ":core-test", - ":cr-usecases-test" + ":cr-usecases-test", ) From c3de8c0801eb2d09518879abc1205be732b7862c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rudolf=20Hlad=C3=ADk?= Date: Fri, 20 Mar 2026 13:02:12 +0100 Subject: [PATCH 5/5] Update detekt config --- build.gradle.kts | 2 +- detekt.yml | 271 ++++++++--------------------------------------- 2 files changed, 47 insertions(+), 226 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 27b78de0..ef10536b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -86,7 +86,7 @@ detekt { "decompose-annotation/src/commonMain/kotlin", "decompose-processor/src/jvmMain/kotlin", "arkitekt-lint/src/main/java", - ) + ), ) // filters = ".*/resources/.*,.*/build/.*" config.setFrom(files("detekt.yml")) diff --git a/detekt.yml b/detekt.yml index d912ed2e..dc52161b 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,248 +1,69 @@ build: maxIssues: 100 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 -complexity: - active: true - ComplexCondition: - active: true - ComplexMethod: - active: true - threshold: 20 - ignoreSimpleWhenEntries: true - LargeClass: - threshold: 250 - active: true - TooManyFunctions: - active: true - thresholdInFiles: 30 - thresholdInClasses: 30 - thresholdInInterfaces: 30 - thresholdInObjects: 25 - LongParameterList: - active: true - NestedBlockDepth: - active: true - threshold: 4 - StringLiteralDuplication: - active: true - -empty-blocks: - active: true - EmptyCatchBlock: - active: true - EmptyClassBlock: - active: true - EmptyDefaultConstructor: - active: true - EmptyDoWhileBlock: - active: true - EmptyElseBlock: - active: true - EmptyFinallyBlock: - active: true - EmptyForBlock: - active: true - EmptyFunctionBlock: - active: true - EmptyIfBlock: - active: true - EmptyInitBlock: - active: true - EmptyKtFile: - active: true - EmptySecondaryConstructor: - active: true - EmptyWhenBlock: - active: true - EmptyWhileBlock: - active: true - -exceptions: - active: true - ExceptionRaisedInUnexpectedLocation: - active: true - InstanceOfCheckForException: - active: true - ReturnFromFinally: - active: true - TooGenericExceptionCaught: - active: false - SwallowedException: - active: true - ignoredExceptionTypes: 'CancellationException' - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionsWithoutMessageOrCause: - active: true - ThrowingNewInstanceOfSameException: - active: true - TooGenericExceptionThrown: - active: true +config: + validation: true + warningsAsErrors: false + checkExhaustiveness: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' naming: active: true - ClassNaming: - active: true - ConstructorParameterNaming: - active: true - EnumNaming: - active: true - ForbiddenClassName: - active: true - FunctionMaxLength: - active: true - maximumFunctionNameLength: 35 - FunctionMinLength: - active: true + MatchingDeclarationName: + active: false FunctionNaming: active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' ignoreAnnotated: - Composable - FunctionParameterNaming: - active: true - MatchingDeclarationName: - active: true - MemberNameEqualsClassName: - active: true - ObjectPropertyNaming: - active: true - PackageNaming: - active: true + - Preview TopLevelPropertyNaming: - active: true - VariableMaxLength: - active: true - maximumVariableNameLength: 50 - VariableMinLength: - active: true - VariableNaming: - active: true - -performance: - ArrayPrimitive: - active: true - ForEachOnRange: - active: true - SpreadOperator: - active: true - UnnecessaryTemporaryInstantiation: - active: true - -potential-bugs: - active: true - DuplicateCaseInWhenExpression: - active: true - EqualsAlwaysReturnsTrueOrFalse: - active: true - EqualsWithHashCodeExist: - active: true - ExplicitGarbageCollectionCall: - active: true - InvalidRange: - active: true - IteratorHasNextCallsNextMethod: - active: true - IteratorNotThrowingNoSuchElementException: - active: true - UnconditionalJumpStatementInLoop: - active: true - UnreachableCode: - active: true - UnsafeCallOnNullableType: - active: true - UnsafeCast: - active: true - UselessPostfixExpression: - active: true - WrongEqualsTypeParameter: - active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' style: active: true - MaxLineLength: - active: false - CollapsibleIfStatements: - active: true - DataClassContainsFunctions: - active: false - EqualsNullCall: - active: true - ExplicitItLambdaParameter: - active: true - ExpressionBodySyntax: - active: true - ForbiddenComment: + UnusedPrivateProperty: active: true - ForbiddenImport: - active: true - ForbiddenVoid: - active: true - FunctionOnlyReturningConstant: + allowedNames: '_|ignored|expected|serialVersionUID|commonMain|iosMain|androidMain|commonTest|iosTest|androidTest|debug|enterprise|release' + UnusedPrivateMember: active: true - LoopWithTooManyJumpStatements: + ignoreAnnotated: + - Composable + - Preview + MaxLineLength: active: true + maxLineLength: 140 # Keep aligned with .editorconfig file to prevent false positives MagicNumber: active: true - MandatoryBracesIfStatements: - active: true - MayBeConst: - active: true - ModifierOrder: - active: true - NestedClassesVisibility: - active: false - NewLineAtEndOfFile: - active: true - NoTabs: - active: true - OptionalAbstractKeyword: - active: true - OptionalUnit: - active: true - OptionalWhenBraces: - active: true - PreferToOverPairSyntax: - active: false - ProtectedMemberInFinalClass: - active: true - RedundantVisibilityModifierRule: - active: true - ReturnCount: - active: true - max: 4 - SafeCast: - active: true - SerialVersionUIDInSerializableClass: - active: true - SpacingBetweenPackageAndImports: - active: true - ThrowsCount: - active: true - TrailingWhitespace: - active: true - UnnecessaryAbstractClass: - active: false - ignoreAnnotated: ["dagger.Module,android.arch.persistence.room.Dao"] - UnnecessaryApply: - active: false # wait for fix - UnnecessaryInheritance: - active: true - UnnecessaryLet: - active: true - UnnecessaryParentheses: - active: true - UntilInsteadOfRangeTo: - active: true - UnusedImports: - active: false - UnusedPrivateMember: - active: true - UseDataClass: + ignoreAnnotated: + - Preview + DestructuringDeclarationWithTooManyEntries: active: true - UtilityClassWithPublicConstructor: + ignoreAnnotated: + - Composable + +complexity: + LongParameterList: active: true - VarCouldBeVal: + ignoreAnnotated: + - Composable + TooManyFunctions: active: true - WildcardImport: + thresholdInInterfaces: 25 + thresholdInClasses: 25 + thresholdInFiles: 20 + thresholdInObjects: 20 + LongMethod: active: true - excludeImports: 'kotlinx.android.synthetic.*' + ignoreAnnotated: + - Composable + - Preview