diff --git a/build.gradle b/build.gradle index 37eb9a7..c5e5bc3 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,24 @@ spotless { "disabled_rules" : "package-name", "ij_kotlin_allow_trailing_comma" : "true", "ij_kotlin_allow_trailing_comma_on_call_site": "true", + "ktlint_standard_annotation" : "disabled", + "ktlint_standard_annotation-spacing" : "disabled", + "ktlint_standard_backing-property-naming" : "disabled", + "ktlint_standard_comment-wrapping" : "disabled", + "ktlint_standard_enum-entry-name-case" : "disabled", + "ktlint_standard_filename" : "disabled", + "ktlint_standard_package-name" : "disabled", + // Making something an expression body should be a choice around readability. + "ktlint_standard_function-expression-body" : "disabled", + // Temporarily disabled because these got more strict in a ktlint update. + "ktlint_standard_class-naming" : "disabled", + "ktlint_standard_function-naming" : "disabled", + "ktlint_standard_kdoc-wrapping" : "disabled", + "ktlint_standard_no-empty-file" : "disabled", + "ktlint_standard_property-naming" : "disabled", + "ktlint_standard_value-parameter-comment" : "disabled", + // Is disabled by default since ktlint 1.7.0. + "ktlint_standard_no-unused-imports" : "enabled", ]) trimTrailingWhitespace() endWithNewline() diff --git a/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/DynamicFeaturesSymbolProcessorProvider.kt b/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/DynamicFeaturesSymbolProcessorProvider.kt index 2d195a3..94ded36 100644 --- a/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/DynamicFeaturesSymbolProcessorProvider.kt +++ b/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/DynamicFeaturesSymbolProcessorProvider.kt @@ -20,6 +20,5 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider class DynamicFeaturesSymbolProcessorProvider : SymbolProcessorProvider { - override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = - FeatureModuleSymbolProcessor(environment) + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = FeatureModuleSymbolProcessor(environment) } diff --git a/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessor.kt b/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessor.kt index 74f4183..27feaa6 100644 --- a/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessor.kt +++ b/codegen/ksp/src/main/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessor.kt @@ -34,8 +34,7 @@ import kotlin.io.path.Path import kotlin.io.path.div import kotlin.io.path.outputStream -class FeatureModuleSymbolProcessor(private val environment: SymbolProcessorEnvironment) : - SymbolProcessor { +class FeatureModuleSymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor { private val logger = environment.logger private val reportDirectoryPaths = environment.options.filterKeys { it.startsWith(KSP_REPORT_DIRECTORY_PREFIX) }.map { (_, path) -> Path(path) } @@ -47,17 +46,21 @@ class FeatureModuleSymbolProcessor(private val environment: SymbolProcessorEnvir resolver.getSymbolsWithAnnotation(RUNTIME_IMPLEMENTATION_ANNOTATION, inDepth = true) .filterIsInstance() .onEach { implementation -> - if (implementation.isAbstract()) logger.error( - "'@DynamicImplementation' cannot be applied to abstract classes.", - symbol = implementation, - ) + if (implementation.isAbstract()) { + logger.error( + "'@DynamicImplementation' cannot be applied to abstract classes.", + symbol = implementation, + ) + } } .map { implementation -> implementation to findApiSuperType(listOf(implementation)) } .filter { (implementation, superType) -> - if (superType == null) logger.error( - "Class does not inherit from a 'DynamicApi' super type.", - symbol = implementation, - ) + if (superType == null) { + logger.error( + "Class does not inherit from a 'DynamicApi' super type.", + symbol = implementation, + ) + } superType != null } diff --git a/codegen/ksp/src/test/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessorTests.kt b/codegen/ksp/src/test/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessorTests.kt index 7f56b4c..5a72f87 100644 --- a/codegen/ksp/src/test/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessorTests.kt +++ b/codegen/ksp/src/test/kotlin/app/cash/better/dynamic/features/codegen/ksp/FeatureModuleSymbolProcessorTests.kt @@ -138,21 +138,19 @@ class FeatureModuleSymbolProcessorTests { .contains("""{"qualifiedName":"test.MyImplementation","parentClass":{"packageName":"test","className":"MyApi"}}""") } - private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation = - KotlinCompilation().apply { - workingDir = workingDirectory.root - inheritClassPath = true - sources = sourceFiles.asList() - verbose = true - kspIncremental = true - useKsp2() - - symbolProcessorProviders = mutableListOf(DynamicFeaturesSymbolProcessorProvider()) - kspProcessorOptions[KSP_REPORT_DIRECTORY_PREFIX] = resultsDirectory.root.absolutePath - } - - private fun compile(vararg sourceFile: SourceFile): JvmCompilationResult = - prepareCompilation(*sourceFile).compile() + private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation = KotlinCompilation().apply { + workingDir = workingDirectory.root + inheritClassPath = true + sources = sourceFiles.asList() + verbose = true + kspIncremental = true + useKsp2() + + symbolProcessorProviders = mutableListOf(DynamicFeaturesSymbolProcessorProvider()) + kspProcessorOptions[KSP_REPORT_DIRECTORY_PREFIX] = resultsDirectory.root.absolutePath + } + + private fun compile(vararg sourceFile: SourceFile): JvmCompilationResult = prepareCompilation(*sourceFile).compile() private fun assertThatFileContent(file: File) = assertThat(file.readText()) } diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt index 09f77ed..3abd9d4 100644 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt +++ b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/BetterDynamicFeaturesPlugin.kt @@ -115,10 +115,10 @@ class BetterDynamicFeaturesPlugin : Plugin { androidComponents.onVariants { variant -> // We only want to enforce the lockfile if we aren't explicitly trying to update it if (project.gradle.startParameter.taskNames.none { - it.contains("writeLockfile", ignoreCase = true) || - it.contains(WRITE_DEPENDENCY_GRAPH_REGEX) || - it.contains("checkLockfile", ignoreCase = true) - } + it.contains("writeLockfile", ignoreCase = true) || + it.contains(WRITE_DEPENDENCY_GRAPH_REGEX) || + it.contains("checkLockfile", ignoreCase = true) + } ) { project.configurations.named("${variant.name}RuntimeClasspath").configure { it.resolutionStrategy.activateDependencyLocking() @@ -132,79 +132,75 @@ class BetterDynamicFeaturesPlugin : Plugin { project.setupBaseResourcesCheckingTasks(androidComponents, pluginExtension) } - private fun Configuration.getConfigurationArtifactCollection(): ArtifactCollection = - incoming.artifactView { config -> - config.attributes { container -> - container.attribute( - AndroidArtifacts.ARTIFACT_TYPE, - AndroidArtifacts.ArtifactType.AAR_OR_JAR.type, - ) - } - - // Look only for external dependencies - config.componentFilter { it !is ProjectComponentIdentifier } - }.artifacts - - private fun Project.configureDependencyGraphTask(variant: Variant): TaskProvider = - tasks.register( - "write${variant.name.capitalized()}DependencyGraph", - DependencyGraphWriterTask::class.java, - ) { task -> - val artifactCollections = project.configurations.getByName("${variant.name}RuntimeClasspath") - .getConfigurationArtifactCollection() - - task.dependencyFileCollection.setFrom(artifactCollections.artifactFiles) - - task.setResolvedLockfileEntriesProvider( - project.provider { - val runtime = project.configurations.getByName("${variant.name}RuntimeClasspath") - .incoming - .resolutionResult - .root - - val compile = project.configurations.getByName("${variant.name}CompileClasspath") - .incoming - .resolutionResult - .root - - ResolvedComponentResultPair(runtime, compile) - }, - variant.name, + private fun Configuration.getConfigurationArtifactCollection(): ArtifactCollection = incoming.artifactView { config -> + config.attributes { container -> + container.attribute( + AndroidArtifacts.ARTIFACT_TYPE, + AndroidArtifacts.ArtifactType.AAR_OR_JAR.type, ) - - task.partialLockFile.set { buildDir.resolve("betterDynamicFeatures/deps/${variant.name}DependencyGraph.json") } - task.group = GROUP } - private fun Project.createSharedFeatureConfiguration(): Configuration = - configurations.create(CONFIGURATION_BDF).apply { - isCanBeConsumed = true - isCanBeResolved = false - isVisible = false + // Look only for external dependencies + config.componentFilter { it !is ProjectComponentIdentifier } + }.artifacts + + private fun Project.configureDependencyGraphTask(variant: Variant): TaskProvider = tasks.register( + "write${variant.name.capitalized()}DependencyGraph", + DependencyGraphWriterTask::class.java, + ) { task -> + val artifactCollections = project.configurations.getByName("${variant.name}RuntimeClasspath") + .getConfigurationArtifactCollection() + + task.dependencyFileCollection.setFrom(artifactCollections.artifactFiles) + + task.setResolvedLockfileEntriesProvider( + project.provider { + val runtime = project.configurations.getByName("${variant.name}RuntimeClasspath") + .incoming + .resolutionResult + .root + + val compile = project.configurations.getByName("${variant.name}CompileClasspath") + .incoming + .resolutionResult + .root + + ResolvedComponentResultPair(runtime, compile) + }, + variant.name, + ) - attributes.apply { - attribute( - Usage.USAGE_ATTRIBUTE, - project.objects.named(Usage::class.java, ATTRIBUTE_USAGE_METADATA), - ) - } - outgoing.variants.create(VARIANT_DEPENDENCY_GRAPHS) + task.partialLockFile.set { buildDir.resolve("betterDynamicFeatures/deps/${variant.name}DependencyGraph.json") } + task.group = GROUP + } + + private fun Project.createSharedFeatureConfiguration(): Configuration = configurations.create(CONFIGURATION_BDF).apply { + isCanBeConsumed = true + isCanBeResolved = false + isVisible = false + + attributes.apply { + attribute( + Usage.USAGE_ATTRIBUTE, + project.objects.named(Usage::class.java, ATTRIBUTE_USAGE_METADATA), + ) } + outgoing.variants.create(VARIANT_DEPENDENCY_GRAPHS) + } - private fun Project.createSharedBaseConfiguration(): Configuration = - configurations.create(CONFIGURATION_BDF).apply { - isCanBeConsumed = true - isCanBeResolved = true - isVisible = false + private fun Project.createSharedBaseConfiguration(): Configuration = configurations.create(CONFIGURATION_BDF).apply { + isCanBeConsumed = true + isCanBeResolved = true + isVisible = false - attributes.apply { - attribute( - Usage.USAGE_ATTRIBUTE, - project.objects.named(Usage::class.java, ATTRIBUTE_USAGE_METADATA), - ) - } - outgoing.variants.create(VARIANT_DEPENDENCY_GRAPHS) + attributes.apply { + attribute( + Usage.USAGE_ATTRIBUTE, + project.objects.named(Usage::class.java, ATTRIBUTE_USAGE_METADATA), + ) } + outgoing.variants.create(VARIANT_DEPENDENCY_GRAPHS) + } private fun Project.setupFeatureDependencyGraphTasks( androidComponents: AndroidComponentsExtension<*, *, *>, @@ -501,7 +497,6 @@ class BetterDynamicFeaturesPlugin : Plugin { Regex("write(.+)DependencyGraph", RegexOption.IGNORE_CASE) @Suppress("FunctionName") - private fun VARIANT_FEATURE_IMPLEMENTATION(variant: String): String = - "$variant-implementations" + private fun VARIANT_FEATURE_IMPLEMENTATION(variant: String): String = "$variant-implementations" } } diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/codegen/CompileTypesafeImplementations.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/codegen/CompileTypesafeImplementations.kt index 61d84f3..0526a54 100644 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/codegen/CompileTypesafeImplementations.kt +++ b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/codegen/CompileTypesafeImplementations.kt @@ -28,8 +28,7 @@ import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream -abstract class CompileTypesafeImplementations : - WorkAction { +abstract class CompileTypesafeImplementations : WorkAction { private val logger = LoggerFactory.getLogger("CompileTypesafeImplementations") override fun execute() { diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/CheckExternalResourcesTask.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/CheckExternalResourcesTask.kt index b804d35..fa148c4 100644 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/CheckExternalResourcesTask.kt +++ b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/CheckExternalResourcesTask.kt @@ -169,8 +169,7 @@ abstract class CheckExternalResourcesTask : DefaultTask() { /** * Transforms a style reference, e.g. `@style/My.Theme.Identifier`, into a compiled resource name e.g. `My_Theme_Identifier`. */ - private fun transformStyleReference(ref: String): String = - ref.replace(Regex("""@(android:)?style\/"""), "").replace(".", "_") + private fun transformStyleReference(ref: String): String = ref.replace(Regex("""@(android:)?style\/"""), "").replace(".", "_") private data class ResourceModule(val namespace: String, val resources: Map>) diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraph.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraph.kt index 678f448..39117ba 100644 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraph.kt +++ b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraph.kt @@ -30,7 +30,7 @@ data class Node( enum class DependencyType { Runtime, - Compile; + Compile, } @JsonClass(generateAdapter = true) @@ -39,8 +39,7 @@ data class NodeList(val nodes: List) @JsonClass(generateAdapter = true) @JvmInline value class Version(val string: String) : Comparable { - override fun compareTo(other: Version): Int = - VersionNumber.parse(string).compareTo(VersionNumber.parse(other.string)) + override fun compareTo(other: Version): Int = VersionNumber.parse(string).compareTo(VersionNumber.parse(other.string)) override fun toString(): String = string } diff --git a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraphWriterTask.kt b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraphWriterTask.kt index e4585d5..192054d 100644 --- a/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraphWriterTask.kt +++ b/gradle-plugin/src/main/kotlin/app/cash/better/dynamic/features/tasks/DependencyGraphWriterTask.kt @@ -40,18 +40,26 @@ abstract class DependencyGraphWriterTask : DefaultTask() { fun setResolvedLockfileEntriesProvider(provider: Provider, variant: String) { graph = provider.map { (runtime, compile) -> - val runtimeNodes = if (runtime.variants.isEmpty()) emptyList() else buildDependencyGraph( - runtime.getDependenciesForVariant(runtime.variants.first()), - variant, - mutableSetOf(), - type = DependencyType.Runtime, - ) - val compileNodes = if (compile.variants.isEmpty()) emptyList() else buildDependencyGraph( - compile.getDependenciesForVariant(compile.variants.first()), - variant, - mutableSetOf(), - type = DependencyType.Compile, - ) + val runtimeNodes = if (runtime.variants.isEmpty()) { + emptyList() + } else { + buildDependencyGraph( + runtime.getDependenciesForVariant(runtime.variants.first()), + variant, + mutableSetOf(), + type = DependencyType.Runtime, + ) + } + val compileNodes = if (compile.variants.isEmpty()) { + emptyList() + } else { + buildDependencyGraph( + compile.getDependenciesForVariant(compile.variants.first()), + variant, + mutableSetOf(), + type = DependencyType.Compile, + ) + } runtimeNodes + compileNodes } @@ -69,24 +77,23 @@ abstract class DependencyGraphWriterTask : DefaultTask() { private val ResolvedDependencyResult.key: String get() = selected.moduleVersion?.let { info -> "${info.group}:${info.name}" } ?: "" - private fun buildDependencyGraph(topLevel: List, variant: String, visited: MutableSet, type: DependencyType): List = - topLevel - .asSequence() - .filterIsInstance() - .filter { !it.isConstraint && it.key !in visited } - .onEach { visited += it.key } - .map { - val info = it.selected.moduleVersion!! - Node( - "${info.group}:${info.name}", - Version(info.version), - mutableSetOf(variant), - children = buildDependencyGraph(it.selected.getDependenciesForVariant(it.resolvedVariant), variant, visited, type), - isProjectModule = it.resolvedVariant.owner is ProjectComponentIdentifier, - type = type, - ) - } - .toList() + private fun buildDependencyGraph(topLevel: List, variant: String, visited: MutableSet, type: DependencyType): List = topLevel + .asSequence() + .filterIsInstance() + .filter { !it.isConstraint && it.key !in visited } + .onEach { visited += it.key } + .map { + val info = it.selected.moduleVersion!! + Node( + "${info.group}:${info.name}", + Version(info.version), + mutableSetOf(variant), + children = buildDependencyGraph(it.selected.getDependenciesForVariant(it.resolvedVariant), variant, visited, type), + isProjectModule = it.resolvedVariant.owner is ProjectComponentIdentifier, + type = type, + ) + } + .toList() data class ResolvedComponentResultPair(val runtime: ResolvedComponentResult, val compile: ResolvedComponentResult) } diff --git a/gradle-plugin/src/test/java/app/cash/better/dynamic/features/BetterDynamicFeaturesPluginTest.kt b/gradle-plugin/src/test/java/app/cash/better/dynamic/features/BetterDynamicFeaturesPluginTest.kt index b3595ed..241cb92 100644 --- a/gradle-plugin/src/test/java/app/cash/better/dynamic/features/BetterDynamicFeaturesPluginTest.kt +++ b/gradle-plugin/src/test/java/app/cash/better/dynamic/features/BetterDynamicFeaturesPluginTest.kt @@ -869,6 +869,5 @@ class BetterDynamicFeaturesPluginTest { root.lockfile().takeIf { it.exists() }?.delete() } - private fun File.lockfile(module: String? = null) = - resolve(if (module != null) "$module/gradle.lockfile" else "gradle.lockfile") + private fun File.lockfile(module: String? = null) = resolve(if (module != null) "$module/gradle.lockfile" else "gradle.lockfile") } diff --git a/gradle-plugin/src/test/java/app/cash/better/dynamic/features/utils/SequenceSubject.kt b/gradle-plugin/src/test/java/app/cash/better/dynamic/features/utils/SequenceSubject.kt index 7fe0918..b2ef0bc 100644 --- a/gradle-plugin/src/test/java/app/cash/better/dynamic/features/utils/SequenceSubject.kt +++ b/gradle-plugin/src/test/java/app/cash/better/dynamic/features/utils/SequenceSubject.kt @@ -20,8 +20,7 @@ import com.google.common.truth.FailureMetadata import com.google.common.truth.Subject import com.google.common.truth.Truth.assertAbout -class SequenceSubject(metadata: FailureMetadata, private val actual: Sequence) : - Subject(metadata, actual) { +class SequenceSubject(metadata: FailureMetadata, private val actual: Sequence) : Subject(metadata, actual) { fun containsInConsecutiveOrder(vararg values: T) { var matchCounter = 0 actual.forEach { @@ -37,10 +36,10 @@ class SequenceSubject(metadata: FailureMetadata, private val actual: Sequence failWithoutActual( simpleFact( "Expected to find ${ - values.joinToString( - prefix = "[", - postfix = "]", - ) + values.joinToString( + prefix = "[", + postfix = "]", + ) } in consecutive order, but they were not found.", ), ) @@ -57,5 +56,4 @@ class SequenceSubject(metadata: FailureMetadata, private val actual: Sequence } } -fun assertThat(actual: Sequence): SequenceSubject = - assertAbout(SequenceSubject.sequences()).that(actual) +fun assertThat(actual: Sequence): SequenceSubject = assertAbout(SequenceSubject.sequences()).that(actual) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b99d8fd..57a7b99 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ compilerTesting = "0.7.1" kotlin = "2.1.20" kotlinPoet = "1.13.1" ksp = "2.1.20-1.0.32" -ktlint = "0.46.1" +ktlint = "1.7.1" moshi = "1.15.2" # Used by the sample app @@ -55,7 +55,7 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } publish = { id = "com.vanniktech.maven.publish", version = "0.34.0" } -spotless = { id = "com.diffplug.spotless", version = "6.18.0" } +spotless = { id = "com.diffplug.spotless", version = "8.0.0" } wire = { id = "com.squareup.wire", version = "5.4.0" } # Used by the sample app diff --git a/sample/app/src/main/kotlin/app/cash/boxapp/api/ServiceRegistry.kt b/sample/app/src/main/kotlin/app/cash/boxapp/api/ServiceRegistry.kt index 450f05e..69bce00 100644 --- a/sample/app/src/main/kotlin/app/cash/boxapp/api/ServiceRegistry.kt +++ b/sample/app/src/main/kotlin/app/cash/boxapp/api/ServiceRegistry.kt @@ -43,7 +43,10 @@ public interface ServiceRegistry { private fun exhaustRunWhenInstalledQueue() { val instance = value ?: return - runWhenInstalledQueue.removeAll { it(instance); true } + runWhenInstalledQueue.removeAll { + it(instance) + true + } } } } as ServiceRegistry diff --git a/sample/app/src/main/kotlin/app/cash/boxapp/ui/MainActivity.kt b/sample/app/src/main/kotlin/app/cash/boxapp/ui/MainActivity.kt index 2f9c1e8..de42748 100644 --- a/sample/app/src/main/kotlin/app/cash/boxapp/ui/MainActivity.kt +++ b/sample/app/src/main/kotlin/app/cash/boxapp/ui/MainActivity.kt @@ -26,7 +26,9 @@ import androidx.compose.runtime.setValue import app.cash.boxapp.api.Navigator import java.util.Stack -internal class MainActivity : ComponentActivity(), Navigator { +internal class MainActivity : + ComponentActivity(), + Navigator { init { Navigator.INSTANCE = this }