From 8b3b370d47699aec4186518bc58d49b3901c47a9 Mon Sep 17 00:00:00 2001 From: Dagger Team Date: Mon, 10 Jun 2024 13:20:12 -0700 Subject: [PATCH] Internal change RELNOTES=n/a PiperOrigin-RevId: 642001855 --- java/dagger/hilt/android/compose/BUILD | 117 -- .../android/compose/ComponentHostCreator.kt | 31 - .../android/compose/ComposeComponentHost.kt | 88 - .../compose/ComposeComponentHostExtras.kt | 131 -- .../compose/ComposeRetainedLifecycle.kt | 33 - .../compose/ComposeRetainedProvided.kt | 69 - .../android/compose/RememberComponentHost.kt | 335 ---- .../compose/RetainedViewModelManager.kt | 45 - .../hilt/android/compose/components/BUILD | 33 - .../compose/components/ComposeComponent.kt | 24 - .../components/ComposeRetainedComponent.kt | 26 - .../hilt/android/compose/internal/BUILD | 30 - .../internal/InternalComponentHostCreator.kt | 45 - .../android/compose/internal/builders/BUILD | 35 - .../builders/ComposeComponentBuilder.kt | 38 - .../ComposeRetainedComponentBuilder.kt | 32 - .../android/compose/internal/qualifiers/BUILD | 30 - .../qualifiers/HiltComposeInternal.kt | 25 - .../android/compose/internal/testing/BUILD | 35 - .../compose/internal/testing/CustomInfo.kt | 56 - .../compose/internal/testing/LabeledNodes.kt | 89 - .../hilt/android/compose/package-info.java | 18 - java/dagger/hilt/android/compose/scopes/BUILD | 29 - .../compose/scopes/ComposeRetainedScoped.kt | 25 - .../android/compose/scopes/ComposeScoped.kt | 22 - .../lifecycle/RetainedLifecycleImpl.java | 4 +- .../compose/composecomponenthost/BUILD | 51 - .../ComposeComponentHostGenerator.kt | 416 ---- .../ComposeComponentHostMetadata.kt | 155 -- .../ComposeComponentHostProcessingStep.kt | 46 - .../ComposeComponentHostProcessor.kt | 35 - .../KspComposeComponentHostProcessor.kt | 39 - .../compose/composeretainedprovided/BUILD | 51 - .../ComposeRetainedProvidedGenerator.kt | 304 --- .../ComposeRetainedProvidedMetadata.kt | 248 --- .../ComposeRetainedProvidedProcessingStep.kt | 75 - .../ComposeRetainedProvidedProcessor.kt | 35 - .../KspComposeRetainedProvidedProcessor.kt | 39 - javatests/dagger/hilt/android/compose/BUILD | 106 -- .../compose/ComposeComponentHostExtrasTest.kt | 174 -- ...omponentHostExtrasTest_AndroidManifest.xml | 14 - .../RememberComponentHostExtrasTest.kt | 509 ----- ...omponentHostExtrasTest_AndroidManifest.xml | 45 - .../compose/RememberComponentHostTest.kt | 1676 ----------------- ...emberComponentHostTest_AndroidManifest.xml | 69 - .../hilt/android/compose/res/values/ids.xml | 5 - .../compose/composecomponenthost/BUILD | 40 - .../ComposeComponentHostProcessorTest.kt | 263 --- .../compose/composeretainedprovided/BUILD | 40 - .../ComposeRetainedProvidedProcessorTest.kt | 715 ------- 50 files changed, 1 insertion(+), 6594 deletions(-) delete mode 100644 java/dagger/hilt/android/compose/BUILD delete mode 100644 java/dagger/hilt/android/compose/ComponentHostCreator.kt delete mode 100644 java/dagger/hilt/android/compose/ComposeComponentHost.kt delete mode 100644 java/dagger/hilt/android/compose/ComposeComponentHostExtras.kt delete mode 100644 java/dagger/hilt/android/compose/ComposeRetainedLifecycle.kt delete mode 100644 java/dagger/hilt/android/compose/ComposeRetainedProvided.kt delete mode 100644 java/dagger/hilt/android/compose/RememberComponentHost.kt delete mode 100644 java/dagger/hilt/android/compose/RetainedViewModelManager.kt delete mode 100644 java/dagger/hilt/android/compose/components/BUILD delete mode 100644 java/dagger/hilt/android/compose/components/ComposeComponent.kt delete mode 100644 java/dagger/hilt/android/compose/components/ComposeRetainedComponent.kt delete mode 100644 java/dagger/hilt/android/compose/internal/BUILD delete mode 100644 java/dagger/hilt/android/compose/internal/InternalComponentHostCreator.kt delete mode 100644 java/dagger/hilt/android/compose/internal/builders/BUILD delete mode 100644 java/dagger/hilt/android/compose/internal/builders/ComposeComponentBuilder.kt delete mode 100644 java/dagger/hilt/android/compose/internal/builders/ComposeRetainedComponentBuilder.kt delete mode 100644 java/dagger/hilt/android/compose/internal/qualifiers/BUILD delete mode 100644 java/dagger/hilt/android/compose/internal/qualifiers/HiltComposeInternal.kt delete mode 100644 java/dagger/hilt/android/compose/internal/testing/BUILD delete mode 100644 java/dagger/hilt/android/compose/internal/testing/CustomInfo.kt delete mode 100644 java/dagger/hilt/android/compose/internal/testing/LabeledNodes.kt delete mode 100644 java/dagger/hilt/android/compose/package-info.java delete mode 100644 java/dagger/hilt/android/compose/scopes/BUILD delete mode 100644 java/dagger/hilt/android/compose/scopes/ComposeRetainedScoped.kt delete mode 100644 java/dagger/hilt/android/compose/scopes/ComposeScoped.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostGenerator.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostMetadata.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessingStep.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessor.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composecomponenthost/KspComposeComponentHostProcessor.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedGenerator.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedMetadata.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessingStep.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessor.kt delete mode 100644 java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/KspComposeRetainedProvidedProcessor.kt delete mode 100644 javatests/dagger/hilt/android/compose/BUILD delete mode 100644 javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest.kt delete mode 100644 javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest_AndroidManifest.xml delete mode 100644 javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest.kt delete mode 100644 javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest_AndroidManifest.xml delete mode 100644 javatests/dagger/hilt/android/compose/RememberComponentHostTest.kt delete mode 100644 javatests/dagger/hilt/android/compose/RememberComponentHostTest_AndroidManifest.xml delete mode 100644 javatests/dagger/hilt/android/compose/res/values/ids.xml delete mode 100644 javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD delete mode 100644 javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessorTest.kt delete mode 100644 javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD delete mode 100644 javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessorTest.kt diff --git a/java/dagger/hilt/android/compose/BUILD b/java/dagger/hilt/android/compose/BUILD deleted file mode 100644 index f3ccf365665..00000000000 --- a/java/dagger/hilt/android/compose/BUILD +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Hilt Android classes for Jetpack Compose. - -load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library") - -package(default_visibility = ["//:src"]) - -java_library( - name = "package_info", - srcs = ["package-info.java"], -) - -kt_android_library( - name = "retained_view_model_manager", - srcs = ["RetainedViewModelManager.kt"], - visibility = ["//visibility:private"], - deps = [ - ":remember_component_host", - "//:dagger_with_compiler", - "//third_party/java/androidx/activity", - "//third_party/java/androidx/lifecycle", - ], -) - -kt_android_library( - name = "component_host_creator", - srcs = ["ComponentHostCreator.kt"], -) - -kt_android_library( - name = "compose_component_host", - srcs = ["ComposeComponentHost.kt"], - exported_plugins = [ - "//java/dagger/hilt/android/processor/internal/compose/composecomponenthost:processor", - ], - exports = [ - ":component_host_creator", - ":compose_component_host_extras", - ":retained_view_model_manager", - "//:dagger_with_compiler", - "//java/dagger/hilt:install_in", - "//java/dagger/hilt/android/components", - "//java/dagger/hilt/android/compose/components", - "//java/dagger/hilt/android/compose/internal", - "//java/dagger/hilt/android/compose/internal/builders", - "//java/dagger/hilt/codegen:originating_element", - "//java/dagger/hilt/internal:generated_entry_point", - ], - deps = [ - "//java/dagger/hilt:generates_root_input", - ], -) - -kt_android_library( - name = "compose_component_host_extras", - srcs = ["ComposeComponentHostExtras.kt"], - deps = ["//java/dagger/hilt/android/internal"], -) - -kt_android_library( - name = "compose_retained_lifecycle", - srcs = ["ComposeRetainedLifecycle.kt"], - deps = ["//java/dagger/hilt/android/lifecycle:retained_lifecycle"], -) - -kt_android_library( - name = "compose_retained_provided", - srcs = ["ComposeRetainedProvided.kt"], - exported_plugins = [ - "//java/dagger/hilt/android/processor/internal/compose/composeretainedprovided:processor", - ], - exports = [ - "//:dagger_with_compiler", - "//java/dagger/hilt:install_in", - "//java/dagger/hilt/android/compose/components", - "//java/dagger/hilt/android/compose/internal/qualifiers", - "//java/dagger/hilt/codegen:originating_element", - "//java/dagger/hilt/internal:generated_entry_point", - ], -) - -kt_android_library( - name = "remember_component_host", - srcs = ["RememberComponentHost.kt"], - deps = [ - ":component_host_creator", - ":compose_component_host_extras", - ":compose_retained_lifecycle", - ":compose_retained_provided", - "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", - "//java/dagger/hilt:install_in", - "//java/dagger/hilt/android/compose/components", - "//java/dagger/hilt/android/compose/internal", - "//java/dagger/hilt/android/compose/scopes", - "//java/dagger/hilt/android/internal", - "//java/dagger/hilt/android/internal/lifecycle", - "//third_party/java/androidx/compose/runtime", - "//third_party/java/androidx/compose/ui", - "@maven//:androidx_lifecycle_lifecycle_viewmodel", - "@maven//:androidx_lifecycle_lifecycle_viewmodel_compose", - ], -) diff --git a/java/dagger/hilt/android/compose/ComponentHostCreator.kt b/java/dagger/hilt/android/compose/ComponentHostCreator.kt deleted file mode 100644 index a6f9ec0ab2b..00000000000 --- a/java/dagger/hilt/android/compose/ComponentHostCreator.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -/** - * Provides a way to retrieve the given ComponentHost type from an appropriate component. - * - * This class should never be used on its own. It should always be used with [rememberComponentHost] - * to ensure that the lifecycle of the given host is correct. - * - * The annotation processor will generate appropriate classes to ensure that it's possible to inject - * `ComponentHostCreator`. - * - * @param HostT the type annotated with [ComposeComponentHost], which is used here to differentiate one - * ComponentHostCreator from another. - */ -interface ComponentHostCreator diff --git a/java/dagger/hilt/android/compose/ComposeComponentHost.kt b/java/dagger/hilt/android/compose/ComposeComponentHost.kt deleted file mode 100644 index f1df54e9e0e..00000000000 --- a/java/dagger/hilt/android/compose/ComposeComponentHost.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import dagger.hilt.GeneratesRootInput - -/** - * Annotation used to indicate that the annotated type hosts the Compose and ComposeRetained Dagger - * components. - * - * See the following example on how to use `@ComposeComponentHost`: - * ``` - * /** Hosts the MyScreen Composable. Hilt_MyHost is generated and must be the super type. */ - * @ComposeComponentHost - * class MyHost @Inject internal constructor(dependency: MyInjectedDependency) : Hilt_MyHost { - * - * @Composable - * fun MyScreen(modifier: Modifier = Modifier) { - * // Define MyScreen, using data from the injected dependency - * } - * } - * ``` - * - * `MyHost` is annotated with `@ComposeComponentHost`, so Hilt generates a - * `ComponentHostCreator` and provides a binding for it. `MyHost` is injected and used as - * follows: - * ``` - * @AndroidEntryPoint(ComponentActivity::class) - * class HomeActivity: Hilt_HomeActivity() { - * @Inject lateinit var myHostCreator: ComponentHostCreator - * - * override fun onCreate(bundle: Bundle?) { - * super.onCreate(bundle) - * setContent { - * // myHostCreator must be used with rememberComponentHost to have the correct lifecycle - * val myHost = rememberComponentHost(myHostCreator) - * myHost.MyScreen() - * } - * } - * } - * ``` - * - * As with other Hilt components, bindings can be scoped to either the ComposeComponent or - * ComposeRetainedComponent by using the appropriate scope annotation: - * [dagger.hilt.android.compose.scopes.ComposeScoped] for the ComposeComponent and - * [dagger.hilt.android.compose.scopes.ComposeRetainedScoped] for the ComposeRetainedComponent. - * Annotating a binding as such results in Hilt providing the same instance of that binding every - * time it's requested in the corresponding component. For example: - * ``` - * /** - * * Because ComposeScopedDependency is @ComposeScoped, - * * firstDependency.scopedDependency === secondDependency.scopedDependency. - * */ - * @ComposeComponentHost - * class MyHost @Inject internal constructor( - * private val firstDependency: FirstDependency, - * private val secondDependency: SecondDependency - * ) {...} - * - * class FirstDependency @Inject constructor(private val scopedDependency: ComposeScopedDependency) - * class SecondDependency @Inject constructor(private val scopedDependency: ComposeScopedDependency) - * - * @ComposeScoped - * class ComposeScopedDependency @Inject constructor() - * ``` - * - * The equivalent applies to `@ComposeRetainedScoped` annotated bindings, however additional - * machinery is needed. See the documentation on - * [dagger.hilt.android.compose.ComposeRetainedProvided] for details. - */ -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.BINARY) -@GeneratesRootInput -annotation class ComposeComponentHost diff --git a/java/dagger/hilt/android/compose/ComposeComponentHostExtras.kt b/java/dagger/hilt/android/compose/ComposeComponentHostExtras.kt deleted file mode 100644 index 3483bcc8d83..00000000000 --- a/java/dagger/hilt/android/compose/ComposeComponentHostExtras.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import dagger.hilt.android.internal.ThreadUtil - -/** - * Read only version of extras provided to a ComposeComponentHost. - * - * Use [buildComposeComponentHostExtras] to create an instance of ComposeComponentHostExtras. - * - * ComposeComponentHostExtras support a specific use case and should not replace alternate means of - * providing objects to a host: - * - Objects that are part of the Dagger graph should be injected directly where they're used. - * - Objects that aren't part of the Dagger graph can be provided through ComposeComponentHostExtras - * if they're used in enough places to benefit from making them globally accessible in the - * ComposeComponent and ComposeRetainedComponent. - * - Objects that are not used in enough places to warrant use of ComposeComponentHostExtras can be - * passed as parameters to `@Composable` functions. - */ -class ComposeComponentHostExtras internal constructor(private val keyToExtra: Map, Any>) { - - /** Returns the argument associated with the given key, if available. */ - @Suppress("UNCHECKED_CAST") // extras can only be inserted with a type that matches the key - fun get(key: Key): T? = keyToExtra[key] as T - - /** - * Represents the key for a given argument. - * - * Key subclasses require no implementation and should generally be implemented as a Kotlin - * object. The generic type parameter should be set as the type of the argument. Example key for a - * specific argument of type String: - * ```kotlin - * object MyStringKey: Key - * ``` - */ - interface Key - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ComposeComponentHostExtras) return false - return keyToExtra == other.keyToExtra - } - - override fun hashCode(): Int = keyToExtra.hashCode() -} - -/** - * Builds a new instance of [ComposeComponentHostExtras] by populating a - * [MutableComposeComponentHostExtras] instance using the given [builderAction]. - * - * Build and use the extras in a host with the following: - * ```kotlin - * @Composable - * fun MyContent() { - * val extras = buildComposeComponentHostExtras { - * put(MyStringKey, "someStringExtra") - * put(MyIntKey, 5) - * put(MyBooleanKey, true) - * } - * - * val host = rememberComponentHost(myHost, extras) - * } - * ``` - */ -fun buildComposeComponentHostExtras( - builderAction: MutableComposeComponentHostExtras.() -> Unit -): ComposeComponentHostExtras { - ThreadUtil.ensureMainThread() - return MutableComposeComponentHostExtras().apply { this.builderAction() }.build() -} - -/** - * The write-only version of extras provided to a ComposeComponentHost. - * - * For all [put] methods, calling the method multiple times with the same key will overwrite the - * stored value. For example: - * ```kotlin - * @Composable fun Content() { - * val extras = buildComposeComponentHostExtras { - * put(SomeStringKey, "hello") // "hello" is associated with SomeStringKey - * put(SomeStringKey, "world") // now "world" is associated with SomeStringKey - * } - * } - * ``` - */ -class MutableComposeComponentHostExtras internal constructor() { - private val keyToExtra: MutableMap, Any> = mutableMapOf() - - fun put(key: ComposeComponentHostExtras.Key, value: Int) { - keyToExtra[key] = value - } - - fun put(key: ComposeComponentHostExtras.Key, value: Long) { - keyToExtra[key] = value - } - - fun put(key: ComposeComponentHostExtras.Key, value: Float) { - keyToExtra[key] = value - } - - fun put(key: ComposeComponentHostExtras.Key, value: Double) { - keyToExtra[key] = value - } - - fun put(key: ComposeComponentHostExtras.Key, value: Boolean) { - keyToExtra[key] = value - } - - fun put(key: ComposeComponentHostExtras.Key, value: String) { - keyToExtra[key] = value - } - - // TODO: b/303256918 - Also support immutable data types and protos - - internal fun build() = ComposeComponentHostExtras(keyToExtra) -} diff --git a/java/dagger/hilt/android/compose/ComposeRetainedLifecycle.kt b/java/dagger/hilt/android/compose/ComposeRetainedLifecycle.kt deleted file mode 100644 index ad5e97ee838..00000000000 --- a/java/dagger/hilt/android/compose/ComposeRetainedLifecycle.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import dagger.hilt.android.lifecycle.RetainedLifecycle - -/** - * A [RetainedLifecycle] which follows the lifecycle of the - * [dagger.hilt.android.compose.components.ComposeRetainedComponent]. - */ -interface ComposeRetainedLifecycle : RetainedLifecycle { - - /** - * Listener which receives a callback when the - * [dagger.hilt.android.compose.components.ComposeRetainedComponent] will no longer be used and is - * destroyed. - */ - interface OnClearedListener : RetainedLifecycle.OnClearedListener -} diff --git a/java/dagger/hilt/android/compose/ComposeRetainedProvided.kt b/java/dagger/hilt/android/compose/ComposeRetainedProvided.kt deleted file mode 100644 index 648f1695796..00000000000 --- a/java/dagger/hilt/android/compose/ComposeRetainedProvided.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -/** - * Annotation used to indicate that the annotated binding can be injected from the - * [dagger.hilt.android.compose.components.ComposeComponent]. - * - * Classes can be annotated `@ComposeRetainedProvided`: - * ```kotlin - * @ComposeRetainedScoped - * @ComposeRetainedProvided - * class MyRetainedClass @Inject constructor(...) {...} - * ``` - * - * `@Provides` and `@Binds` methods can also be annotated `@ComposeRetainedProvided`: - * ```kotlin - * @Module - * @InstallIn(ComposeRetainedComponent.class) - * object MyRetainedClassModule { - * - * @Provides - * @ComposeRetainedScoped - * @ComposeRetainedProvided - * fun provideMyRetainedClass(...) { - * return MyRetainedClass(...) - * } - * } - * ``` - * - * Annotating a binding with `@ComposeRetainedProvided` will cause Hilt to generate code to provide - * it in the [dagger.hilt.android.compose.components.ComposeComponent]. Any other binding that is in - * the ComposeComponent can then inject and use the annotated type directly. - * - * For example: - * ```kotlin - * class MyClassInComposeComponent @Inject constructor(private val retainedDep: MyRetainedClass) { - * - * @Composable - * fun MyComposable() { - * retainedDep.doSomethingWithRetainedDep() - * } - * } - * ``` - * - * Similarly, since ComponentHosts are bindings in the ComposeComponent, `MyRetainedClass` can be - * directly injected into the class annotated as `@ComposeComponentHost`: - * ```kotlin - * @ComposeComponentHost - * class MyComposeComponentHost @Inject constructor(private val retainedDep: MyRetainedDep) {...} - * ``` - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.BINARY) -annotation class ComposeRetainedProvided diff --git a/java/dagger/hilt/android/compose/RememberComponentHost.kt b/java/dagger/hilt/android/compose/RememberComponentHost.kt deleted file mode 100644 index 87641235c8f..00000000000 --- a/java/dagger/hilt/android/compose/RememberComponentHost.kt +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import android.content.Context -import android.content.ContextWrapper -import android.os.Handler -import android.os.Looper -import android.view.View -import androidx.activity.ComponentActivity -import androidx.compose.runtime.Composable -import androidx.compose.runtime.RememberObserver -import androidx.compose.runtime.currentCompositeKeyHash -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import dagger.Module -import dagger.Provides -import dagger.hilt.EntryPoint -import dagger.hilt.EntryPoints -import dagger.hilt.InstallIn -import dagger.hilt.android.compose.components.ComposeRetainedComponent -import dagger.hilt.android.compose.internal.InternalComponentHostCreator -import dagger.hilt.android.compose.scopes.ComposeRetainedScoped -import dagger.hilt.android.internal.ThreadUtil -import dagger.hilt.android.internal.lifecycle.RetainedLifecycleImpl -import java.util.ArrayDeque -import java.util.Deque - -/** - * Uses the provided [ComponentHostCreator] to create an underlying host and remembers it in the - * composition. The host is remembered as long as [extras] is - * [equal][ComposeComponentHostExtras.equals] to its value at the previous composition; if [extras] - * changes, a new host will be created and remembered. - * - * The `ComponentHostCreator` must be generated by Hilt based on a `@ComposeComponentHost` annotated - * class. See [the annotation][ComposeComponentHost] for details. - * - * When using this function with [ComposeViews][androidx.compose.ui.platform.ComposeView] manually - * created either in XML or Kotlin, make sure to set the id of the ComposeView. Otherwise, two - * different ComposeViews may unintentionally share dependencies from the - * [ComposeRetainedComponent]. For example: - * ```xml - * # ids.xml - * - * - * - * - * - * ``` - * ```kotlin - * // MyActivity.kt - * override fun onCreate(savedInstanceState: Bundle?) { - * super.onCreate(savedInstanceState) - * setContentView( - * LinearLayout(this).apply { - * addView( - * ComposeView(this).apply { - * id = R.id.compose_view_id1 - * setContent { ... } - * } - * ) - * addView( - * ComposeView(this).apply { - * id = R.id.compose_view_id2 - * setContent { ... } - * } - * ) - * } - * ) - * } - * ``` - */ -@Composable -fun rememberComponentHost( - creator: ComponentHostCreator, - extras: ComposeComponentHostExtras? = null -): HostT { - require(creator is InternalComponentHostCreator) { - "$creator should be generated to implement InternalComponentHostCreator" - } - - val retainedComponent = rememberComposeRetainedComponent(creator, extras) - - // This double remembers the retainedComponent, but that's ok because remembering in - // rememberComposeRetainedComponent always has a lifetime that encompasses the component host. - return remember(extras) { creator.createComponentHost(extras, retainedComponent) } -} - -/** - * Uses the provided [InternalComponentHostCreator] to create the [ComposeRetainedComponent] and - * remembers it such that it is retained across configuration changes. The component is remembered - * as long as [extras] is equal to its value at the previous composition; if [extras] changes, a new - * component will be created and remembered and the previous one will be discarded. - */ -@Composable -private fun rememberComposeRetainedComponent( - creator: InternalComponentHostCreator<*>, - extras: ComposeComponentHostExtras? -): ComposeRetainedComponent { - val activity = LocalContext.current.findActivity() - val retainedViewModel = ViewModelProvider(activity)[RetainedViewModel::class.java] - val key = calculateComposeRetainedComponentKey(creator, extras) - - val holder = - remember(extras) { - object : RememberObserver { - val component = initializeComponent() - - private fun initializeComponent(): ComposeRetainedComponent { - val restored = retainedViewModel.restoreRetainedComponent(key) - if (restored == null) { - return creator.createRetainedComponent(extras) - } - - if (restored.extras() == extras) { - return restored - } - - restored.composeRetainedLifecycle().dispatchOnCleared() - return creator.createRetainedComponent(extras) - } - - override fun onForgotten() { - // If we are changing configuration, then the component should be saved to satisfy the - // "retained" part of ComposeRetainedComponent. Otherwise, either we're being removed from - // the composition and shouldn't save the retained component, or the whole composition is - // being destroyed in which case the ViewModel will be cleared as well. - if (activity.isChangingConfigurations) { - retainedViewModel.storeRetainedComponent(key, component) - } else { - component.composeRetainedLifecycle().dispatchOnCleared() - } - } - - /** - * No-op, onRemembered is used to run side effects after the RememberObserver is guaranteed - * to be successfully remembered in the composition. No side effects are needed here. - */ - override fun onRemembered() {} - - /** - * Used to release resources or undo side effects in the event of failures during - * composition. - * - * In practice, failures during composition mean exceptions being thrown, which will crash - * the app. It's not likely for the app to recover in this scenario, but we call - * dispatchOnCleared anyway to let clients cleanup any state created as a result of the - * host/component being initialized. - */ - override fun onAbandoned() { - component.composeRetainedLifecycle().dispatchOnCleared() - } - } - } - - return holder.component -} - -/** - * Calculates a key that is likely to be unique for the given inputs, which can be used to store the - * [ComposeRetainedComponent]. - * - * This function uses [currentCompositeKeyHash] to try calculating a unique hash for the current - * place in the composition. That alone is not sufficient as it's only based on the composition and - * doesn't include an identifier for the root ComposeView. To resolve this, we also try to include - * the id of the ComposeView hosting this composition. - * - * We can't assert that an id is set on the ComposeView because the standard - * [androidx.compose.ui.platform.setContent] extension function doesn't set an id on the ComposeView - * it creates. SetContent doesn't have a key interference issue though because there's only one - * ComposeView in the view hierarchy when its used. - * - * See the creation functions for [androidx.compose.ui.platform.DisposableSaveableStateRegistry], - * which has this same logic to create a unique DisposableSaveableStateRegistry per ComposeView. - */ -@Composable -private fun calculateComposeRetainedComponentKey( - creator: InternalComponentHostCreator<*>, - extras: ComposeComponentHostExtras? -): String { - // LocalView is configured as an AndroidComposeView whose parent is the ComposeView itself. The - // AndroidComposeView can't have an id, but the parent ComposeView might. - // TODO(b/284334800): LocalView.current.parent is usually a View, evaluate if that's ever not true - val parent = LocalView.current.parent as View - val composeViewKeyPart = parent.id - - val creatorKeyPart = creator.javaClass.name - - val extrasKeyPart = extras.hashCode() - - // Copied from rememberSaveable's MaxSupportedRadix. - val compositionKeyPart = currentCompositeKeyHash.toString(radix = 36) - - return "${composeViewKeyPart}_${creatorKeyPart}_${extrasKeyPart}_$compositionKeyPart" -} - -/** ViewModel used to retain ComposeRetainedComponents across configuration changes. */ -internal class RetainedViewModel : ViewModel(), DefaultLifecycleObserver { - private val mainThreadHandler by lazy { Handler(Looper.getMainLooper()) } - private val cleanUpUnusedComponentsRunnable = Runnable { clearAllComponents() } - private var needsCleanup = false - - private val keyToRetainedComponent = mutableMapOf>() - - /** - * Returns the most recently stored ComposeRetainedComponent for the given [key] if any, consuming - * it in the process. - */ - fun restoreRetainedComponent(key: String): ComposeRetainedComponent? { - ThreadUtil.ensureMainThread() - return keyToRetainedComponent[key]?.pollFirst() - } - - /** - * Stores the given [ComposeRetainedComponent] for the given [key]. - * - * In the event that there's already a ComposeRetainedComponent stored for the key, the new - * component will be stored in addition to the original. Later calls to [restoreRetainedComponent] - * for the same key will return the ComposeRetainedComponents in **LIFO order**. This is necessary - * and important because [rememberComposeRetainedComponent] uses a RememberObserver to store - * retained components and [RememberObserver.onForgotten] is called in the opposite order that - * objects are remembered. - */ - fun storeRetainedComponent(key: String, component: ComposeRetainedComponent) { - ThreadUtil.ensureMainThread() - keyToRetainedComponent.getOrPut(key) { ArrayDeque() }.addFirst(component) - needsCleanup = true - } - - /** - * Posts a runnable to the main thread to cleanup non-restored components. - * - * In normal scenarios, the onForgotten part of [rememberComposeRetainedComponent] will clean up - * components when they're removed from the composition. However, there's an edge case where the - * composition changes during a configuration change. When that happens, onForgotten isn't called - * so the component doesn't have a chance to be cleaned up. - * - * This workaround is used to handle the edge case. It relies on the fact that recomposition - * happens on the main thread and will complete before the posted runnable runs. After - * recomposition completes, any components left in [keyToRetainedComponent] are no longer part of - * the composition and should be cleaned up to avoid leaks. - * - * TODO: b/294901855 - Remove the workaround and use rememberRetained when it's available. - */ - override fun onResume(owner: LifecycleOwner) { - ThreadUtil.ensureMainThread() - owner.lifecycle.removeObserver(this) - - if (!needsCleanup) { - return - } - - needsCleanup = false - mainThreadHandler.post(cleanUpUnusedComponentsRunnable) - } - - override fun onCleared() { - super.onCleared() - mainThreadHandler.removeCallbacks(cleanUpUnusedComponentsRunnable) - clearAllComponents() - } - - private fun clearAllComponents() { - ThreadUtil.ensureMainThread() - for (component in keyToRetainedComponent.values.flatten()) { - component.composeRetainedLifecycle().dispatchOnCleared() - } - keyToRetainedComponent.clear() - } -} - -/** - * Attempts to find the closest [ComponentActivity] in the Context hierarchy. - * - * This method looks for ComponentActivity, as opposed to another subclass, because - * ComponentActivity is the very top of the class hierarchy that extends ViewModelStoreOwner. - * Looking for something lower, such as FragmentActivity, could miss valid contexts for a - * ComposeComponentHost. - * - * Note that this function recursively walks up the ContextWrapper hierarchy to find the - * ComponentActivity, if it exists. - * - * @throws IllegalStateException if no ComponentActivity can be found. - */ -private tailrec fun Context.findActivity(): ComponentActivity = - when (this) { - is ComponentActivity -> this - is ContextWrapper -> this.baseContext.findActivity() - else -> - throw IllegalStateException( - "ComposeComponentHosts must be used within the context of a ComponentActivity." - ) - } - -private fun ComposeRetainedComponent.composeRetainedLifecycle(): RetainedLifecycleImpl = - EntryPoints.get(this, ComposeRetainedComponentEntryPoint::class.java) - .getComposeRetainedLifecycle() as RetainedLifecycleImpl - -private fun ComposeRetainedComponent.extras(): ComposeComponentHostExtras? = - EntryPoints.get(this, ComposeRetainedComponentEntryPoint::class.java).getExtras() - -@EntryPoint -@InstallIn(ComposeRetainedComponent::class) -internal interface ComposeRetainedComponentEntryPoint { - fun getComposeRetainedLifecycle(): ComposeRetainedLifecycle - - fun getExtras(): ComposeComponentHostExtras? -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object LifecycleModule { - @Provides - @ComposeRetainedScoped - fun provideComposeRetainedLifecycle(): ComposeRetainedLifecycle = RetainedLifecycleImpl() -} diff --git a/java/dagger/hilt/android/compose/RetainedViewModelManager.kt b/java/dagger/hilt/android/compose/RetainedViewModelManager.kt deleted file mode 100644 index 87d924c0ffc..00000000000 --- a/java/dagger/hilt/android/compose/RetainedViewModelManager.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import android.app.Activity -import androidx.activity.ComponentActivity -import androidx.lifecycle.ViewModelProvider -import javax.inject.Inject - -/** Manages aspects of the RetainedViewModel that it can't handle internally. */ -class RetainedViewModelManager @Inject internal constructor(private val activity: Activity) { - - /** - * Adds the [RetainedViewModel] as a LifecycleObserver of the associated Activity. - * - * The RetainedViewModel needs to be added as an observer to prevent leaking - * ComposeRetainedComponents that aren't used in a composition after Activity recreation. - * [rememberComponentHost] is the natural place to add the ViewModel as an observer, but it's not - * guaranteed to be called - the composition could change such that rememberComponentHost is no - * longer called at all. Since the ComponentHostCreator should be injected regardless of the - * composition structure, the Hilt generated implementation calls this method in its constructor. - */ - fun addAsObserver() { - check(activity is ComponentActivity) { - "ComposeComponentHosts must be used within the context of a ComponentActivity." - } - - val retainedViewModel = ViewModelProvider(activity)[RetainedViewModel::class.java] - activity.lifecycle.addObserver(retainedViewModel) - } -} diff --git a/java/dagger/hilt/android/compose/components/BUILD b/java/dagger/hilt/android/compose/components/BUILD deleted file mode 100644 index 14de4fb4eaf..00000000000 --- a/java/dagger/hilt/android/compose/components/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Hilt Components for Jetpack Compose. - -load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library") - -package(default_visibility = ["//:src"]) - -kt_android_library( - name = "components", - srcs = [ - "ComposeComponent.kt", - "ComposeRetainedComponent.kt", - ], - deps = [ - "//java/dagger/hilt:define_component", - "//java/dagger/hilt/android/components", - "//java/dagger/hilt/android/compose/scopes", - ], -) diff --git a/java/dagger/hilt/android/compose/components/ComposeComponent.kt b/java/dagger/hilt/android/compose/components/ComposeComponent.kt deleted file mode 100644 index 941d2e6605b..00000000000 --- a/java/dagger/hilt/android/compose/components/ComposeComponent.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.components - -import dagger.hilt.DefineComponent -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.compose.scopes.ComposeScoped - -/** A Hilt component that has the lifetime of the Jetpack composition. */ -@ComposeScoped @DefineComponent(parent = ActivityComponent::class) interface ComposeComponent diff --git a/java/dagger/hilt/android/compose/components/ComposeRetainedComponent.kt b/java/dagger/hilt/android/compose/components/ComposeRetainedComponent.kt deleted file mode 100644 index 7a34a06b12b..00000000000 --- a/java/dagger/hilt/android/compose/components/ComposeRetainedComponent.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.components - -import dagger.hilt.DefineComponent -import dagger.hilt.android.components.ActivityRetainedComponent -import dagger.hilt.android.compose.scopes.ComposeRetainedScoped - -/** A Hilt component that has the lifetime of a configuration surviving Jetpack composition. */ -@ComposeRetainedScoped -@DefineComponent(parent = ActivityRetainedComponent::class) -interface ComposeRetainedComponent diff --git a/java/dagger/hilt/android/compose/internal/BUILD b/java/dagger/hilt/android/compose/internal/BUILD deleted file mode 100644 index 7ff4c0c5159..00000000000 --- a/java/dagger/hilt/android/compose/internal/BUILD +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Internal Hilt Android classes for Jetpack Compose. - -load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library") - -package(default_visibility = ["//:src"]) - -kt_android_library( - name = "internal", - srcs = ["InternalComponentHostCreator.kt"], - deps = [ - "//java/dagger/hilt/android/compose:component_host_creator", - "//java/dagger/hilt/android/compose:compose_component_host_extras", - "//java/dagger/hilt/android/compose/components", - ], -) diff --git a/java/dagger/hilt/android/compose/internal/InternalComponentHostCreator.kt b/java/dagger/hilt/android/compose/internal/InternalComponentHostCreator.kt deleted file mode 100644 index 7a9dc2bed43..00000000000 --- a/java/dagger/hilt/android/compose/internal/InternalComponentHostCreator.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.internal - -import dagger.hilt.android.compose.ComponentHostCreator -import dagger.hilt.android.compose.ComposeComponentHostExtras -import dagger.hilt.android.compose.components.ComposeRetainedComponent - -/** - * Parallel interface to [dagger.hilt.android.compose.ComponentHostCreator] that exists to prevent - * Hilt clients from improperly creating Component Hosts. - * - * ComponentHostCreator is a marker interface that is only used as a parameter to - * [dagger.hilt.android.compose.rememberComponentHost]. Within rememberComponentHost, we cast - * ComponentHostCreator to InternalComponentHostCreator to call createComponentHost. This is a safe - * cast because the generated ComponentHostCreator always implements both interfaces. - * - * Creating a Host without going through rememberComponentHost is wrong and will result in the host - * having an incorrect lifecycle. - * - * @param HostT the underlying type annotated with - * [dagger.hilt.android.compose.ComposeComponentHost]. - */ -interface InternalComponentHostCreator : ComponentHostCreator { - fun createComponentHost( - extras: ComposeComponentHostExtras?, - retainedComponent: ComposeRetainedComponent, - ): HostT - - fun createRetainedComponent(extras: ComposeComponentHostExtras?): ComposeRetainedComponent -} diff --git a/java/dagger/hilt/android/compose/internal/builders/BUILD b/java/dagger/hilt/android/compose/internal/builders/BUILD deleted file mode 100644 index 0957d2f7b8a..00000000000 --- a/java/dagger/hilt/android/compose/internal/builders/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Hilt Component Builders for Jetpack Compose. - -load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library") - -package(default_visibility = ["//:src"]) - -kt_android_library( - name = "builders", - srcs = [ - "ComposeComponentBuilder.kt", - "ComposeRetainedComponentBuilder.kt", - ], - deps = [ - "//:dagger_with_compiler", - "//java/dagger/hilt:define_component", - "//java/dagger/hilt/android/compose:compose_component_host_extras", - "//java/dagger/hilt/android/compose/components", - "//java/dagger/hilt/android/compose/internal/qualifiers", - ], -) diff --git a/java/dagger/hilt/android/compose/internal/builders/ComposeComponentBuilder.kt b/java/dagger/hilt/android/compose/internal/builders/ComposeComponentBuilder.kt deleted file mode 100644 index 84df18a7d7c..00000000000 --- a/java/dagger/hilt/android/compose/internal/builders/ComposeComponentBuilder.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.internal.builders - -import dagger.BindsInstance -import dagger.hilt.DefineComponent -import dagger.hilt.android.compose.ComposeComponentHostExtras -import dagger.hilt.android.compose.components.ComposeComponent -import dagger.hilt.android.compose.components.ComposeRetainedComponent -import dagger.hilt.android.compose.internal.qualifiers.HiltComposeInternal - -/** Builder used to create a [ComposeComponent]. */ -@DefineComponent.Builder -interface ComposeComponentBuilder { - fun bindComposeComponentHostExtras( - @BindsInstance extras: ComposeComponentHostExtras? - ): ComposeComponentBuilder - - fun bindComposeRetainedComponent( - @BindsInstance @HiltComposeInternal composeRetainedComponent: ComposeRetainedComponent - ): ComposeComponentBuilder - - fun build(): ComposeComponent -} diff --git a/java/dagger/hilt/android/compose/internal/builders/ComposeRetainedComponentBuilder.kt b/java/dagger/hilt/android/compose/internal/builders/ComposeRetainedComponentBuilder.kt deleted file mode 100644 index ebbdd9567fd..00000000000 --- a/java/dagger/hilt/android/compose/internal/builders/ComposeRetainedComponentBuilder.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.internal.builders - -import dagger.BindsInstance -import dagger.hilt.DefineComponent -import dagger.hilt.android.compose.ComposeComponentHostExtras -import dagger.hilt.android.compose.components.ComposeRetainedComponent - -/** Builder used to create a [ComposeRetainedComponent]. */ -@DefineComponent.Builder -interface ComposeRetainedComponentBuilder { - fun bindComposeComponentHostExtras( - @BindsInstance extras: ComposeComponentHostExtras? - ): ComposeRetainedComponentBuilder - - fun build(): ComposeRetainedComponent -} diff --git a/java/dagger/hilt/android/compose/internal/qualifiers/BUILD b/java/dagger/hilt/android/compose/internal/qualifiers/BUILD deleted file mode 100644 index c360683949a..00000000000 --- a/java/dagger/hilt/android/compose/internal/qualifiers/BUILD +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Internal qualifiers for Hilt bindings for Jetpack Compose - -load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library") - -kt_android_library( - name = "qualifiers", - srcs = ["HiltComposeInternal.kt"], - visibility = [ - "//java/dagger/hilt/android/compose:__pkg__", - "//java/dagger/hilt/android/compose/internal/builders:__pkg__", - ], - deps = [ - "//third_party/java/jsr330_inject", - ], -) diff --git a/java/dagger/hilt/android/compose/internal/qualifiers/HiltComposeInternal.kt b/java/dagger/hilt/android/compose/internal/qualifiers/HiltComposeInternal.kt deleted file mode 100644 index 6b2ea37c917..00000000000 --- a/java/dagger/hilt/android/compose/internal/qualifiers/HiltComposeInternal.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.internal.qualifiers - -import javax.inject.Qualifier - -/** Annotation for Hilt Compose internal bindings. */ -@Qualifier -@Target(AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.BINARY) -annotation class HiltComposeInternal diff --git a/java/dagger/hilt/android/compose/internal/testing/BUILD b/java/dagger/hilt/android/compose/internal/testing/BUILD deleted file mode 100644 index bd458ee0aca..00000000000 --- a/java/dagger/hilt/android/compose/internal/testing/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Testing helpers for Hilt Android classes for Jetpack Compose - -load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library") - -package(default_visibility = ["//:src"]) - -kt_android_library( - name = "testing", - testonly = True, - srcs = [ - "CustomInfo.kt", - "LabeledNodes.kt", - ], - deps = [ - "//third_party/java/androidx/compose/material", - "//third_party/java/androidx/compose/runtime", - "//third_party/java/androidx/compose/ui", - "//third_party/java/androidx/compose/ui/test", - ], -) diff --git a/java/dagger/hilt/android/compose/internal/testing/CustomInfo.kt b/java/dagger/hilt/android/compose/internal/testing/CustomInfo.kt deleted file mode 100644 index 053f2574017..00000000000 --- a/java/dagger/hilt/android/compose/internal/testing/CustomInfo.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.internal.testing - -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.SemanticsPropertyKey -import androidx.compose.ui.semantics.SemanticsPropertyReceiver -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.test.SemanticsMatcher -import androidx.compose.ui.test.SemanticsNodeInteraction -import androidx.compose.ui.test.assert -import com.google.errorprone.annotations.CanIgnoreReturnValue - -/** - * Adds custom info to the Modifier. - * - * Use [assertHasCustomInfo] to check if a given node has some customInfo. - */ -fun Modifier.customInfo(info: Any?): Modifier = semantics(properties = { customInfo = info }) - -/** - * Asserts that the current semantics node has the given [customInfo][Modifier.customInfo]. - * - * Use [Modifier.customInfo] to add custom info to a node. - */ -@CanIgnoreReturnValue -fun SemanticsNodeInteraction.assertHasCustomInfo(info: Any?): SemanticsNodeInteraction = - this.assert(hasCustomInfo(info)) - -/** - * Returns a [SemanticsMatcher] which checks if the associated node has some - * [customInfo][Modifier.customInfo]. - */ -private fun hasCustomInfo(info: Any?): SemanticsMatcher = - SemanticsMatcher.expectValue(customInfoKey, info) - -/** Key used to identify the customInfo in SemanticsProperties. */ -private val customInfoKey: SemanticsPropertyKey = - SemanticsPropertyKey(name = "CustomInfo", mergePolicy = { parentValue, _ -> parentValue }) - -/** Property which stores the customInfo itself. */ -private var SemanticsPropertyReceiver.customInfo by customInfoKey diff --git a/java/dagger/hilt/android/compose/internal/testing/LabeledNodes.kt b/java/dagger/hilt/android/compose/internal/testing/LabeledNodes.kt deleted file mode 100644 index 41df55f436b..00000000000 --- a/java/dagger/hilt/android/compose/internal/testing/LabeledNodes.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.internal.testing - -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.SemanticsPropertyKey -import androidx.compose.ui.semantics.SemanticsPropertyReceiver -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.test.SemanticsMatcher -import androidx.compose.ui.test.SemanticsNodeInteraction -import androidx.compose.ui.test.SemanticsNodeInteractionCollection -import androidx.compose.ui.test.SemanticsNodeInteractionsProvider - -/** - * A unique label for a node based on the provided "path" to it in the Compose tree. - * - * For example, for a Compose node called "Count", which is nested in a "CounterWidget" that's - * placed in "MyTestActivity", call `NodeLabel("MyTestActivity", "CounterWidget", "Count")`. - */ -class NodeLabel(vararg path: String) { - /** - * The string version of the provided path, created by placing " -> " between each part of the - * path and wrapping the whole string in brackets. - */ - private val label: String = path.joinToString(prefix = "[", separator = " -> ", postfix = "]") - - override fun toString(): String { - return "NodeLabel(label='$label')" - } -} - -/** - * Creates a Compose node with the given [nodeLabel]. - * - * Use [onNodeWithLabel] or [onAllNodesWithLabel] to find nodes with a given label in the - * composition. - */ -@Composable -fun LabeledNode(nodeLabel: NodeLabel, modifier: Modifier = Modifier) { - // The `text` parameter needs to be non-empty for clicks to work well. - Text(text = "LabeledNode", modifier.nodeLabel(nodeLabel)) -} - -/** - * Adds a [NodeLabel] to the Modifier. - * - * Use [onNodeWithLabel] or [onAllNodesWithLabel] to find nodes in the composition with a given - * label. - */ -fun Modifier.nodeLabel(label: NodeLabel): Modifier = semantics(properties = { nodeLabel = label }) - -/** Finds a semantics node with the given label. */ -fun SemanticsNodeInteractionsProvider.onNodeWithLabel(label: NodeLabel): SemanticsNodeInteraction = - this.onNode(hasNodeLabel(label)) - -/** Finds all semantics nodes with the given label. */ -fun SemanticsNodeInteractionsProvider.onAllNodesWithLabel( - label: NodeLabel -): SemanticsNodeInteractionCollection = this.onAllNodes(hasNodeLabel(label)) - -/** - * Returns a [SemanticsMatcher] which checks if the associated node has some - * [nodeLabel][Modifier.nodeLabel]. - */ -private fun hasNodeLabel(label: NodeLabel): SemanticsMatcher = - SemanticsMatcher.expectValue(nodeLabelKey, label) - -/** Key used to identify the NodeLabel in SemanticsProperties. */ -private val nodeLabelKey: SemanticsPropertyKey = - SemanticsPropertyKey(name = "NodeLabel", mergePolicy = { parentValue, _ -> parentValue }) - -/** Property which stores the NodeLabel itself. */ -private var SemanticsPropertyReceiver.nodeLabel by nodeLabelKey diff --git a/java/dagger/hilt/android/compose/package-info.java b/java/dagger/hilt/android/compose/package-info.java deleted file mode 100644 index dd070437909..00000000000 --- a/java/dagger/hilt/android/compose/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** This package contains Hilt's built-in classes for Compose. */ -package dagger.hilt.android.compose; diff --git a/java/dagger/hilt/android/compose/scopes/BUILD b/java/dagger/hilt/android/compose/scopes/BUILD deleted file mode 100644 index 8e25e931ef5..00000000000 --- a/java/dagger/hilt/android/compose/scopes/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Hilt scopes for Jetpack Compose. - -load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library") - -package(default_visibility = ["//:src"]) - -kt_android_library( - name = "scopes", - srcs = [ - "ComposeRetainedScoped.kt", - "ComposeScoped.kt", - ], - deps = ["//third_party/java/jsr330_inject"], -) diff --git a/java/dagger/hilt/android/compose/scopes/ComposeRetainedScoped.kt b/java/dagger/hilt/android/compose/scopes/ComposeRetainedScoped.kt deleted file mode 100644 index c9f826b9e5e..00000000000 --- a/java/dagger/hilt/android/compose/scopes/ComposeRetainedScoped.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.scopes - -import javax.inject.Scope - -/** - * Scope annotations for bindings that should exist for the life of a Jetpack composition, surviving - * configuration. - */ -@Scope @Retention(AnnotationRetention.BINARY) annotation class ComposeRetainedScoped diff --git a/java/dagger/hilt/android/compose/scopes/ComposeScoped.kt b/java/dagger/hilt/android/compose/scopes/ComposeScoped.kt deleted file mode 100644 index 3799add67dc..00000000000 --- a/java/dagger/hilt/android/compose/scopes/ComposeScoped.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose.scopes - -import javax.inject.Scope - -/** Scope annotation for bindings that should exist for the life of a Jetpack composition. */ -@Scope @Retention(AnnotationRetention.BINARY) annotation class ComposeScoped diff --git a/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java b/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java index 806377b9be7..5be3144a01f 100644 --- a/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java +++ b/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java @@ -25,9 +25,7 @@ import java.util.Set; /** Internal implementation. Do not use. */ -public final class RetainedLifecycleImpl - implements ActivityRetainedLifecycle, - ViewModelLifecycle { +public final class RetainedLifecycleImpl implements ActivityRetainedLifecycle, ViewModelLifecycle { private final Set listeners = new HashSet<>(); private boolean onClearedDispatched = false; diff --git a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD b/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD deleted file mode 100644 index e7ff9b5ed5c..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# A processor for @dagger.hilt.android.compose.ComposeComponentHost. - -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") - -package(default_visibility = ["//:src"]) - -java_plugin( - name = "processor", - generates_api = True, - processor_class = "dagger.hilt.android.processor.internal.compose.composecomponenthost.ComposeComponentHostProcessor", - deps = [":processor_lib"], -) - -kt_jvm_library( - name = "processor_lib", - srcs = [ - "ComposeComponentHostGenerator.kt", - "ComposeComponentHostMetadata.kt", - "ComposeComponentHostProcessingStep.kt", - "ComposeComponentHostProcessor.kt", - "KspComposeComponentHostProcessor.kt", - ], - deps = [ - "//java/dagger/hilt/android/processor/internal:android_classnames", - "//java/dagger/hilt/processor/internal:base_processor", - "//java/dagger/hilt/processor/internal:classnames", - "//java/dagger/hilt/processor/internal:processor_errors", - "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/codegen/xprocessing", - "//third_party/java/auto:service", - "//third_party/java/guava/collect", - "//third_party/java/incap", - "//third_party/java/javapoet", - "@maven//:com_google_devtools_ksp_symbol_processing_api", - ], -) diff --git a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostGenerator.kt b/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostGenerator.kt deleted file mode 100644 index e4dffc882fc..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostGenerator.kt +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composecomponenthost - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XTypeElement -import androidx.room.compiler.processing.addOriginatingElement -import com.squareup.javapoet.AnnotationSpec -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.JavaFile -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.ParameterSpec -import com.squareup.javapoet.TypeSpec -import dagger.hilt.android.processor.internal.AndroidClassNames -import dagger.hilt.processor.internal.ClassNames -import dagger.hilt.processor.internal.Processors -import javax.lang.model.element.Modifier - -/** Source generator for classes annotated as @ComposeComponentHost. */ -@OptIn(ExperimentalProcessingApi::class) -internal class ComposeComponentHostGenerator( - private val env: XProcessingEnv, - private val metadata: ComposeComponentHostMetadata -) { - - /** - * Generates three separate classes for a given $CLASS annotated as `@ComposeComponentHost`: - * Hilt_${CLASS}, Hilt_${CLASS}ComponentHostCreator, and ${CLASS}_HiltModule. - */ - fun generate() { - env.filer.write(componentHolderJavaFile()) - env.filer.write(componentHostEntryPointJavaFile()) - env.filer.write(componentHostCreatorJavaFile()) - env.filer.write(moduleJavaFile()) - } - - /** - * Creates a JavaFile representing the generated Hilt_${CLASS}. - * - * For a class "SpecificHost", the JavaFile contains the following: - * ```java - * @Generated("ComposeComponentHostGenerator") - * class Hilt_SpecificHost {} - * ``` - */ - private fun componentHolderJavaFile(): JavaFile { - val typeSpec = - TypeSpec.classBuilder(metadata.componentHolderClassName) - .addGeneratedCodeAnnotations(metadata.element, env) - .addModifiers(*metadata.componentHolderModifiers()) - .build() - return JavaFile.builder(metadata.componentHolderClassName.packageName(), typeSpec).build() - } - - /** - * Creates a JavaFile representing the generated Hilt_${CLASS}EntryPoint. - * - * For a class "SpecificHost", the JavaFile contains the following: - * ```java - * @OriginatingElement(topLevelClass = SpecificHost.class) - * @Generated("ComposeComponentHostGenerator") - * @GeneratedEntryPoint - * @InstallIn(ComposeComponent.class) - * interface Hilt_SpecificHostEntryPoint { - * SpecificHost getSpecificHost(); - * } - * ``` - */ - private fun componentHostEntryPointJavaFile(): JavaFile { - val typeSpec = - TypeSpec.interfaceBuilder(metadata.hostEntryPointClassName) - .addGeneratedCodeAnnotations(metadata.element, env) - .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.element)) - .addAnnotation(ClassNames.GENERATED_ENTRY_POINT) - .addAnnotation( - AnnotationSpec.builder(ClassNames.INSTALL_IN) - .addMember("value", "\$T.class", COMPOSE_COMPONENT_CLASS) - .build() - ) - .addMethod( - MethodSpec.methodBuilder("get" + metadata.elementClassName.simpleName()) - .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) - .returns(metadata.elementClassName) - .build() - ) - .build() - - return JavaFile.builder(metadata.hostEntryPointClassName.packageName(), typeSpec).build() - } - - /** - * Creates a JavaFile representing the generated Hilt_${CLASS}ComponentHostCreator. - * - * For a class "SpecificHost", the JavaFile contains the following: - * ```java - * @Generated("ComposeComponentHostGenerator") - * final class Hilt_SpecificHostComponentHostCreator - * implements ComponentHostCreator, InternalComponentHostCreator { - * private final ComposeComponentBuilder composeComponentBuilder; - * private final ComposeRetainedComponentBuilder composeRetainedComponentBuilder; - * - * @Inject - * Hilt_SpecificHostComponentHostCreator( - * ComposeComponentBuilder componentBuilder, - * ComposeRetainedComponentBuilder composeRetainedComponentBuilder, - * RetainedViewModelManager retainedViewModelManager) { - * this.composeComponentBuilder = composeComponentBuilder; - * this.composeRetainedComponentBuilder = composeRetainedComponentBuilder; - * retainedViewModelManager.addAsObserver() - * } - * - * @Override - * public SpecificHost createComponentHost( - * @Nullable ComposeComponentHostExtras extras - * ComposeRetainedComponent composeRetainedComponent) { - * ComposeComponent composeComponent = - * composeComponentBuilder - * .bindComposeComponentHostExtras(extras) - * .bindComposeRetainedComponent(composeRetainedComponent) - * .build(); - * return ((Hilt_SpecificHostEntryPoint) composeComponent).getSpecificHost(); - * } - * - * @Override - * public ComposeRetainedComponent createRetainedComponent( - * @Nullable ComposeComponentHostExtras extras) { - * return composeRetainedComponentBuilder - * .bindComposeComponentHostExtras(extras) - * .build(); - * } - * } - * ``` - */ - private fun componentHostCreatorJavaFile(): JavaFile { - val composeComponentBuilder = - ParameterSpec.builder(COMPOSE_COMPONENT_BUILDER_CLASS, "composeComponentBuilder").build() - val composeRetainedComponentBuilder = - ParameterSpec.builder( - COMPOSE_RETAINED_COMPONENT_BUILDER_CLASS, - "composeRetainedComponentBuilder" - ) - .build() - - val typeSpec = - TypeSpec.classBuilder(metadata.creatorClassName) - .addGeneratedCodeAnnotations(metadata.element, env) - .addModifiers(Modifier.FINAL) - .addSuperinterface(metadata.componentHostCreatorInterfaceTypeName) - .addSuperinterface(metadata.internalComponentHostCreatorInterfaceTypeName) - .addField( - composeComponentBuilder.type, - composeComponentBuilder.name, - Modifier.PRIVATE, - Modifier.FINAL - ) - .addField( - composeRetainedComponentBuilder.type, - composeRetainedComponentBuilder.name, - Modifier.PRIVATE, - Modifier.FINAL - ) - .addMethod( - componentHostConstructor(composeComponentBuilder, composeRetainedComponentBuilder) - ) - .addMethod(createComponentHost(composeComponentBuilder.name)) - .addMethod(createRetainedComponent(composeRetainedComponentBuilder.name)) - .build() - - return JavaFile.builder(metadata.creatorClassName.packageName(), typeSpec).build() - } - - /** - * ```java - * @Inject - * Hilt_SpecificHostComponentHostCreator( - * ComposeComponentBuilder componentBuilder, - * ComposeRetainedComponentBuilder composeRetainedComponentBuilder, - * RetainedViewModelManager retainedViewModelManager) { - * this.composeComponentBuilder = composeComponentBuilder; - * this.composeRetainedComponentBuilder = composeRetainedComponentBuilder; - * retainedViewModelManager.addAsObserver() - * } - * ``` - */ - private fun componentHostConstructor( - composeComponentBuilderParam: ParameterSpec, - composeRetainedComponentBuilderParam: ParameterSpec, - ): MethodSpec { - val retainedViewModelManagerParam = - ParameterSpec.builder(RETAINED_VIEW_MODEL_MANAGER_CLASS, "retainedViewModelManager").build() - - return MethodSpec.constructorBuilder() - .addAnnotation(ClassNames.INJECT) - .addParameter(composeComponentBuilderParam) - .addParameter(composeRetainedComponentBuilderParam) - .addParameter(retainedViewModelManagerParam) - .addStatement( - "this.\$L = \$L", - composeComponentBuilderParam.name, - composeComponentBuilderParam.name - ) - .addStatement( - "this.\$L = \$L", - composeRetainedComponentBuilderParam.name, - composeRetainedComponentBuilderParam.name - ) - .addStatement("\$L.addAsObserver()", retainedViewModelManagerParam.name) - .build() - } - - /** - * ```java - * @Override - * public SpecificHost createComponentHost( - * @Nullable ComposeComponentHostExtras extras - * ComposeRetainedComponent composeRetainedComponent) { - * ComposeComponent composeComponent = - * composeComponentBuilder - * .bindComposeComponentHostExtras(extras) - * .bindComposeRetainedComponent(composeRetainedComponent) - * .build(); - * return ((Hilt_SpecificEntryPoint) composeComponent).getSpecificHost() - * } - * ``` - */ - private fun createComponentHost(composeComponentBuilderName: String): MethodSpec { - val composeRetainedComponentParam = - ParameterSpec.builder(COMPOSE_RETAINED_COMPONENT_CLASS, "composeRetainedComponent").build() - - val composeComponentHostExtrasParam = - ParameterSpec.builder(COMPOSE_COMPONENT_HOST_EXTRAS, "extras") - .addAnnotation(AndroidClassNames.NULLABLE) - .build() - - return MethodSpec.methodBuilder("createComponentHost") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC) - .returns(metadata.element.type.typeName) - .addParameter(composeComponentHostExtrasParam) - .addParameter(composeRetainedComponentParam) - .addStatement( - "\$T composeComponent = \$L" + - ".bindComposeComponentHostExtras(\$L)" + - ".bindComposeRetainedComponent(\$L)" + - ".build()", - COMPOSE_COMPONENT_CLASS, - composeComponentBuilderName, - composeComponentHostExtrasParam.name, - composeRetainedComponentParam.name, - ) - .addStatement( - "return ((\$T) composeComponent).get\$L()", - metadata.hostEntryPointClassName, - metadata.elementClassName.simpleName() - ) - .build() - } - - /** - * ```java - * @Override - * public ComposeRetainedComponent createRetainedComponent( - * @Nullable ComposeComponentHostExtras extras) { - * return composeRetainedComponentBuilder - * .bindComposeComponentHostExtras(extras) - * .build(); - * } - * ``` - */ - private fun createRetainedComponent(composeRetainedComponentBuilderName: String): MethodSpec { - val composeComponentHostExtrasParam = - ParameterSpec.builder(COMPOSE_COMPONENT_HOST_EXTRAS, "extras") - .addAnnotation(AndroidClassNames.NULLABLE) - .build() - - return MethodSpec.methodBuilder("createRetainedComponent") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC) - .returns(COMPOSE_RETAINED_COMPONENT_CLASS) - .addParameter(composeComponentHostExtrasParam) - .addStatement( - "return \$L.bindComposeComponentHostExtras(\$L).build()", - composeRetainedComponentBuilderName, - composeComponentHostExtrasParam.name - ) - .build() - } - - /** - * Creates a JavaFile representing the generated Hilt_${CLASS}Module. - * - * For a class "SpecificHost", the JavaFile contains the following: - * ```java - * @Generated("ComposeComponentHostGenerator") - * @OriginatingElement(topLevelClass = SpecificHost.class) - * @Module - * @InstallIn(ActivityComponent.class) - * abstract class Hilt_SpecificHostCreatorModule { - * @Binds - * abstract ComponentHostCreator bindComponentHostCreator( - * Hilt_SpecificHostComponentHostCreator impl); - * } - * ``` - */ - private fun moduleJavaFile(): JavaFile { - val typeSpec = - TypeSpec.classBuilder(metadata.moduleClassName) - .addGeneratedCodeAnnotations(metadata.element, env) - .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.element)) - .addAnnotation(ClassNames.MODULE) - .addAnnotation( - AnnotationSpec.builder(ClassNames.INSTALL_IN) - .addMember("value", "\$T.class", AndroidClassNames.ACTIVITY_COMPONENT) - .build() - ) - .addModifiers(Modifier.ABSTRACT) - .addMethod( - MethodSpec.methodBuilder("bindComponentHostCreator") - .addAnnotation(ClassNames.BINDS) - .addModifiers(Modifier.ABSTRACT) - .returns(metadata.componentHostCreatorInterfaceTypeName) - .addParameter(ParameterSpec.builder(metadata.creatorClassName, "impl").build()) - .build() - ) - .build() - - return JavaFile.builder(metadata.moduleClassName.packageName(), typeSpec).build() - } - - companion object { - /** - * ClassName for [dagger.hilt.android.compose.components.ComposeComponent]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSE_COMPONENT_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose.components", "ComposeComponent") - - /** - * ClassName for [dagger.hilt.android.compose.internal.builders.ComposeComponentBuilder]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSE_COMPONENT_BUILDER_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose.internal.builders", "ComposeComponentBuilder") - - /** - * ClassName for [dagger.hilt.android.compose.RetainedViewModelManager]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val RETAINED_VIEW_MODEL_MANAGER_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose", "RetainedViewModelManager") - - /** - * ClassName for [dagger.hilt.android.compose.ComposeComponentHostExtras]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSE_COMPONENT_HOST_EXTRAS: ClassName = - ClassName.get("dagger.hilt.android.compose", "ComposeComponentHostExtras") - - /** - * ClassName for [dagger.hilt.android.compose.components.ComposeRetainedComponent]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSE_RETAINED_COMPONENT_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose.components", "ComposeRetainedComponent") - - /** - * ClassName for - * [dagger.hilt.android.compose.internal.builders.ComposeRetainedComponentBuilder]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSE_RETAINED_COMPONENT_BUILDER_CLASS: ClassName = - ClassName.get( - "dagger.hilt.android.compose.internal.builders", - "ComposeRetainedComponentBuilder" - ) - } -} - -/** - * Adds default required code generation annotations to the TypeSpec.Builder. - * - * For a class "SpecificHost", the Type will be annotated as follows: - * ```java - * @Generated("ComposeComponentHostGenerator") - * ``` - */ -@OptIn(ExperimentalProcessingApi::class) -private fun TypeSpec.Builder.addGeneratedCodeAnnotations( - originatingElement: XTypeElement, - env: XProcessingEnv -): TypeSpec.Builder { - this.addOriginatingElement(originatingElement) - Processors.addGeneratedAnnotation(this, env, ComposeComponentHostGenerator::class.java) - return this -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostMetadata.kt b/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostMetadata.kt deleted file mode 100644 index f41409b0410..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostMetadata.kt +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composecomponenthost - -import androidx.room.compiler.codegen.toJavaPoet -import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XTypeElement -import androidx.room.compiler.processing.compat.XConverters.getProcessingEnv -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import dagger.hilt.processor.internal.ClassNames -import dagger.hilt.processor.internal.ProcessorErrors -import dagger.hilt.processor.internal.Processors -import javax.lang.model.element.Modifier - -/** - * Validates information about a class annotated with @ComposeComponentHost and provides metadata - * about it for code generation. - */ -internal class ComposeComponentHostMetadata private constructor(val element: XTypeElement) { - - /** JavaPoet ClassName for ${CLASS}. */ - val elementClassName: ClassName = element.asClassName().toJavaPoet() - - /** - * The class name of the generated class which holds onto the ComposeComponent and - * ComposeRetainedComponent. Has the form: Hilt_${CLASS}. - */ - val componentHolderClassName: ClassName = componentHolderClassName(element) - - /** - * The class name of the generated EntryPoint providing ${CLASS}. Has the form: - * Hilt_${CLASS}EntryPoint. - */ - val hostEntryPointClassName: ClassName = Processors.append(componentHolderClassName, "EntryPoint") - - /** - * The class name of the generated class which implements ComponentHostCreator<${CLASS}>. Has the - * form: Hilt_${CLASS}ComponentHostCreator. - */ - val creatorClassName: ClassName = - Processors.append(componentHolderClassName, "ComponentHostCreator") - - /** [TypeName] for ComponentHostCreator<${CLASS}>. */ - val componentHostCreatorInterfaceTypeName: TypeName = - ParameterizedTypeName.get(COMPONENT_HOST_CREATOR_INTERFACE_CLASS, element.type.typeName) - - /** [TypeName] for InternalComponentHostCreator<${CLASS}>. */ - val internalComponentHostCreatorInterfaceTypeName: TypeName = - ParameterizedTypeName.get( - INTERNAL_COMPONENT_HOST_CREATOR_INTERFACE_CLASS, - element.type.typeName - ) - - /** - * The class name of the generated Dagger module which provides appropriate bindings for the - * generated classes. Has the form: Hilt_${CLASS}ComponentHostCreatorModule. - */ - val moduleClassName: ClassName = Processors.append(creatorClassName, "Module") - - /** - * Modifiers that should be applied to the generated component holder class. - * - *

Note that the generated class must have public visibility if used by a - * public @ComposeComponentHost-annotated kotlin class. See: - * https://discuss.kotlinlang.org/t/why-does-kotlin-prohibit-exposing-restricted-visibility-types/7047 - */ - fun componentHolderModifiers(): Array { - // Note XElement#isPublic() refers to the jvm visibility. Since "internal" visibility is - // represented as public in the jvm, we have to check XElement#isInternal() explicitly. - return if ( - element.isFromKotlin() && element.isPublic() && !element.isInternal() - ) { - arrayOf(Modifier.PUBLIC) - } else { - arrayOf() - } - } - - companion object { - @ExperimentalProcessingApi - internal fun create(typeElement: XTypeElement): ComposeComponentHostMetadata { - ProcessorErrors.checkState( - typeElement.getConstructors().any { it.hasAnnotation(ClassNames.INJECT) }, - "@ComposeComponentHost annotated classes should have an @Inject constructor." - ) - - // TODO(b/279185507): Revisit if we should have this requirement. - ProcessorErrors.checkState( - typeElement.getAllMethods().any { it.hasAnnotation(COMPOSABLE_ANNOTATION_CLASS) }, - "@ComposeComponentHost annotated classes should have at least one @Composable function. " + - "Use a regular class with an @Inject constructor for classes without composables." - ) - - val superTypeName = - typeElement.superClass?.typeElement?.asClassName()?.toJavaPoet()?.simpleName() - val expectedSuperTypeName = componentHolderClassName(typeElement).simpleName() - // TODO(b/288210593): Add this check back to KSP once this bug is fixed. - if (typeElement.getProcessingEnv().backend == XProcessingEnv.Backend.JAVAC) { - ProcessorErrors.checkState( - expectedSuperTypeName.contentEquals(superTypeName), - typeElement, - "@ComposeComponentHost annotated class expected to extend %s. Found: %s", - expectedSuperTypeName, - superTypeName - ) - } - - return ComposeComponentHostMetadata(typeElement) - } - - private fun componentHolderClassName(typeElement: XTypeElement) = - Processors.prepend(Processors.getEnclosedClassName(typeElement), "Hilt_") - - /** - * [ClassName] for ComponentHostCreator. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPONENT_HOST_CREATOR_INTERFACE_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose", "ComponentHostCreator") - - /** - * [ClassName] for InternalComponentHostCreator. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val INTERNAL_COMPONENT_HOST_CREATOR_INTERFACE_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose.internal", "InternalComponentHostCreator") - - /** - * [ClassName] for @Composable. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSABLE_ANNOTATION_CLASS: ClassName = - ClassName.get("androidx.compose.runtime", "Composable") - } -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessingStep.kt b/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessingStep.kt deleted file mode 100644 index 2458282b60c..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessingStep.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composecomponenthost - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XProcessingEnv -import com.google.common.collect.ImmutableSet -import com.squareup.javapoet.ClassName -import dagger.hilt.processor.internal.BaseProcessingStep -import dagger.internal.codegen.xprocessing.XElements - -/** Annotation processing step for [dagger.hilt.android.compose.ComposeComponentHost]. */ -@OptIn(ExperimentalProcessingApi::class) -class ComposeComponentHostProcessingStep(env: XProcessingEnv) : BaseProcessingStep(env) { - override fun annotationClassNames() = ImmutableSet.of(composeComponentHostClass) - - override fun processEach(annotation: ClassName, element: XElement) { - val metadata = ComposeComponentHostMetadata.create(XElements.asTypeElement(element)) - ComposeComponentHostGenerator(processingEnv(), metadata).generate() - } - - companion object { - /** - * ClassName for [dagger.hilt.android.compose.ComposeComponentHost]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val composeComponentHostClass: ClassName = - ClassName.get("dagger.hilt.android.compose", "ComposeComponentHost") - } -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessor.kt b/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessor.kt deleted file mode 100644 index 7679b12c3fb..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessor.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composecomponenthost - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import com.google.auto.service.AutoService -import dagger.hilt.processor.internal.BaseProcessingStep -import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor -import javax.annotation.processing.Processor -import net.ltgt.gradle.incap.IncrementalAnnotationProcessor -import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType - -/** Annotation processor for [dagger.hilt.android.compose.ComposeComponentHost]. */ -@AutoService(Processor::class) -@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) -@OptIn(ExperimentalProcessingApi::class) -class ComposeComponentHostProcessor : JavacBaseProcessingStepProcessor() { - override fun processingStep(): BaseProcessingStep { - return ComposeComponentHostProcessingStep(xProcessingEnv) - } -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/KspComposeComponentHostProcessor.kt b/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/KspComposeComponentHostProcessor.kt deleted file mode 100644 index af1a65af63b..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composecomponenthost/KspComposeComponentHostProcessor.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composecomponenthost - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import com.google.auto.service.AutoService -import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.processing.SymbolProcessorProvider -import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor - -/** Annotation processor for [dagger.hilt.android.compose.ComposeComponentHost]. */ -class KspComposeComponentHostProcessor( - environment: SymbolProcessorEnvironment, -) : KspBaseProcessingStepProcessor(environment) { - - @ExperimentalProcessingApi - override protected fun processingStep() = ComposeComponentHostProcessingStep(xProcessingEnv) - - /** Provides the {@link KspComposeComponentHostProcessor}. */ - @AutoService(SymbolProcessorProvider::class) - class Provider : SymbolProcessorProvider { - override fun create(environment: SymbolProcessorEnvironment) = - KspComposeComponentHostProcessor(environment) - } -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD b/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD deleted file mode 100644 index e6ed31cc32b..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# A processor for @dagger.hilt.android.compose.ComposeRetainedProvided. - -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") - -package(default_visibility = ["//:src"]) - -java_plugin( - name = "processor", - generates_api = True, - processor_class = "dagger.hilt.android.processor.internal.compose.composeretainedprovided.ComposeRetainedProvidedProcessor", - deps = [":processor_lib"], -) - -kt_jvm_library( - name = "processor_lib", - srcs = [ - "ComposeRetainedProvidedGenerator.kt", - "ComposeRetainedProvidedMetadata.kt", - "ComposeRetainedProvidedProcessingStep.kt", - "ComposeRetainedProvidedProcessor.kt", - "KspComposeRetainedProvidedProcessor.kt", - ], - deps = [ - "//java/dagger/hilt/processor/internal:base_processor", - "//java/dagger/hilt/processor/internal:classnames", - "//java/dagger/hilt/processor/internal:components", - "//java/dagger/hilt/processor/internal:processor_errors", - "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/codegen/xprocessing", - "//third_party/java/auto:service", - "//third_party/java/guava/collect", - "//third_party/java/incap", - "//third_party/java/javapoet", - "@maven//:com_google_devtools_ksp_symbol_processing_api", - ], -) diff --git a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedGenerator.kt b/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedGenerator.kt deleted file mode 100644 index 8bd4d038788..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedGenerator.kt +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composeretainedprovided - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.XAnnotation -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XTypeElement -import androidx.room.compiler.processing.addOriginatingElement -import com.google.common.collect.ImmutableSet -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.JavaFile -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.ParameterSpec -import com.squareup.javapoet.TypeSpec -import dagger.hilt.processor.internal.ClassNames -import dagger.hilt.processor.internal.Components -import dagger.hilt.processor.internal.Processors -import dagger.internal.codegen.xprocessing.XAnnotations -import javax.lang.model.element.Modifier - -/** - * Source generator for classes annotated as [dagger.hilt.android.compose.ComposeRetainedProvided]. - */ -@OptIn(ExperimentalProcessingApi::class) -internal class ComposeRetainedProvidedGenerator -constructor( - private val env: XProcessingEnv, - private val metadata: ComposeRetainedProvidedMetadata -) { - - /** Generates needed files for a given type marked as `@ComposeRetainedProvided`. */ - fun generate() { - env.filer.write(entryPointJavaFile()) - metadata.providedTypes - .map { providedType -> accessorJavaFile(providedType) } - .forEach { javaFile -> env.filer.write(javaFile) } - env.filer.write(moduleJavaFile()) - } - - /** - * Creates a JavaFile representing the generated Hilt_${ProvidedType}ComposeRetainedEntryPoint. - * - * For a binding of "Foo", the JavaFile contains the following: - * ```java - * @Generated("ComposeRetainedProvidedGenerator") - * @OriginatingElement(topLevelClass = Foo.class) - * @GeneratedEntryPoint - * @InstallIn(ComposeRetainedComponent.class) - * interface Hilt_FooComposeRetainedEntryPoint { - * @OptionalQualifier - * Foo get_Foo(); - * } - * ``` - * - * If a module has multiple methods annotated as `@ComposeRetainedProvided`, one EntryPoint is - * generated with getter methods for each method. - */ - private fun entryPointJavaFile(): JavaFile { - val typeSpec = - TypeSpec.interfaceBuilder(metadata.entryPointClassName) - .addGeneratedCodeAnnotations(metadata.enclosingElement, env) - .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.enclosingElement)) - .addAnnotation(ClassNames.GENERATED_ENTRY_POINT) - .addAnnotation( - Components.getInstallInAnnotationSpec(ImmutableSet.of(COMPOSE_RETAINED_COMPONENT_CLASS)) - ) - - for (providedType in metadata.providedTypes) { - typeSpec.addMethod(getEntryPointMethodFor(providedType)) - } - - return JavaFile.builder(metadata.entryPointClassName.packageName(), typeSpec.build()).build() - } - - /** - * ```java - * /* optional */ @Nullable - * @OptionalQualifier - * Foo get_Foo(); - * ``` - */ - private fun getEntryPointMethodFor(providedType: ProvidedType): MethodSpec { - return MethodSpec.methodBuilder(metadata.entryPointGetterMethodNameFor(providedType)) - .addOptionalQualifier(providedType.qualifier) - .addAnnotations(providedType.nullableAnnotations.map { XAnnotations.getAnnotationSpec(it) }) - .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) - .returns(providedType.typeName) - .build() - } - - /** - * Creates a JavaFile representing the generated Hilt_${CLASS}Accessor. - * - * For a binding of "Foo", the JavaFile contains the following: - * ```java - * @Generated("ComposeRetainedProvidedGenerator") - * final class Hilt_FooAccessor { - * private final ComposeRetainedComponent currentComposeRetainedComponent; - * - * @Inject - * Hilt_FooAccessor( - * @HiltComposeInternal ComposeRetainedComponent currentComposeRetainedComponent) { - * this.currentComposeRetainedComponent = currentComposeRetainedComponent; - * } - * - * Foo get() { - * return ((Hilt_FooComposeRetainedEntryPoint) currentComposeRetainedComponent).get_Foo(); - * } - * } - * ``` - * - * If a module has multiple methods annotated as `@ComposeRetainedProvided`, one Accessor class is - * generated for each method. - */ - private fun accessorJavaFile(providedType: ProvidedType): JavaFile { - val typeSpec = - TypeSpec.classBuilder(metadata.accessorClassNameOf(providedType)) - .addGeneratedCodeAnnotations(metadata.enclosingElement, env) - .addModifiers(Modifier.FINAL) - .addField( - CURRENT_COMPOSE_RETAINED_COMPONENT_PARAM.type, - CURRENT_COMPOSE_RETAINED_COMPONENT_PARAM.name, - Modifier.PRIVATE, - Modifier.FINAL - ) - .addMethod(accessorConstructor()) - .addMethod(accessorGetMethod(providedType)) - .build() - - return JavaFile.builder(metadata.accessorClassNameOf(providedType).packageName(), typeSpec) - .build() - } - - /** - * ```java - * @Inject - * Hilt_FooAccessor( - * @HiltComposeInternal ComposeRetainedComponent currentComposeRetainedComponent) { - * this.currentComposeRetainedComponent = currentComposeRetainedComponent; - * } - * ``` - */ - private fun accessorConstructor(): MethodSpec { - return MethodSpec.constructorBuilder() - .addAnnotation(ClassNames.INJECT) - .addParameter(CURRENT_COMPOSE_RETAINED_COMPONENT_PARAM) - .addStatement("this.\$1L = \$1L", CURRENT_COMPOSE_RETAINED_COMPONENT_PARAM.name) - .build() - } - - /** - * ```java - * Foo get() { - * return ((Hilt_FooComposeRetainedEntryPoint) currentComposeRetainedComponent).get_Foo(); - * } - * ``` - */ - private fun accessorGetMethod(providedType: ProvidedType): MethodSpec { - return MethodSpec.methodBuilder("get") - .returns(providedType.typeName) - .addStatement( - "return ((\$T) \$L).\$L()", - metadata.entryPointClassName, - CURRENT_COMPOSE_RETAINED_COMPONENT_PARAM.name, - metadata.entryPointGetterMethodNameFor(providedType), - ) - .build() - } - - /** - * Creates a JavaFile representing the generated Hilt_${CLASS}Module. - * - * For a binding of "Foo", the JavaFile contains the following: - * ```java - * @Generated("ComposeRetainedProvidedGenerator") - * @OriginatingElement(topLevelClass = Foo.class) - * @Module - * @InstallIn(ComposeComponent.class) - * abstract class Hilt_FooModule { - * @OptionalQualifier - * @Provides - * static Foo provideFoo(Hilt_FooAccessor accessor) { - * return accessor.get() - * } - * } - * ``` - * - * If the source module has multiple methods annotated as `@ComposeRetainedProvided`, one Module - * is generated with @Binds methods for each method. - */ - private fun moduleJavaFile(): JavaFile { - val typeSpec = - TypeSpec.classBuilder(metadata.moduleClassName) - .addGeneratedCodeAnnotations(metadata.enclosingElement, env) - .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.enclosingElement)) - .addAnnotation(ClassNames.MODULE) - .addAnnotation( - Components.getInstallInAnnotationSpec(ImmutableSet.of(COMPOSE_COMPONENT_CLASS)) - ) - .addModifiers(Modifier.ABSTRACT) - - for (providedType in metadata.providedTypes) { - typeSpec.addMethod(providesProvidedTypeMethod(providedType)) - } - - return JavaFile.builder(metadata.moduleClassName.packageName(), typeSpec.build()).build() - } - - /** - * ```java - * /* optional */ @Nullable - * @OptionalQualifier - * @Provides - * static Foo provides_Foo(Hilt_FooAccessor accessor) { - * return accessor.get() - * } - * ``` - */ - private fun providesProvidedTypeMethod(providedType: ProvidedType): MethodSpec { - return MethodSpec.methodBuilder(metadata.moduleProvidesMethodNameFor(providedType)) - .addAnnotation(ClassNames.PROVIDES) - .addOptionalQualifier(providedType.qualifier) - .addAnnotations(providedType.nullableAnnotations.map { XAnnotations.getAnnotationSpec(it) }) - .addModifiers(Modifier.STATIC) - .returns(providedType.typeName) - .addParameter( - ParameterSpec.builder(metadata.accessorClassNameOf(providedType), "accessor").build() - ) - .addStatement("return accessor.get()") - .build() - } - - companion object { - /** - * ClassName for [dagger.hilt.android.compose.components.ComposeComponent]. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSE_COMPONENT_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose.components", "ComposeComponent") - - /** ClassName for [dagger.hilt.android.compose.components.ComposeRetainedComponent]. */ - private val COMPOSE_RETAINED_COMPONENT_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose.components", "ComposeRetainedComponent") - - /** - * Classname for @HiltComposeInternal. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val HILT_COMPOSE_INTERNAL = - ClassName.get("dagger.hilt.android.compose.internal.qualifiers", "HiltComposeInternal") - - /** - * ParameterSpec for [dagger.hilt.android.compose.components.ComposeRetainedComponent], named - * "currentComposeRetainedComponent" and annotated with @HiltComposeInternal. - */ - private val CURRENT_COMPOSE_RETAINED_COMPONENT_PARAM = - ParameterSpec.builder(COMPOSE_RETAINED_COMPONENT_CLASS, "currentComposeRetainedComponent") - .addAnnotation(HILT_COMPOSE_INTERNAL) - .build() - } -} - -/** - * Adds default required code generation annotations to the TypeSpec.Builder. - * - * For a class "SpecificHost", the Type will be annotated as follows: - * ```java - * @Generated("ComposeRetainedProvidedGenerator") - * ``` - */ -@OptIn(ExperimentalProcessingApi::class) -private fun TypeSpec.Builder.addGeneratedCodeAnnotations( - originatingElement: XTypeElement, - env: XProcessingEnv -): TypeSpec.Builder { - this.addOriginatingElement(originatingElement) - Processors.addGeneratedAnnotation(this, env, ComposeRetainedProvidedGenerator::class.java) - return this -} - -/** Adds the provided annotation to the MethodSpec.Builder, if the annotation is present. */ -private fun MethodSpec.Builder.addOptionalQualifier( - optionalQualifier: XAnnotation? -): MethodSpec.Builder { - return if (optionalQualifier == null) this - else this.addAnnotation(XAnnotations.getAnnotationSpec(optionalQualifier)) -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedMetadata.kt b/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedMetadata.kt deleted file mode 100644 index 9be1bdc466d..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedMetadata.kt +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composeretainedprovided - -import androidx.room.compiler.codegen.toJavaPoet -import androidx.room.compiler.processing.XAnnotation -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XMethodElement -import androidx.room.compiler.processing.XTypeElement -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.TypeName -import dagger.hilt.processor.internal.ClassNames -import dagger.hilt.processor.internal.Components -import dagger.hilt.processor.internal.ProcessorErrors -import dagger.hilt.processor.internal.Processors -import dagger.internal.codegen.xprocessing.XElements - -/** - * Validates information about a class or module method annotated with - * [dagger.hilt.android.compose.ComposeRetainedProvided] and provides metadata about it for code - * generation. - * - * @param enclosingElement The element enclosing the provided type as an XTypeElement. For classes - * annotated as `@ComposeRetainedScoped`, this is the class itself. For methods annotated as - * `@ComposeRetainedScoped`, this is the module which defines the method. - * @param providedTypes A list of all of the types provided by the [enclosingElement] as - * [ProvidedTypes][ProvidedType]. For classes annotated as `@ComposeRetainedScoped`, this is the - * class itself. For methods annotated as `@ComposeRetainedScoped`, this is the return type of the - * method. - */ -internal class ComposeRetainedProvidedMetadata -private constructor(val enclosingElement: XTypeElement, val providedTypes: List) { - - /** JavaPoet ClassName for ${ENCLOSING_ELEMENT}. */ - private val enclosingElementClassName: ClassName = enclosingElement.asClassName().toJavaPoet() - - /** - * Returns the base class name for the [enclosingElement]. Has the form: - * Hilt_${ENCLOSING_ELEMENT}. - * - * This class name is not directly used in code generation, rather it's the base class name that - * other generated classes build upon. - * - * For classes annotated as `@ComposeRetainedScoped`, this is Hilt_${PROVIDED_TYPE}. For module - * methods annotated as `@ComposeRetainedScoped`, this is is Hilt_${MODULE_NAME}. - */ - private val baseEnclosingElementClassName: ClassName = - Processors.prepend(Processors.getEnclosedClassName(enclosingElementClassName), "Hilt_") - - /** - * The class name of the generated EntryPoint providing access to ${CLASS} outside of the - * ComposeRetainedComponent. Has the form Hilt_${ENCLOSING_ELEMENT}ComposeRetainedEntryPoint. - */ - val entryPointClassName: ClassName = - Processors.append(baseEnclosingElementClassName, "ComposeRetainedEntryPoint") - - /** - * The class name of the generated Dagger module which provides appropriate bindings for the - * generated classes. Has the form: Hilt_${ENCLOSING_ELEMENT}Module. - */ - val moduleClassName: ClassName = Processors.append(baseEnclosingElementClassName, "Module") - - /** - * The EntryPoint method name which provides access to [providedType]. Has the form - * "get_${fullyQualifiedEnclosingElement}${providedType.uniqueIdentifier}". - */ - fun entryPointGetterMethodNameFor(providedType: ProvidedType): String { - return "get_${Processors.getFullEnclosedName(enclosingElement)}${providedType.uniqueIdentifier}" - } - - /** - * The class name of the generated accessor class which has a getter for [providedType]. Has the - * form: Hilt_${ENCLOSING_ELEMENT}_${providedType.uniqueIdentifier}Accessor. - */ - fun accessorClassNameOf(providedType: ProvidedType): ClassName = - Processors.append(baseEnclosingElementClassName, "_${providedType.uniqueIdentifier}Accessor") - - /** - * The Module method name which provides the ProvidedType. Has the form - * "provides_${fullyQualifiedEnclosingElement}${providedType.uniqueIdentifier}". - */ - fun moduleProvidesMethodNameFor(providedType: ProvidedType): String { - return "provides_${Processors.getFullEnclosedName(enclosingElement)}${providedType.uniqueIdentifier}" - } - - companion object { - - /** Creates metadata for a class which is annotated as `@ComposeRetainedProvided`. */ - internal fun forClass(injectableClass: XTypeElement): ComposeRetainedProvidedMetadata { - ProcessorErrors.checkState( - injectableClass.getConstructors().any { it.hasAnnotation(ClassNames.INJECT) }, - "@ComposeRetainedProvided annotated classes should have an @Inject constructor:\n %s", - XElements.toStableString(injectableClass), - ) - - checkAllScopeAnnotationsAreComposeRetainedScoped(injectableClass) - - return ComposeRetainedProvidedMetadata( - injectableClass, - listOf( - ProvidedType( - uniqueIdentifier = injectableClass.asClassName().toJavaPoet().simpleName(), - typeName = injectableClass.type.typeName - ) - ), - ) - } - - /** - * Creates metadata for a group of methods annotated as `@ComposeRetainedProvided`. The methods - * should all be declared within the same [enclosingElement]. - */ - internal fun forMethods( - enclosingElement: XTypeElement, - composeRetainedProvidedMethods: List - ): ComposeRetainedProvidedMetadata { - val installInComponents = Components.getComponents(enclosingElement) - ProcessorErrors.checkState( - installInComponents.size == 1 && - installInComponents.contains(COMPOSE_RETAINED_COMPONENT_CLASS), - enclosingElement, - "@%s annotated methods must be inside @InstallIn(ComposeRetainedComponent.class) " + - "annotated classes:\n %s", - COMPOSE_RETAINED_PROVIDED_CLASS_NAME.simpleName(), - XElements.toStableString(enclosingElement) - ) - - val methodReturnTypes = mutableListOf() - - for (composeRetainedProvidedMethod in composeRetainedProvidedMethods) { - ProcessorErrors.checkState( - composeRetainedProvidedMethod.hasAnyAnnotation(ClassNames.PROVIDES, ClassNames.BINDS), - composeRetainedProvidedMethod, - "@%s annotated methods must also be annotated @Provides or @Binds:\n %s", - COMPOSE_RETAINED_PROVIDED_CLASS_NAME.simpleName(), - XElements.toStableString(composeRetainedProvidedMethod) - ) - - checkAllScopeAnnotationsAreComposeRetainedScoped(composeRetainedProvidedMethod) - - ProcessorErrors.checkState( - !composeRetainedProvidedMethod.hasAnyAnnotation( - ClassNames.INTO_SET, - ClassNames.INTO_MAP, - ClassNames.ELEMENTS_INTO_SET - ), - composeRetainedProvidedMethod, - "Multibindings are not supported for @%s annotated methods:\n %s", - COMPOSE_RETAINED_PROVIDED_CLASS_NAME.simpleName(), - XElements.toStableString(composeRetainedProvidedMethod) - ) - - val nullableAnnotations = - composeRetainedProvidedMethod.getAllAnnotations().filter { - XElements.getSimpleName(it.typeElement) == "Nullable" - } - - // At this point, we know the method is a @Provides or @Binds method. Other annotation - // processors guarantee that such methods have a return type and at most one @Qualifier - // annotation, so it's safe to get that information without validating it. - methodReturnTypes.add( - ProvidedType( - uniqueIdentifier = XElements.getSimpleName(composeRetainedProvidedMethod), - typeName = composeRetainedProvidedMethod.returnType.typeName, - qualifier = - Processors.getQualifierAnnotations(composeRetainedProvidedMethod).firstOrNull(), - nullableAnnotations = nullableAnnotations, - ) - ) - } - - return ComposeRetainedProvidedMetadata(enclosingElement, methodReturnTypes) - } - - private fun checkAllScopeAnnotationsAreComposeRetainedScoped(element: XElement) { - val scopeAnnotations = Processors.getScopeAnnotations(element) - ProcessorErrors.checkState( - scopeAnnotations.isNotEmpty(), - "@%s annotated classes and methods must be annotated @%s:\n %s", - COMPOSE_RETAINED_PROVIDED_CLASS_NAME.simpleName(), - COMPOSE_RETAINED_SCOPED_CLASS_NAME.simpleName(), - XElements.toStableString(element), - ) - - for (scopeAnnotation in Processors.getScopeAnnotations(element)) { - ProcessorErrors.checkState( - scopeAnnotation.className == COMPOSE_RETAINED_SCOPED_CLASS_NAME, - "@%s annotated classes and methods can only be annotated @%s:\n @%s %s", - COMPOSE_RETAINED_PROVIDED_CLASS_NAME.simpleName(), - COMPOSE_RETAINED_SCOPED_CLASS_NAME.simpleName(), - scopeAnnotation.name, - XElements.toStableString(element), - ) - } - } - - /** - * ClassName for [dagger.hilt.android.compose.ComposeRetainedProvided] - * - * TODO(b/281594970): Move to appropriate constants file. - */ - internal val COMPOSE_RETAINED_PROVIDED_CLASS_NAME: ClassName = - ClassName.get("dagger.hilt.android.compose", "ComposeRetainedProvided") - - /** - * Class name for @ComposeRetainedScoped. - * - * TODO(b/281594970): Move to appropriate constants file. - */ - private val COMPOSE_RETAINED_SCOPED_CLASS_NAME: ClassName = - ClassName.get("dagger.hilt.android.compose.scopes", "ComposeRetainedScoped") - - /** ClassName for [dagger.hilt.android.compose.components.ComposeRetainedComponent]. */ - private val COMPOSE_RETAINED_COMPONENT_CLASS: ClassName = - ClassName.get("dagger.hilt.android.compose.components", "ComposeRetainedComponent") - } -} - -/** - * Represents a type which is provided by either a class or a method annotated with - * `@ComposeRetainedProvided`. - * - * @param uniqueIdentifier a unique identifier used when generating classes and methods for the - * provided type. For example, the generated EntryPoint needs a getter method to return the - * typeName. - * @param typeName the TypeName of the provided type. - * @param qualifier an optional qualifier for the provided type. - */ -internal data class ProvidedType( - val uniqueIdentifier: String, - val typeName: TypeName, - val qualifier: XAnnotation? = null, - val nullableAnnotations: List = emptyList(), -) diff --git a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessingStep.kt b/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessingStep.kt deleted file mode 100644 index 7fb478fcaab..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessingStep.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composeretainedprovided - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XMethodElement -import androidx.room.compiler.processing.XProcessingEnv -import androidx.room.compiler.processing.XRoundEnv -import androidx.room.compiler.processing.isMethod -import androidx.room.compiler.processing.isTypeElement -import com.google.common.collect.ImmutableSet -import com.squareup.javapoet.ClassName -import dagger.hilt.processor.internal.BadInputException -import dagger.hilt.processor.internal.BaseProcessingStep -import dagger.internal.codegen.xprocessing.XElements - -/** Annotation processing step for [dagger.hilt.android.compose.ComposeRetainedProvided]. */ -@OptIn(ExperimentalProcessingApi::class) -class ComposeRetainedProvidedProcessingStep(env: XProcessingEnv) : BaseProcessingStep(env) { - - private val annotatedMethodElements = mutableListOf() - - override fun annotationClassNames() = - ImmutableSet.of(ComposeRetainedProvidedMetadata.COMPOSE_RETAINED_PROVIDED_CLASS_NAME) - - override fun processEach(annotation: ClassName, element: XElement) { - when { - element.isMethod() -> { - // Methods annotated as `@ComposeRetainedProvided` need to be collected and processed later - // since only one EntryPoint and one Module are generated for all the methods in a given - // enclosing class, i.e. module. The EntryPoint and Module have methods corresponding to - // each non-generated method annotated as `@ComposeRetainedProvided`. - annotatedMethodElements.add(XElements.asMethod(element)) - } - element.isTypeElement() && element.isClass() -> { - // Classes annotated as `@ComposeRetainedProvided` can be processed immediately as they're - // both the enclosing class and the single provided type. - val metadata = ComposeRetainedProvidedMetadata.forClass(element) - ComposeRetainedProvidedGenerator(processingEnv(), metadata).generate() - } - else -> { - throw BadInputException( - "@${ComposeRetainedProvidedMetadata.COMPOSE_RETAINED_PROVIDED_CLASS_NAME.simpleName()} " + - "can only be used with method and class types: \n\t${XElements.toStableString(element)}" - ) - } - } - } - - override fun postProcess(env: XProcessingEnv, round: XRoundEnv) { - try { - annotatedMethodElements - .groupBy { method -> XElements.asTypeElement(method.enclosingElement) } - .map { entry -> ComposeRetainedProvidedMetadata.forMethods(entry.key, entry.value) } - .forEach { metadata -> ComposeRetainedProvidedGenerator(env, metadata).generate() } - } finally { - annotatedMethodElements.clear() - } - } -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessor.kt b/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessor.kt deleted file mode 100644 index 96882d04458..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessor.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composeretainedprovided - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import com.google.auto.service.AutoService -import dagger.hilt.processor.internal.BaseProcessingStep -import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor -import javax.annotation.processing.Processor -import net.ltgt.gradle.incap.IncrementalAnnotationProcessor -import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType - -/** Annotation processor for [dagger.hilt.android.compose.ComposeRetainedProvided]. */ -@AutoService(Processor::class) -@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) -class ComposeRetainedProvidedProcessor : JavacBaseProcessingStepProcessor() { - @OptIn(ExperimentalProcessingApi::class) - override fun processingStep(): BaseProcessingStep { - return ComposeRetainedProvidedProcessingStep(xProcessingEnv) - } -} diff --git a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/KspComposeRetainedProvidedProcessor.kt b/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/KspComposeRetainedProvidedProcessor.kt deleted file mode 100644 index fe68b4182f3..00000000000 --- a/java/dagger/hilt/android/processor/internal/compose/composeretainedprovided/KspComposeRetainedProvidedProcessor.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composeretainedprovided - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import com.google.auto.service.AutoService -import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.processing.SymbolProcessorProvider -import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor - -/** Annotation processor for [dagger.hilt.android.compose.ComposeRetainedProvided]. */ -class KspComposeRetainedProvidedProcessor( - environment: SymbolProcessorEnvironment, -) : KspBaseProcessingStepProcessor(environment) { - - @ExperimentalProcessingApi - override protected fun processingStep() = ComposeRetainedProvidedProcessingStep(xProcessingEnv) - - /** Provides the {@link KspComposeRetainedProvidedProcessor}. */ - @AutoService(SymbolProcessorProvider::class) - class Provider : SymbolProcessorProvider { - override fun create(environment: SymbolProcessorEnvironment) = - KspComposeRetainedProvidedProcessor(environment) - } -} diff --git a/javatests/dagger/hilt/android/compose/BUILD b/javatests/dagger/hilt/android/compose/BUILD deleted file mode 100644 index 2f023866405..00000000000 --- a/javatests/dagger/hilt/android/compose/BUILD +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Hilt Android classes for Jetpack Compose. - -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_local_test") - -package(default_visibility = ["//:src"]) - -kt_android_local_test( - name = "ComposeComponentHostExtrasTest", - srcs = ["ComposeComponentHostExtrasTest.kt"], - manifest = "ComposeComponentHostExtrasTest_AndroidManifest.xml", - manifest_values = { - "minSdkVersion": "21", - }, - deps = [ - "//:dagger_with_compiler/hilt/testing", - "//java/dagger/hilt/android/compose:compose_component_host_extras", - "//third_party/java/android_libs/guava_jdk5:guava_testlib", - "//third_party/java/androidx/compose/runtime", - "//third_party/java/androidx/compose/ui/test_junit4", - "//third_party/java/truth", - ], -) - -kt_android_local_test( - name = "RememberComponentHostTest", - srcs = ["RememberComponentHostTest.kt"], - manifest = "RememberComponentHostTest_AndroidManifest.xml", - manifest_values = { - "minSdkVersion": "21", - }, - resource_files = glob(["res/**"]), - deps = [ - "//:dagger_with_compiler/hilt:android_entry_point", - "//:dagger_with_compiler/hilt:view_model", - "//:dagger_with_compiler/hilt/testing", - "//:dagger_with_compiler/hilt/testing:bind_value", - "//java/dagger/hilt/android/compose:component_host_creator", - "//java/dagger/hilt/android/compose:compose_component_host", - "//java/dagger/hilt/android/compose:compose_retained_lifecycle", - "//java/dagger/hilt/android/compose:compose_retained_provided", - "//java/dagger/hilt/android/compose:remember_component_host", - "//java/dagger/hilt/android/compose/internal/testing", - "//java/dagger/hilt/android/compose/scopes", - "//java/dagger/hilt/android/qualifiers", - "//java/dagger/hilt/android/testing:hilt_android_rule", - "//third_party/java/androidx/activity", - "//third_party/java/androidx/activity/compose", - "//third_party/java/androidx/compose/foundation", - "//third_party/java/androidx/compose/foundation/layout", - "//third_party/java/androidx/compose/material", - "//third_party/java/androidx/compose/runtime", - "//third_party/java/androidx/compose/ui", - "//third_party/java/androidx/compose/ui/test", - "//third_party/java/androidx/compose/ui/test_junit4", - "//third_party/java/androidx/compose/ui/text", - "//third_party/java/androidx/lifecycle/common", - "//third_party/java/error_prone:annotations", - "//third_party/java/jsr305_annotations", - "//third_party/java/jsr330_inject", - "//third_party/java/truth", - "@maven//:androidx_lifecycle_lifecycle_viewmodel", - ], -) - -kt_android_local_test( - name = "RememberComponentHostExtrasTest", - srcs = ["RememberComponentHostExtrasTest.kt"], - manifest = "RememberComponentHostExtrasTest_AndroidManifest.xml", - manifest_values = { - "minSdkVersion": "21", - }, - deps = [ - "//:dagger_with_compiler/hilt:android_entry_point", - "//:dagger_with_compiler/hilt/testing", - "//:dagger_with_compiler/hilt/testing:bind_value", - "//java/dagger/hilt/android/compose:compose_component_host", - "//java/dagger/hilt/android/compose:compose_component_host_extras", - "//java/dagger/hilt/android/compose:compose_retained_lifecycle", - "//java/dagger/hilt/android/compose:compose_retained_provided", - "//java/dagger/hilt/android/compose:remember_component_host", - "//java/dagger/hilt/android/compose/internal/testing", - "//java/dagger/hilt/android/compose/scopes", - "//third_party/java/androidx/activity", - "//third_party/java/androidx/activity/compose", - "//third_party/java/androidx/compose/foundation/layout", - "//third_party/java/androidx/compose/runtime", - "//third_party/java/androidx/compose/ui/test_junit4", - "//third_party/java/jsr330_inject", - "//third_party/java/truth", - ], -) diff --git a/javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest.kt b/javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest.kt deleted file mode 100644 index fae44486779..00000000000 --- a/javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.testing.EqualsTester -import com.google.common.truth.Truth.assertThat -import dagger.hilt.android.testing.HiltAndroidTest -import dagger.hilt.android.testing.HiltTestApplication -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@RunWith(AndroidJUnit4::class) -@Config(application = HiltTestApplication::class) -@HiltAndroidTest -class ComposeComponentHostExtrasTest { - @get:Rule val composeTestRule = createComposeRule() - - @Test - fun get_notPresent_isNull() { - composeTestRule.setContent { - val value: Int? = buildComposeComponentHostExtras {}.get(IntKey) - assertThat(value).isNull() - } - } - - @Test - fun get_intKey_returnsProperValue() { - composeTestRule.setContent { - val value: Int? = buildComposeComponentHostExtras { put(IntKey, 5) }.get(IntKey) - assertThat(value).isEqualTo(5) - } - } - - @Test - fun get_longKey_returnsProperValue() { - composeTestRule.setContent { - val value: Long? = buildComposeComponentHostExtras { put(LongKey, 5) }.get(LongKey) - assertThat(value).isEqualTo(5) - } - } - - @Test - fun get_floatKey_returnsProperValue() { - composeTestRule.setContent { - val value: Float? = buildComposeComponentHostExtras { put(FloatKey, 5.5f) }.get(FloatKey) - assertThat(value).isEqualTo(5.5f) - } - } - - @Test - fun get_doubleKey_returnsProperValue() { - composeTestRule.setContent { - val value: Double? = buildComposeComponentHostExtras { put(DoubleKey, 5.5) }.get(DoubleKey) - assertThat(value).isEqualTo(5.5) - } - } - - @Test - fun get_booleanKey_returnsProperValue() { - composeTestRule.setContent { - val value: Boolean? = - buildComposeComponentHostExtras { put(BooleanKey, true) }.get(BooleanKey) - assertThat(value).isNotNull() - assertThat(value).isTrue() - } - } - - @Test - fun get_stringKey_returnsProperValue() { - composeTestRule.setContent { - val value: String? = - buildComposeComponentHostExtras { put(StringKey, "hello") }.get(StringKey) - assertThat(value).isNotNull() - assertThat(value).isEqualTo("hello") - } - } - - @Test - fun get_sameKeyPutMultipleTimes_overwritesValue() { - composeTestRule.setContent { - val extras = buildComposeComponentHostExtras { - put(StringKey, "hello") - put(StringKey, "world") - } - - assertThat(extras.get(StringKey)).isEqualTo("world") - } - } - - @Test - fun get_multipleSameTypeExtrasInserted_returnsProperValue() { - composeTestRule.setContent { - val extras = buildComposeComponentHostExtras { - put(StringKey, "hello") - put(StringKey2, "world") - } - - assertThat(extras.get(StringKey)).isEqualTo("hello") - assertThat(extras.get(StringKey2)).isEqualTo("world") - } - } - - @Test - fun get_multipleDifferentTypeExtrasInserted_allReturnProperValues() { - composeTestRule.setContent { - val extras = buildComposeComponentHostExtras { - put(IntKey, 5) - put(FloatKey, 5.5f) - put(StringKey, "hello") - } - - assertThat(extras.get(IntKey)).isEqualTo(5) - assertThat(extras.get(FloatKey)).isEqualTo(5.5f) - assertThat(extras.get(StringKey)).isEqualTo("hello") - } - } - - @Test - fun equals() { - composeTestRule.setContent { - EqualsTester() - .addEqualityGroup(buildComposeComponentHostExtras { put(IntKey, 5) }) - .addEqualityGroup(buildComposeComponentHostExtras { put(IntKey, 10) }) - .addEqualityGroup(buildComposeComponentHostExtras { put(FloatKey, 10f) }) - .addEqualityGroup( - buildComposeComponentHostExtras { put(StringKey, "hello") }, - buildComposeComponentHostExtras { put(StringKey, "hello") } - ) - .addEqualityGroup( - buildComposeComponentHostExtras { - put(IntKey, 5) - put(StringKey, "hello") - }, - buildComposeComponentHostExtras { - put(IntKey, 5) - put(StringKey, "hello") - } - ) - .testEquals() - } - } - - private object IntKey : ComposeComponentHostExtras.Key - - private object LongKey : ComposeComponentHostExtras.Key - - private object FloatKey : ComposeComponentHostExtras.Key - - private object DoubleKey : ComposeComponentHostExtras.Key - - private object BooleanKey : ComposeComponentHostExtras.Key - - private object StringKey : ComposeComponentHostExtras.Key - - private object StringKey2 : ComposeComponentHostExtras.Key -} diff --git a/javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest_AndroidManifest.xml b/javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest_AndroidManifest.xml deleted file mode 100644 index 30003ef7125..00000000000 --- a/javatests/dagger/hilt/android/compose/ComposeComponentHostExtrasTest_AndroidManifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest.kt b/javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest.kt deleted file mode 100644 index 575eda130a5..00000000000 --- a/javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest.kt +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.junit4.createEmptyComposeRule -import androidx.compose.ui.test.performClick -import androidx.test.core.app.ActivityScenario -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import dagger.hilt.android.AndroidEntryPoint -import dagger.hilt.android.compose.RememberComponentHostExtrasTest.TestHost.Companion.getOnlyOrNull -import dagger.hilt.android.compose.internal.testing.assertHasCustomInfo -import dagger.hilt.android.compose.internal.testing.customInfo -import dagger.hilt.android.compose.internal.testing.LabeledNode -import dagger.hilt.android.compose.internal.testing.onNodeWithLabel -import dagger.hilt.android.compose.internal.testing.NodeLabel -import dagger.hilt.android.compose.scopes.ComposeRetainedScoped -import dagger.hilt.android.testing.BindValue -import dagger.hilt.android.testing.HiltAndroidRule -import dagger.hilt.android.testing.HiltAndroidTest -import dagger.hilt.android.testing.HiltTestApplication -import java.util.concurrent.atomic.AtomicBoolean -import javax.inject.Inject -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@RunWith(AndroidJUnit4::class) -@Config(application = HiltTestApplication::class) -@HiltAndroidTest -class RememberComponentHostExtrasTest { - @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) - @get:Rule(order = 1) val composeTestRule = createEmptyComposeRule() - - @BindValue val isRetainedLifecycleCleared: AtomicBoolean = AtomicBoolean(false) - - @Before - fun setUp() { - hiltRule.inject() - } - - @Test - fun rememberComponentHost_argsAvailableInHost() { - ActivityScenario.launch(ConstantHostExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(TestHost.extraLabel) - .assertHasCustomInfo(ConstantHostExtrasActivity.EXTRA_VALUE) - } - } - - @Test - fun rememberComponentHost_argsAvailableInComposeComponentDep() { - ActivityScenario.launch(ConstantHostExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeComponentDep.extraLabel) - .assertHasCustomInfo(ConstantHostExtrasActivity.EXTRA_VALUE) - } - } - - @Test - fun rememberComponentHost_argsAvailableInComposeRetainedDep() { - ActivityScenario.launch(ConstantHostExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeRetainedComponentDep.extraLabel) - .assertHasCustomInfo(ConstantHostExtrasActivity.EXTRA_VALUE) - } - } - - @Test - fun rememberComponentHost_rememberedExtraChanges_componentsAreConsistent() { - ActivityScenario.launch(RememberedExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(RememberedExtrasActivity.clickToChangeExtraLabel) - .performClick() - composeTestRule - .onNodeWithLabel(RememberedExtrasActivity.clickToChangeExtraLabel) - .assertHasCustomInfo(1) - - // The above click changes the value of the extra provided to the host. Both the - // ComposeComponent and the ComposeRetainedComponent should be recreated and the dependencies - // injected from each should see a consistent value for the extra. - composeTestRule.onNodeWithLabel(ComposeComponentDep.extraLabel).assertHasCustomInfo(1) - composeTestRule.onNodeWithLabel(ComposeRetainedComponentDep.extraLabel).assertHasCustomInfo(1) - } - } - - @Test - fun rememberComponentHost_rememberedExtraConfigurationChange_componentsAreConsistent() { - ActivityScenario.launch(RememberedExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(RememberedExtrasActivity.clickToChangeExtraLabel) - .performClick() - composeTestRule - .onNodeWithLabel(RememberedExtrasActivity.clickToChangeExtraLabel) - .assertHasCustomInfo(1) - - it.recreate() - - // The extras's value is only remembered, so it's reset after activity recreation. Resetting - // the remembered value means the extra changes, so both the ComposeComponent and the - // ComposeRetainedComponent should be recreated and the dependencies injected from each should - // see a consistent value for the extra. - composeTestRule.onNodeWithLabel(ComposeComponentDep.extraLabel).assertHasCustomInfo(0) - composeTestRule.onNodeWithLabel(ComposeRetainedComponentDep.extraLabel).assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_rememberSaveabledExtraChanges_componentsAreConsistent() { - ActivityScenario.launch(RemeberSaveabledExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(RemeberSaveabledExtrasActivity.clickToChangeExtraLabel) - .performClick() - composeTestRule - .onNodeWithLabel(RemeberSaveabledExtrasActivity.clickToChangeExtraLabel) - .assertHasCustomInfo(1) - - // The above click changes the value of the extra provided to the host. Both the - // ComposeComponent and the ComposeRetainedComponent should be recreated and the dependencies - // injected from each should see a consistent value for the extra. - composeTestRule.onNodeWithLabel(ComposeComponentDep.extraLabel).assertHasCustomInfo(1) - composeTestRule.onNodeWithLabel(ComposeRetainedComponentDep.extraLabel).assertHasCustomInfo(1) - } - } - - @Test - fun rememberComponentHost_rememberSaveabledExtraConfigurationChange_componentsAreConsistent() { - ActivityScenario.launch(RemeberSaveabledExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(RemeberSaveabledExtrasActivity.clickToChangeExtraLabel) - .performClick() - composeTestRule - .onNodeWithLabel(RemeberSaveabledExtrasActivity.clickToChangeExtraLabel) - .assertHasCustomInfo(1) - - it.recreate() - - // The extra's value is rememberSaveable, so it doesn't change after activity recreation. - // The host and the ComposeComponent are recreated, but the ComposeRetainedComponent is not. - // Dependencies injected from each component should still see a consistent value. - composeTestRule.onNodeWithLabel(ComposeComponentDep.extraLabel).assertHasCustomInfo(1) - composeTestRule.onNodeWithLabel(ComposeRetainedComponentDep.extraLabel).assertHasCustomInfo(1) - } - } - - @Test - fun rememberComponentHost_extrasWithSameHashCodeChange_extrasAreCorrect() { - ActivityScenario.launch(HashCollisionActivity::class.java).use { - // Switch to using extras2 - composeTestRule - .onNodeWithLabel(HashCollisionActivity.clickToToggleUseExtra1Label) - .performClick() - composeTestRule - .onNodeWithLabel(ComposeComponentDep.extraLabel) - .assertHasCustomInfo(TestHost.CollidingExtraKey2.value) - composeTestRule - .onNodeWithLabel(ComposeRetainedComponentDep.extraLabel) - .assertHasCustomInfo(TestHost.CollidingExtraKey2.value) - - // Recreating switches back to using extras1, but extras1 and extras2 have the same hashCode - it.recreate() - - // Even though the hashCodes for the extras are the same, they aren't equal so the retained - // component should be recreated and pick up the new extra value - composeTestRule - .onNodeWithLabel(ComposeComponentDep.extraLabel) - .assertHasCustomInfo(TestHost.CollidingExtraKey1.value) - composeTestRule - .onNodeWithLabel(ComposeRetainedComponentDep.extraLabel) - .assertHasCustomInfo(TestHost.CollidingExtraKey1.value) - } - } - - @Test - fun rememberComponentHost_extrasWithSameHashCodeChange_retainedComponentCleared() { - ActivityScenario.launch(HashCollisionActivity::class.java).use { - composeTestRule - .onNodeWithLabel(HashCollisionActivity.clickToToggleUseExtra1Label) - .performClick() - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performClick() - assertThat(isRetainedLifecycleCleared.get()).isFalse() - - it.recreate() - - // The extras changed, but have the same hashCode. This means we'll restore the retained - // component, see that the extras are not equal, then discard and clear the component - assertThat(isRetainedLifecycleCleared.get()).isTrue() - } - } - - @Test - fun rememberComponentHost_constantExtras_retainedComponentRetained() { - ActivityScenario.launch(ConstantHostExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performClick() - assertThat(isRetainedLifecycleCleared.get()).isFalse() - - it.recreate() - - // Since the extra is constant, the ComposeRetainedComponent should not be cleared or - // recreated after activity recreation - assertThat(isRetainedLifecycleCleared.get()).isFalse() - } - } - - @Test - fun rememberComponentHost_extrasChange_previousRetainedComponentCleared() { - ActivityScenario.launch(RememberedExtrasActivity::class.java).use { - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performClick() - assertThat(isRetainedLifecycleCleared.get()).isFalse() - - composeTestRule - .onNodeWithLabel(RememberedExtrasActivity.clickToChangeExtraLabel) - .performClick() - composeTestRule - .onNodeWithLabel(RememberedExtrasActivity.clickToChangeExtraLabel) - .assertHasCustomInfo(1) - - // Changing the extra causes the previous ComposeRetainedComponent to be cleared - assertThat(isRetainedLifecycleCleared.get()).isTrue() - } - } - - /** Sets [TestHost's][TestHost] extras to constant values. */ - @AndroidEntryPoint(ComponentActivity::class) - class ConstantHostExtrasActivity : - Hilt_RememberComponentHostExtrasTest_ConstantHostExtrasActivity() { - @Inject lateinit var testHostCreator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - val extras = buildComposeComponentHostExtras { put(TestHost.ExtraKey, EXTRA_VALUE) } - - val host = rememberComponentHost(testHostCreator, extras) - host.Content() - } - } - - companion object { - const val EXTRA_VALUE = 99 - } - } - - /** Preserves extra values across recomposition by using [remember]. */ - @AndroidEntryPoint(ComponentActivity::class) - class RememberedExtrasActivity : Hilt_RememberComponentHostExtrasTest_RememberedExtrasActivity() { - @Inject lateinit var testHostCreator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - Column { - var extraValue by remember { mutableStateOf(0) } - - LabeledNode( - clickToChangeExtraLabel, - Modifier.clickable { extraValue++ }.customInfo(extraValue) - ) - - val extras = buildComposeComponentHostExtras { put(TestHost.ExtraKey, extraValue) } - - val host = rememberComponentHost(testHostCreator, extras) - host.Content() - } - } - } - - companion object { - val clickToChangeExtraLabel = NodeLabel("RememberedExtrasActivity", "clickToChangeExtra") - } - } - - /** Preserves extra values across configuration changes by using [rememberSaveable]. */ - @AndroidEntryPoint(ComponentActivity::class) - class RemeberSaveabledExtrasActivity : - Hilt_RememberComponentHostExtrasTest_RemeberSaveabledExtrasActivity() { - @Inject lateinit var testHostCreator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - Column { - var extraValue by rememberSaveable { mutableStateOf(0) } - - LabeledNode( - clickToChangeExtraLabel, - Modifier.clickable { extraValue++ }.customInfo(extraValue), - ) - - val extras = buildComposeComponentHostExtras { put(TestHost.ExtraKey, extraValue) } - - val host = rememberComponentHost(testHostCreator, extras) - host.Content() - } - } - } - - companion object { - val clickToChangeExtraLabel = - NodeLabel("RemeberSaveabledExtrasActivity", "clickToChangeExtra") - } - } - - @AndroidEntryPoint(ComponentActivity::class) - class HashCollisionActivity : Hilt_RememberComponentHostExtrasTest_HashCollisionActivity() { - @Inject lateinit var testHostCreator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - Column { - var useExtra1 by remember { mutableStateOf(true) } - - LabeledNode( - clickToToggleUseExtra1Label, - Modifier.clickable { useExtra1 = !useExtra1 }.customInfo(useExtra1) - ) - - val extras = buildComposeComponentHostExtras { - if (useExtra1) { - put(TestHost.CollidingExtraKey1, TestHost.CollidingExtraKey1.value) - } else { - put(TestHost.CollidingExtraKey2, TestHost.CollidingExtraKey2.value) - } - } - - val host = rememberComponentHost(testHostCreator, extras) - host.Content() - } - } - } - - companion object { - val clickToToggleUseExtra1Label = NodeLabel("HashCollisionActivity", "clickToToggleUseExtra1") - } - } - - @ComposeComponentHost - class TestHost - @Inject - internal constructor( - private val extras: ComposeComponentHostExtras?, - private val composeComponentDep: ComposeComponentDep, - private val composeRetainedComponentDep: ComposeRetainedComponentDep, - private val retainedComponentClearedTracker: RetainedComponentClearedTracker, - ) : Hilt_RememberComponentHostExtrasTest_TestHost() { - - @Composable - fun Content() { - Column { - LabeledNode(extraLabel, Modifier.customInfo(extras?.get(ExtraKey))) - - composeComponentDep.Content() - - composeRetainedComponentDep.Content() - - retainedComponentClearedTracker.Content() - } - } - - object ExtraKey : ComposeComponentHostExtras.Key - - open class CollidingExtraKey constructor(val value: Int) : ComposeComponentHostExtras.Key { - - /** - * Returns a hashCode which will easily cause hash collisions for ComposeComponentHostExtras. - * - * Map's hashCode is a composition of the hashCodes of all of the entries, and each entry's - * hashCode is `key.hashCode() xor value.hashCode()`. If the key and the value have the same - * hashCode, they'll xor to 0. We can create two non-equal maps with the same hashCode by - * mapping two different objects to themselves. ComposeComponentHostExtras doesn't allow - * arbitrary values, so we can't directly map a key to itself. However, we can instead - * override the key's hashCode to be the same as its value. By using the subclasses - * [CollidingExtraKey1] and [CollidingExtraKey2] we can create two unequal - * ComposeComponentHostExtras that have the same hashCode: - * ```kotlin - * val extras1 = buildComposeComponentHostExtras { - * put(CollidingExtraKey1, CollidingExtraKey1.value) - * } - * - * val extras2 = buildComposeComponentHostExtras { - * put(CollidingExtraKey2, CollidingExtraKey2.value) - * } - * ``` - */ - final override fun hashCode(): Int = value.hashCode() - - /** Not needed for this test, included to avoid overriding hashCode without equals warning. */ - override fun equals(other: Any?): Boolean { - if (other !is CollidingExtraKey) return false - return value == other.value - } - } - - object CollidingExtraKey1 : CollidingExtraKey(5) - - object CollidingExtraKey2 : CollidingExtraKey(10) - - companion object { - val extraLabel = NodeLabel("TestHost", "extra") - - /** - * Picks the first extra that's present in the order of [ExtraKey], [CollidingExtraKey1], then - * [CollidingExtraKey2], crashing if more than one is set. - */ - fun ComposeComponentHostExtras.getOnlyOrNull(): Int? { - val extrasInOrder = - listOfNotNull(get(ExtraKey), get(CollidingExtraKey1), get(CollidingExtraKey2)) - require(extrasInOrder.size < 2) { - "Multiple extras found: $extrasInOrder. Tests assume only one extra is set." - } - return extrasInOrder.firstOrNull() - } - } - } - - class ComposeComponentDep @Inject constructor(private val extras: ComposeComponentHostExtras?) { - - @Composable - fun Content() { - LabeledNode(extraLabel, Modifier.customInfo(extras?.getOnlyOrNull())) - } - - companion object { - val extraLabel = NodeLabel("TestHost", "ComposeComponentDep", "extra") - } - } - - @ComposeRetainedScoped - @ComposeRetainedProvided - class ComposeRetainedComponentDep - @Inject - internal constructor(private val extras: ComposeComponentHostExtras?) { - - @Composable - fun Content() { - LabeledNode(extraLabel, Modifier.customInfo(extras?.getOnlyOrNull())) - } - - companion object { - val extraLabel = NodeLabel("TestHost", "ComposeRetainedDep", "extra") - } - } - - @ComposeRetainedScoped - @ComposeRetainedProvided - class RetainedComponentClearedTracker - @Inject - internal constructor( - composeRetainedLifecycle: ComposeRetainedLifecycle, - private val isRetainedLifecycleCleared: AtomicBoolean, - ) { - - init { - composeRetainedLifecycle.addOnClearedListener { isRetainedLifecycleCleared.set(true) } - } - - @Composable - fun Content() { - LabeledNode( - clickToResetLabel, - Modifier.clickable { isRetainedLifecycleCleared.set(false) }, - ) - } - - companion object { - val clickToResetLabel = - NodeLabel("TestHost", "RetainedComponentClearedTracker", "clickToReset") - } - } -} diff --git a/javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest_AndroidManifest.xml b/javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest_AndroidManifest.xml deleted file mode 100644 index a4e6f28e90b..00000000000 --- a/javatests/dagger/hilt/android/compose/RememberComponentHostExtrasTest_AndroidManifest.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/javatests/dagger/hilt/android/compose/RememberComponentHostTest.kt b/javatests/dagger/hilt/android/compose/RememberComponentHostTest.kt deleted file mode 100644 index 72008c9fc40..00000000000 --- a/javatests/dagger/hilt/android/compose/RememberComponentHostTest.kt +++ /dev/null @@ -1,1676 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.compose - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.widget.LinearLayout -import android.widget.LinearLayout.LayoutParams -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.test.junit4.createEmptyComposeRule -import androidx.compose.ui.test.onAllNodesWithText -import androidx.compose.ui.test.onRoot -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performScrollTo -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.test.core.app.ActivityScenario -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.AndroidEntryPoint -import dagger.hilt.android.compose.components.ComposeRetainedComponent -import dagger.hilt.android.compose.internal.testing.LabeledNode -import dagger.hilt.android.compose.internal.testing.NodeLabel -import dagger.hilt.android.compose.internal.testing.assertHasCustomInfo -import dagger.hilt.android.compose.internal.testing.customInfo -import dagger.hilt.android.compose.internal.testing.onAllNodesWithLabel -import dagger.hilt.android.compose.internal.testing.onNodeWithLabel -import dagger.hilt.android.compose.scopes.ComposeRetainedScoped -import dagger.hilt.android.compose.scopes.ComposeScoped -import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.android.testing.BindValue -import dagger.hilt.android.testing.HiltAndroidRule -import dagger.hilt.android.testing.HiltAndroidTest -import dagger.hilt.android.testing.HiltTestApplication -import java.util.concurrent.atomic.AtomicBoolean -import javax.annotation.Nullable -import javax.inject.Inject -import javax.inject.Named -import javax.inject.Provider -import javax.inject.Qualifier -import javax.inject.Singleton -import org.junit.Assert.assertThrows -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@RunWith(AndroidJUnit4::class) -@Config(application = HiltTestApplication::class) -@HiltAndroidTest -class RememberComponentHostTest { - @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) - @get:Rule(order = 1) val composeTestRule = createEmptyComposeRule() - - @BindValue - @OnClearedComposeHost.RetainedLifecycleCleared - val isOnClearedComposeHostRetainedLifecycleCleared = AtomicBoolean(false) - - @BindValue - @OnClearedComposeHost2.RetainedLifecycleCleared - val isOnClearedComposeHost2RetainedLifecycleCleared = AtomicBoolean(false) - - @Inject @ApplicationContext lateinit var appContext: Context - - @Before - fun setUp() { - hiltRule.inject() - } - - @Test - fun rememberComponentHost_invalidCreator_throws() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - activity.setContent { - @Suppress("CheckReturnValue") // calling rememberComponentHost is sufficient - rememberComponentHost(creator = object : ComponentHostCreator {}) - } - } - - // This has to be tested in a roundabout way. Calling setContent doesn't immediately cause - // the content lambda to be run, instead it's stored to be run later. In order to make that - // lambda run, we need to interact with the composition, which is done here with the assertion - assertThrows(IllegalArgumentException::class.java) { composeTestRule.onRoot().assertExists() } - } - } - - @Test - fun clickRecomposeButton_recompositionTriggered() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - // This test verifies that the testing infrastructure in this class actually works, - // specifically that clicking the recompose button triggers recomposition. Several later tests - // make sure the same instance is used on recomposition so it's important that this button - // works. - scenario.onActivity { activity -> activity.triggerRecomposition() } - - composeTestRule.onNodeWithLabel(TestActivity.recompositionCountLabel).assertHasCustomInfo(1) - } - } - - @Test - fun rememberComponentHost_validCreator_hostIsRememberedAcrossRecomposition() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> activity.triggerRecomposition() } - composeTestRule.onNodeWithLabel(TestActivity.recompositionCountLabel).assertHasCustomInfo(1) - - // If the componentHost wasn't remembered, a new instance would be created and its instance - // number would change. - composeTestRule.onNodeWithLabel(ComposeHost.hostLabel).assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_composeScopedDep_depInstanceIsSharedInHost() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule.onNodeWithLabel(ComposeHost.dep1ScopedDepLabel).assertHasCustomInfo(0) - composeTestRule.onNodeWithLabel(ComposeHost.dep2ScopedDepLabel).assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_composeScopedDep_depInstanceNotSharedAcrossHosts() { - ActivityScenario.launch(TestActivity::class.java).use { - // ComposeComponentHosts define the scope of the ComposeComponent. Two separate hosts mean two - // separate components and two separate ScopedDeps. - composeTestRule.onNodeWithLabel(ComposeHost2.scopedDepLabel).assertHasCustomInfo(1) - } - } - - @Test - fun rememberComponentHost_nestedComponentHost_isAddedCorrectly() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(TestActivity.NestedComponentHost.nestedHostLabel) - .assertExists() - } - } - - @Test - fun rememberComponentHost_retainedScopedDep_depInstanceIsSharedInHost() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .assertHasCustomInfo(0) - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDep2RetainedDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_composeScopedAndRetainedScopedDep_onlyRetainedKeptAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepLabel) - .assertHasCustomInfo(0) - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepLabel) - .assertHasCustomInfo(1) - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_composableWithRetainedRemovedAndReAdded_retainedNotKept() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .assertHasCustomInfo(0) - - // Remove ComposeHostWithRetainedDep from the composition entirely. - scenario.onActivity { activity -> activity.setComposeHostWithRetainedDepEnabled(false) } - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .assertDoesNotExist() - - // Re-add ComposeHostWithRetainedDep to the composition. Since it was completely removed, the - // retained dep is not kept and a new instance is created - scenario.onActivity { activity -> activity.setComposeHostWithRetainedDepEnabled(true) } - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .assertHasCustomInfo(1) - } - } - - @Test - fun rememberComponentHost_multipleComposeViewsWithIds_noKeyInterference() { - ActivityScenario.launch(MultipleComposeViewActivity::class.java).use { - // The same retainedHost1 was added in two separate ComposeViews. Each will have their own - // instance of dep1 since they're separate hosts. - - composeTestRule - .onAllNodesWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .apply { - this[0].assertHasCustomInfo(0) - this[1].assertHasCustomInfo(1) - } - - // Remove the first retainedHost1 from the composition. Its ComposeRetainedComponent is fully - // removed and will not be retained. The second retainedHost1 remains. - composeTestRule - .onAllNodesWithText("click to toggle composeHostWithRetainedDep enabled")[0] - .performClick() - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .assertHasCustomInfo(1) - - // Host2 should still be retained, so recreating the activity should still keep the same - // instance of retainedDep. If there's key interference, removing host1 would remove host2's - // ComposeRetainedComponent, and a new retainedDep instance would be created. Since we're - // recreating, the removal of retainedHost1 is forgotten and it's part of the composition - // again. - it.recreate() - composeTestRule - .onAllNodesWithLabel(ComposeHostWithRetainedDep.usesRetainedDepRetainedDepLabel) - .apply { - this[0].assertHasCustomInfo(2) - this[1].assertHasCustomInfo(1) - } - } - } - - @Test - fun rememberComponentHost_nonQualifiedRetainedDep_isRetainedAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.nonQualifiedTypeDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.nonQualifiedTypeDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_parameterizedTypeRetainedDep_isRetainedAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.parameterizedTypeDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.parameterizedTypeDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_primitiveTypeRetainedDep_isInjectedCorrectly() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.primitiveTypeDepLabel) - .assertHasCustomInfo(5) - } - } - - @Test - fun rememberComponentHost_providerPrimitiveTypeRetainedDep_isInjectedCorrectly() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.providerPrimitiveTypeDepLabel) - .assertHasCustomInfo(5) - } - } - - @Test - fun rememberComponentHost_multipleBindingsModuleFirstTypeRetainedDep_isRetainedAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.moduleFirstTypeDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.moduleFirstTypeDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_multipleBindingsModuleSecondTypeRetainedDep_isRetainedAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.moduleSecondTypeDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.moduleSecondTypeDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_withQualifiedRetainedDepByProvides_isRetainedAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.providedRetainedDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.providedRetainedDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_withQualifiedRetainedDepByBinds_isRetainedAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.boundRetainedDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.boundRetainedDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_withNamedRetainedDep_isRetainedAfterRecreate() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.namedRetainedDepLabel) - .assertHasCustomInfo(0) - - it.recreate() - - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.namedRetainedDepLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_moduleHasSameProvidesMethodNameAsAnother_injectsTypeCorrectly() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.otherNamedRetainedDepLabel) - .assertHasCustomInfo(1) - } - } - - @Test - fun rememberComponentHost_withNullableRetainedDep_isInjectedCorrectly() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(ComposeHostWithRetainedDepsFromModules.nullableRetainedDepLabel) - .assertHasCustomInfo(null) - } - } - - @Test - fun rememberComponentHost_innerClassesWithSameName_injectsTypesCorrectly() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(DuplicateInnerClassComposeHost.outerClass1InnerClassLabel) - .assertHasCustomInfo(0) - composeTestRule - .onNodeWithLabel(DuplicateInnerClassComposeHost.outerClass2InnerClassLabel) - .assertHasCustomInfo(0) - composeTestRule - .onNodeWithLabel(DuplicateInnerClassComposeHost.outerClass1ModuleProvidedTypeLabel) - .assertHasCustomInfo(0) - composeTestRule - .onNodeWithLabel(DuplicateInnerClassComposeHost.outerClass2ModuleProvidedTypeLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_twoOfTheSameHost_useCorrectRetainedComponent() { - ActivityScenario.launch(EmptyTestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - activity.setContent { - Column { - val host1 = rememberComponentHost(activity.keyInterferenceHost1Creator) - host1.Composable() - - val host2 = rememberComponentHost(activity.keyInterferenceHost1Creator) - host2.Composable() - } - } - } - - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[0] - .performClick() - - val nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_differentHosts_useCorrectRetainedComponent() { - ActivityScenario.launch(EmptyTestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - activity.setContent { - Column { - val host1 = rememberComponentHost(activity.keyInterferenceHost1Creator) - host1.Composable() - - val host2 = rememberComponentHost(activity.keyInterferenceHost2Creator) - host2.Composable() - } - } - } - - composeTestRule.onNodeWithLabel(KeyInterferenceHost1.retainedScopedStateLabel).performClick() - - composeTestRule - .onNodeWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - .assertHasCustomInfo(1) - composeTestRule - .onNodeWithLabel(KeyInterferenceHost2.retainedScopedStateLabel) - .assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_hostsRememberedBeforeCallingComposable_useCorrectRetainedComponent() { - ActivityScenario.launch(EmptyTestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - activity.setContent { - Column { - val host1 = rememberComponentHost(activity.keyInterferenceHost1Creator) - val host2 = rememberComponentHost(activity.keyInterferenceHost1Creator) - - host1.Composable() - host2.Composable() - } - } - } - - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[0] - .performClick() - - val nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(0) - } - } - - @Test - fun rememberComopnentHost_hostsCreatedWithDelegate_useCorrectRetainedComponent() { - ActivityScenario.launch(EmptyTestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - activity.setContent { - Column { - activity.Delegate { - val host1 = rememberComponentHost(activity.keyInterferenceHost1Creator) - host1.Composable() - } - - activity.Delegate { - val host2 = rememberComponentHost(activity.keyInterferenceHost1Creator) - host2.Composable() - } - } - } - } - - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[0] - .performClick() - - val nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_hostsCreatedByMethodCallingDelegate_useCorrectRetainedComponent() { - ActivityScenario.launch(EmptyTestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - activity.setContent { - Column { - activity.CallHost(activity.keyInterferenceHost1Creator) - activity.CallHost(activity.keyInterferenceHost1Creator) - } - } - } - - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[0] - .performClick() - - val nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_firstHostRemovedConditionally_useCorrectRetainedComponent() { - ActivityScenario.launch(EmptyTestActivity::class.java).use { scenario -> - val enableHostLabel = NodeLabel("EmptyKeyInterferenceTestActivity", "enableHost") - - scenario.onActivity { activity -> - activity.setContent { - Column { - val enableHost = remember { mutableStateOf(true) } - LabeledNode( - enableHostLabel, - Modifier.clickable { enableHost.value = !enableHost.value } - ) - - activity.ConditionalHost(enableHost.value, activity.keyInterferenceHost1Creator) - activity.ConditionalHost(true, activity.keyInterferenceHost1Creator) - } - } - } - - // Update each host's retained state so it's not the default value. - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[0] - .performClick() - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[1] - .performClick() - .performClick() - - var nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(2) - - // Disable the first host. This should remove its retained component, leaving only the second - composeTestRule.onNodeWithLabel(enableHostLabel).performClick() - composeTestRule - .onNodeWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - .assertHasCustomInfo(2) - - // Re-enable the first host. Since it was fully removed, doing so should create a new retained - // component and its retained state should be reset to 0 - composeTestRule.onNodeWithLabel(enableHostLabel).performClick() - - nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(0) - nodes[1].assertHasCustomInfo(2) - } - } - - @Test - fun rememberComponentHost_secondHostRemovedConditionally_useCorrectRetainedComponent() { - ActivityScenario.launch(EmptyTestActivity::class.java).use { scenario -> - val enableHostLabel = NodeLabel("EmptyKeyInterferenceTestActivity", "enableHost") - - scenario.onActivity { activity -> - activity.setContent { - Column { - val enableHost = remember { mutableStateOf(true) } - LabeledNode( - enableHostLabel, - Modifier.clickable { enableHost.value = !enableHost.value } - ) - - activity.ConditionalHost(true, activity.keyInterferenceHost1Creator) - activity.ConditionalHost(enableHost.value, activity.keyInterferenceHost1Creator) - } - } - } - - // Update each host's retained state so it's not the default value. - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[0] - .performClick() - composeTestRule - .onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel)[1] - .performClick() - .performClick() - - var nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(2) - - // Disable the second host. This should remove its retained component, leaving only the first - composeTestRule.onNodeWithLabel(enableHostLabel).performClick() - - composeTestRule - .onNodeWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - .assertHasCustomInfo(1) - - // Re-enable the second host. Since the it was fully removed, doing so should create a new - // retained component and its retained state should be reset to 0 - composeTestRule.onNodeWithLabel(enableHostLabel).performClick() - - nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_keyInterferenceWithRecreate_usesCorrectRetainedComponent() { - ActivityScenario.launch(KeyInterferenceTestActivity::class.java).use { - var nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].performClick() - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(0) - - it.recreate() - - nodes = composeTestRule.onAllNodesWithLabel(KeyInterferenceHost1.retainedScopedStateLabel) - nodes[0].assertHasCustomInfo(1) - nodes[1].assertHasCustomInfo(0) - } - } - - @Test - fun rememberComponentHost_onRecomposition_onClearedListenerNotCalled() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performScrollTo() - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - - scenario.onActivity { activity -> activity.triggerRecomposition() } - composeTestRule.onNodeWithLabel(TestActivity.recompositionCountLabel).assertHasCustomInfo(1) - - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - } - } - - @Test - fun rememberComponentHost_onActivityRecreate_onClearedListenerNotCalled() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performScrollTo() - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - - it.recreate() - - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - } - } - - @Test - fun rememberComponentHost_hostRemovedFromComposition_onClearedListenerIsCalled() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performScrollTo() - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - - scenario.onActivity { activity -> activity.setOnClearedComposeHostEnabled(false) } - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .assertDoesNotExist() - - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isTrue() - } - } - - @Test - fun rememberComponentHost_activityDestroyed_onClearedListenerIsCalled() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performScrollTo() - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - - scenario.moveToState(Lifecycle.State.DESTROYED) - - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isTrue() - } - } - - @Test - fun rememberComponentHost_onClearedCalled_noInterferenceBetweenHosts() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performScrollTo() - .performClick() - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker2.clickToResetLabel) - .performScrollTo() - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - assertThat(isOnClearedComposeHost2RetainedLifecycleCleared.get()).isFalse() - - scenario.onActivity { activity -> activity.setOnClearedComposeHostEnabled(false) } - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .assertDoesNotExist() - - // Since the first host is removed, its retained lifecycle should be cleared. The second host - // is not removed, so its retained lifecycle should not be cleared - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isTrue() - assertThat(isOnClearedComposeHost2RetainedLifecycleCleared.get()).isFalse() - } - } - - @Test - fun rememberComponentHost_compositionRemovedDuringRecreate_onClearedListenerIsCalled() { - ActivityScenario.launch(RecreateTestActivity::class.java).use { - // Enable the host as it's disabled by default - composeTestRule.onNodeWithLabel(RecreateTestActivity.enableHostLabel).performClick() - - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - - // Recreate the activity, which resets the enableHost value to false. As a result the host is - // removed from the composition. - it.recreate() - - // Since the host was removed from the composition, its retained lifecycle should be cleared - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isTrue() - } - } - - @Test - fun rememberComponentHost_lazyColumnHost_notClearedAfterRecreate() { - val defaultEnableHostIntent = - Intent(appContext, LazyColumnTestActivity::class.java).apply { - putExtra(LazyColumnTestActivity.enableHostIntentKey, true) - } - - ActivityScenario.launch(defaultEnableHostIntent).use { - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - - // Recreate the activity, this should restore the host's ComposeRetainedComponent - it.recreate() - - // Since the host wasn't removed from the composition, its retained lifecycle should not be - // cleared - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - } - } - - @Test - fun rememberComponentHost_lazyColumnCompositionRemovedDuringRecreate_callsOnClearedListener() { - ActivityScenario.launch(LazyColumnTestActivity::class.java).use { - // Enable the host as it's disabled by default - composeTestRule.onNodeWithLabel(LazyColumnTestActivity.enableHostLabel).performClick() - - composeTestRule - .onNodeWithLabel(RetainedComponentClearedTracker.clickToResetLabel) - .performClick() - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isFalse() - - // Recreate the activity, which resets the enableHost value to false. As a result the host is - // removed from the composition. - it.recreate() - - // Since the host was removed from the composition, its retained lifecycle should be cleared - assertThat(isOnClearedComposeHostRetainedLifecycleCleared.get()).isTrue() - } - } - - @Test - fun rememberComponentHost_withRememberSaveableVariable_variableIsRestored() { - ActivityScenario.launch(TestActivity::class.java).use { - composeTestRule.onNodeWithLabel(ComposeHost.rememberSaveableValueLabel).performClick() - composeTestRule.onNodeWithLabel(ComposeHost.rememberSaveableValueLabel).assertHasCustomInfo(1) - - it.recreate() - - composeTestRule.onNodeWithLabel(ComposeHost.rememberSaveableValueLabel).assertHasCustomInfo(1) - } - } - - @AndroidEntryPoint(ComponentActivity::class) - class TestActivity : Hilt_RememberComponentHostTest_TestActivity() { - private lateinit var viewModel: TestActivityViewModel - - @Inject lateinit var composeHostCreator: ComponentHostCreator - @Inject lateinit var composeHostCreator2: ComponentHostCreator - - @Inject lateinit var nestedComponentHost: ComponentHostCreator - - @Inject - lateinit var composeHostWithRetainedDepCreator: ComponentHostCreator - - @Inject - lateinit var composeHostWithRetainedDepsFromModulesCreator: - ComponentHostCreator - - @Inject - lateinit var duplicateInnerClassComposeHostCreator: - ComponentHostCreator - - @Inject lateinit var onClearedComposeHostCreator: ComponentHostCreator - - @Inject lateinit var onClearedComposeHost2Creator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - viewModel = ViewModelProvider(this)[TestActivityViewModel::class.java] - - setContent { - Column { - LabeledNode( - recompositionCountLabel, - Modifier.customInfo(viewModel.referenceMeToBeRecomposed) - ) - RecomposableContent(viewModel.referenceMeToBeRecomposed) - } - } - } - - /** - * referenceMeToBeRecomposed is accepted as a parameter so this function is recomposed when - * [triggerRecomposition] is called. - */ - @Composable - fun RecomposableContent(@Suppress("UNUSED_PARAMETER") referenceMeToBeRecomposed: Int) { - // There's enough items in this Column to push the later ones "off screen". This prevents - // Compose from being able to click on them, so we need the column to be scrollable so we can - // first scroll to the item and then click on it. - Column(Modifier.verticalScroll(rememberScrollState())) { - val creator = rememberComponentHost(composeHostCreator) - creator.Composable() - - val creator2 = rememberComponentHost(composeHostCreator2) - creator2.Composable() - - val nestedCreator = rememberComponentHost(nestedComponentHost) - nestedCreator.NestedComposable() - - if (viewModel.isComposeHostWithRetainedDepEnabled) { - val composeHostWithRetainedDep = rememberComponentHost(composeHostWithRetainedDepCreator) - composeHostWithRetainedDep.Composable() - } - - val composeHostWithRetainedDepFromModules = - rememberComponentHost(composeHostWithRetainedDepsFromModulesCreator) - composeHostWithRetainedDepFromModules.Composable() - - val duplicateInnerClassComposeHost = - rememberComponentHost(duplicateInnerClassComposeHostCreator) - duplicateInnerClassComposeHost.Composable() - - if (viewModel.isOnClearedComposeHostEnabled) { - val onClearedComposeHost = rememberComponentHost(onClearedComposeHostCreator) - onClearedComposeHost.Composable() - } - - val onClearedComposeHost2 = rememberComponentHost(onClearedComposeHost2Creator) - onClearedComposeHost2.Composable() - } - } - - internal fun triggerRecomposition() { - viewModel.referenceMeToBeRecomposed = viewModel.referenceMeToBeRecomposed + 1 - } - - internal fun setComposeHostWithRetainedDepEnabled(enabled: Boolean) { - viewModel.isComposeHostWithRetainedDepEnabled = enabled - } - - internal fun setOnClearedComposeHostEnabled(enabled: Boolean) { - viewModel.isOnClearedComposeHostEnabled = enabled - } - - @HiltViewModel - class TestActivityViewModel @Inject constructor() : ViewModel() { - var referenceMeToBeRecomposed by mutableStateOf(0) - var isComposeHostWithRetainedDepEnabled by mutableStateOf(true) - var isOnClearedComposeHostEnabled by mutableStateOf(true) - } - - companion object { - val recompositionCountLabel = NodeLabel("TestActivity", "recomposition count") - } - - @ComposeComponentHost - class NestedComponentHost @Inject constructor() : - Hilt_RememberComponentHostTest_TestActivity_NestedComponentHost() { - @Composable - fun NestedComposable() { - LabeledNode(nestedHostLabel) - } - - companion object { - val nestedHostLabel = NodeLabel("NestedComponentHost", "NestedHost") - } - } - } - - @AndroidEntryPoint(ComponentActivity::class) - class MultipleComposeViewActivity : Hilt_RememberComponentHostTest_MultipleComposeViewActivity() { - @Inject lateinit var hostWithRetainedDep: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView( - LinearLayout(this).apply { - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - addView(createComposeView(R.id.compose_view1)) - addView(createComposeView(R.id.compose_view2)) - } - ) - } - - private fun createComposeView(id: Int): ComposeView { - val composeView = ComposeView(this) - composeView.id = id - composeView.setContent { - val enableHost = remember { mutableStateOf(true) } - Text( - "click to toggle composeHostWithRetainedDep enabled", - Modifier.clickable { enableHost.value = !enableHost.value } - ) - - if (enableHost.value) { - val host = rememberComponentHost(hostWithRetainedDep) - host.Composable() - } - } - return composeView - } - } - - /** - * Empty Activity which just injects hosts. Tests should manually set the content on the activity: - * ```kotlin - * ActivityScenario.launch(KeyInterferenceTestActivity::class.java).use { scenario -> - * scenario.onActivity { activity -> - * activity.setContent {...} - * } - * - * // Perform test... - * } - * ``` - * - * Note that this will not work well with tests which need to recreate the Activity since the call - * to setContent is lost after the recreation. - */ - @AndroidEntryPoint(ComponentActivity::class) - class EmptyTestActivity : Hilt_RememberComponentHostTest_EmptyTestActivity() { - @Inject lateinit var keyInterferenceHost1Creator: ComponentHostCreator - @Inject lateinit var keyInterferenceHost2Creator: ComponentHostCreator - - @Composable fun Delegate(impl: @Composable () -> Unit) = impl() - - @Composable - fun CallHost(creator: ComponentHostCreator): Unit = Delegate { - val host = rememberComponentHost(creator) - host.Composable() - } - - @Composable - fun ConditionalHost(enable: Boolean, creator: ComponentHostCreator) { - if (enable) { - val host = rememberComponentHost(creator) - host.Composable() - } - } - } - - @AndroidEntryPoint(ComponentActivity::class) - class KeyInterferenceTestActivity : Hilt_RememberComponentHostTest_KeyInterferenceTestActivity() { - @Inject lateinit var keyInterferenceHost1Creator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - Column { - val host1 = rememberComponentHost(keyInterferenceHost1Creator) - host1.Composable() - - val host2 = rememberComponentHost(keyInterferenceHost1Creator) - host2.Composable() - } - } - } - } - - @AndroidEntryPoint(ComponentActivity::class) - class RecreateTestActivity : Hilt_RememberComponentHostTest_RecreateTestActivity() { - @Inject lateinit var onClearedHostCreator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - Column { - val enableHost = remember { mutableStateOf(false) } - - LabeledNode(enableHostLabel, Modifier.clickable { enableHost.value = !enableHost.value }) - - if (enableHost.value) { - val host = rememberComponentHost(onClearedHostCreator) - host.Composable() - } - } - } - } - - companion object { - val enableHostLabel = NodeLabel("RecreateTestActivity", "enableHost") - } - } - - @AndroidEntryPoint(ComponentActivity::class) - class LazyColumnTestActivity : Hilt_RememberComponentHostTest_LazyColumnTestActivity() { - @Inject lateinit var onClearedHostCreator: ComponentHostCreator - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val defaultEnableHost = intent.getBooleanExtra(enableHostIntentKey, false) - - setContent { - val enableHost = remember { mutableStateOf(defaultEnableHost) } - LazyColumn { - item { - LabeledNode( - enableHostLabel, - Modifier.clickable { enableHost.value = !enableHost.value } - ) - } - item { - if (enableHost.value) { - val host = rememberComponentHost(onClearedHostCreator) - host.Composable() - } - } - } - } - } - - companion object { - val enableHostLabel = NodeLabel("LazyColumnTestActivity", "enableHost") - - const val enableHostIntentKey = "enable_host" - } - } -} - -@ComposeComponentHost -class ComposeHost -@Inject -constructor( - typeBasedCounter: TypeBasedCounter, - private val dep: UnscopedDep, - private val dep2: UnscopedDep2, -) : Hilt_ComposeHost() { - private val instanceNum: Int = typeBasedCounter.getAndIncrementCountFor() - - @Composable - fun Composable() { - Column { - LabeledNode(hostLabel, Modifier.customInfo(instanceNum)) - - LabeledNode(dep1ScopedDepLabel, Modifier.customInfo(dep.scopedDep.instanceNum)) - - LabeledNode(dep2ScopedDepLabel, Modifier.customInfo(dep2.scopedDep.instanceNum)) - - var rememberSaveableValue by rememberSaveable { mutableIntStateOf(0) } - LabeledNode( - rememberSaveableValueLabel, - Modifier.clickable { ++rememberSaveableValue }.customInfo(rememberSaveableValue) - ) - } - } - - companion object { - val hostLabel = NodeLabel("ComposeHost", "Host") - val dep1ScopedDepLabel = NodeLabel("ComposeHost", "Dep1", "ScopedDep") - val dep2ScopedDepLabel = NodeLabel("ComposeHost", "Dep2", "ScopedDep") - val rememberSaveableValueLabel = NodeLabel("ComposeHost", "rememberSaveableValue") - } -} - -class UnscopedDep @Inject internal constructor(val scopedDep: ComposeScopedDep) - -class UnscopedDep2 @Inject internal constructor(val scopedDep: ComposeScopedDep) - -@ComposeScoped -class ComposeScopedDep @Inject internal constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum: Int = typeBasedCounter.getAndIncrementCountFor() -} - -@ComposeComponentHost -class ComposeHost2 @Inject constructor(private val scopedDep: ComposeScopedDep) : - Hilt_ComposeHost2() { - @Composable - fun Composable() { - LabeledNode(scopedDepLabel, Modifier.customInfo(scopedDep.instanceNum)) - } - - companion object { - val scopedDepLabel = NodeLabel("ComposeHost2", "ScopedDep") - } -} - -@ComposeComponentHost -class ComposeHostWithRetainedDep -@Inject -constructor( - private val usesRetainedDep: UsesRetainedDep, - private val usesRetainedDep2: UsesRetainedDep2 -) : Hilt_ComposeHostWithRetainedDep() { - - @Composable - fun Composable() { - Column { - LabeledNode(usesRetainedDepLabel, Modifier.customInfo(usesRetainedDep.instanceNum)) - - LabeledNode( - usesRetainedDepRetainedDepLabel, - Modifier.customInfo(usesRetainedDep.calculateRetainedDepInstanceNum()) - ) - - LabeledNode( - usesRetainedDep2RetainedDepLabel, - Modifier.customInfo(usesRetainedDep2.calculateRetainedDepInstanceNum()) - ) - } - } - - companion object { - val usesRetainedDepLabel = NodeLabel("ComposeHostWithRetainedDep", "UsesRetainedDep") - val usesRetainedDepRetainedDepLabel = - NodeLabel("ComposeHostWithRetainedDep", "UsesRetainedDep", "RetainedDep") - val usesRetainedDep2RetainedDepLabel = - NodeLabel("ComposeHostWithRetainedDep", "UsesRetainedDep2", "RetainedDep") - } -} - -@ComposeScoped -class UsesRetainedDep -@Inject -internal constructor(typeBasedCounter: TypeBasedCounter, private val retainedDep: RetainedDep) { - - val instanceNum = typeBasedCounter.getAndIncrementCountFor() - - fun calculateRetainedDepInstanceNum(): Int { - return retainedDep.instanceNum - } -} - -@ComposeScoped -class UsesRetainedDep2 @Inject internal constructor(private val retainedDep: RetainedDep) { - - fun calculateRetainedDepInstanceNum(): Int { - return retainedDep.instanceNum - } -} - -@ComposeRetainedScoped -@ComposeRetainedProvided -class RetainedDep @Inject constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum: Int = typeBasedCounter.getAndIncrementCountFor() -} - -@ComposeComponentHost -class ComposeHostWithRetainedDepsFromModules -@Inject -constructor( - private val nonQualifiedType: NonQualifiedType, - private val parameterizedType: ParameterizedProvidedType, - private val primitiveType: Int, - private val providerPrimitiveType: Provider, - @FirstType private val moduleFirstType: FirstTypeClass, - @SecondType private val moduleSecondType: SecondTypeClass, - @ByProvides private val providedDep: ProvidedRetainedDep, - @ByBinds private val boundDep: BoundRetainedDep, - @Named("NamedRetainedDep") private val namedDep: NamedRetainedDep, - @Named("OtherNamedRetainedDep") private val otherNamedDep: NamedRetainedDep, - private val nullableDep: NullableRetainedDep?, -) : Hilt_ComposeHostWithRetainedDepsFromModules() { - - @Composable - fun Composable() { - Column { - LabeledNode(nonQualifiedTypeDepLabel, Modifier.customInfo(nonQualifiedType.instanceNum)) - - LabeledNode(parameterizedTypeDepLabel, Modifier.customInfo(parameterizedType.instanceNum)) - - LabeledNode(primitiveTypeDepLabel, Modifier.customInfo(primitiveType)) - - LabeledNode(providerPrimitiveTypeDepLabel, Modifier.customInfo(providerPrimitiveType.get())) - - LabeledNode(moduleFirstTypeDepLabel, Modifier.customInfo(moduleFirstType.instanceNum)) - - LabeledNode(moduleSecondTypeDepLabel, Modifier.customInfo(moduleSecondType.instanceNum)) - - LabeledNode(providedRetainedDepLabel, Modifier.customInfo(providedDep.instanceNum)) - - LabeledNode(boundRetainedDepLabel, Modifier.customInfo(boundDep.instanceNum)) - - LabeledNode(namedRetainedDepLabel, Modifier.customInfo(namedDep.instanceNum)) - - LabeledNode(otherNamedRetainedDepLabel, Modifier.customInfo(otherNamedDep.instanceNum)) - - LabeledNode(nullableRetainedDepLabel, Modifier.customInfo(nullableDep)) - } - } - - companion object { - val nonQualifiedTypeDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "NonQualifiedType") - val parameterizedTypeDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "ParameterizedProvidedType") - val primitiveTypeDepLabel = NodeLabel("ComposeHostWithQualifiedRetainedDeps", "Integer") - val providerPrimitiveTypeDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "Provider") - val moduleFirstTypeDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "@FirstType FirstTypeClass") - val moduleSecondTypeDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "@SecondType SecondTypeClass") - val providedRetainedDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "@ByProvides ProvidedRetainedDep") - val boundRetainedDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "@ByBinds BoundRetainedDep") - val namedRetainedDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "@Named(NamedRetainedDep) NamedRetainedDep") - val otherNamedRetainedDepLabel = - NodeLabel( - "ComposeHostWithQualifiedRetainedDeps", - "@Named(OtherNamedRetainedDep) NamedRetainedDep" - ) - val nullableRetainedDepLabel = - NodeLabel("ComposeHostWithQualifiedRetainedDeps", "NullableRetainedDep") - } -} - -class NonQualifiedType(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object ProvidesNonQualifiedRetainedDepModule { - - @Provides - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideNonQualifiedDep(typeBasedCounter: TypeBasedCounter): NonQualifiedType { - return NonQualifiedType(typeBasedCounter) - } -} - -class ParameterizedProvidedType(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor>() -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object ProvidesParameterizedRetainedDepTypeModule { - - @Provides - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideParameterizedType( - typeBasedCounter: TypeBasedCounter - ): ParameterizedProvidedType { - return ParameterizedProvidedType(typeBasedCounter) - } -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object ProvidesPrimitiveRetainedDepTypeModule { - - @Provides @ComposeRetainedScoped @ComposeRetainedProvided fun providePrimitiveType(): Int = 5 -} - -@Qualifier @Retention(AnnotationRetention.BINARY) annotation class FirstType - -@Qualifier @Retention(AnnotationRetention.BINARY) annotation class SecondType - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object ProvidesMultipleRetainedDepsModule { - - @Provides - @FirstType - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideFirstType(typeBasedCounter: TypeBasedCounter) = FirstTypeClass(typeBasedCounter) - - @Provides - @SecondType - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideSecondType(typeBasedCounter: TypeBasedCounter) = SecondTypeClass(typeBasedCounter) -} - -class FirstTypeClass constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -class SecondTypeClass constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object ProvidesProvidedRetainedDepModule { - @Provides - @ByProvides - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideProvidedRetainedDep(typeBasedCounter: TypeBasedCounter): ProvidedRetainedDep { - return ProvidedRetainedDep(typeBasedCounter) - } -} - -@Qualifier @Retention(AnnotationRetention.BINARY) annotation class ByProvides - -class ProvidedRetainedDep @Inject constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal abstract class BindsBoundRetainedDepModule { - - @Binds - @ByBinds - @ComposeRetainedScoped - @ComposeRetainedProvided - abstract fun bindBoundRetainedDep(instance: BoundRetainedDep): BoundRetainedDep -} - -@Qualifier @Retention(AnnotationRetention.BINARY) annotation class ByBinds - -@ComposeRetainedScoped -class BoundRetainedDep @Inject constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object NamedRetainedDepModule { - @Provides - @Named("NamedRetainedDep") - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideNamedRetainedDep(typeBasedCounter: TypeBasedCounter): NamedRetainedDep { - return NamedRetainedDep(typeBasedCounter) - } -} - -class NamedRetainedDep(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object NamedRetainedDepModuleWithSameProvidesMethodName { - @Provides - @Named("OtherNamedRetainedDep") - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideNamedRetainedDep(typeBasedCounter: TypeBasedCounter): NamedRetainedDep { - return NamedRetainedDep(typeBasedCounter) - } -} - -@Module -@InstallIn(ComposeRetainedComponent::class) -internal object NullableRetainedDepModule { - - @Provides - @Nullable - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provideNullableType(): NullableRetainedDep? = null -} - -@ComposeComponentHost -class DuplicateInnerClassComposeHost -@Inject -constructor( - private val outerClass1InnerClass: OuterClass1.InnerClass, - private val outerClass2InnerClass: OuterClass2.InnerClass, - private val outerClass1ModuleProvidedType: OuterClass1InnerModuleProvidedType, - private val outerClass2ModuleProvidedType: OuterClass2InnerModuleProvidedType, -) : Hilt_DuplicateInnerClassComposeHost() { - @Composable - fun Composable() { - Column { - LabeledNode( - outerClass1InnerClassLabel, - Modifier.customInfo(outerClass1InnerClass.instanceNum) - ) - LabeledNode( - outerClass2InnerClassLabel, - Modifier.customInfo(outerClass2InnerClass.instanceNum) - ) - LabeledNode( - outerClass1ModuleProvidedTypeLabel, - Modifier.customInfo(outerClass1ModuleProvidedType.instanceNum) - ) - LabeledNode( - outerClass2ModuleProvidedTypeLabel, - Modifier.customInfo(outerClass2ModuleProvidedType.instanceNum) - ) - } - } - - companion object { - val outerClass1InnerClassLabel = - NodeLabel("DuplicateInnerClassComposeHost", "outerClass1InnerClass") - val outerClass2InnerClassLabel = - NodeLabel("DuplicateInnerClassComposeHost", "outerClass2InnerClass") - val outerClass1ModuleProvidedTypeLabel = - NodeLabel("DuplicateInnerClassComposeHost", "outerClass1ModuleProvidedType") - val outerClass2ModuleProvidedTypeLabel = - NodeLabel("DuplicateInnerClassComposeHost", "outerClass2ModuleProvidedType") - } -} - -class NullableRetainedDep - -class OuterClass1 { - @ComposeRetainedScoped - @ComposeRetainedProvided - class InnerClass @Inject constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() - } - - @Module - @InstallIn(ComposeRetainedComponent::class) - internal object InnerModule { - @Provides - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provide(typeBasedCounter: TypeBasedCounter) = - OuterClass1InnerModuleProvidedType(typeBasedCounter) - } -} - -class OuterClass2 { - @ComposeRetainedScoped - @ComposeRetainedProvided - class InnerClass @Inject constructor(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() - } - - @Module - @InstallIn(ComposeRetainedComponent::class) - internal object InnerModule { - @Provides - @ComposeRetainedScoped - @ComposeRetainedProvided - fun provide(typeBasedCounter: TypeBasedCounter) = - OuterClass2InnerModuleProvidedType(typeBasedCounter) - } -} - -class OuterClass1InnerModuleProvidedType(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -class OuterClass2InnerModuleProvidedType(typeBasedCounter: TypeBasedCounter) { - val instanceNum = typeBasedCounter.getAndIncrementCountFor() -} - -@ComposeComponentHost -class OnClearedComposeHost -@Inject -constructor(private val retainedComponentClearedTracker: RetainedComponentClearedTracker) : - Hilt_OnClearedComposeHost() { - - @Composable - fun Composable() { - retainedComponentClearedTracker.Content() - } - - @Qualifier annotation class RetainedLifecycleCleared -} - -@ComposeComponentHost -class OnClearedComposeHost2 -@Inject -constructor(private val retainedComponentClearedTracker: RetainedComponentClearedTracker2) : - Hilt_OnClearedComposeHost2() { - - @Composable - fun Composable() { - retainedComponentClearedTracker.Content() - } - - @Qualifier annotation class RetainedLifecycleCleared -} - -@ComposeRetainedScoped -@ComposeRetainedProvided -class RetainedComponentClearedTracker -@Inject -internal constructor( - composeRetainedLifecycle: ComposeRetainedLifecycle, - @OnClearedComposeHost.RetainedLifecycleCleared - private val retainedLifecycleCleared: AtomicBoolean, -) { - - init { - composeRetainedLifecycle.addOnClearedListener { retainedLifecycleCleared.set(true) } - } - - @Composable - fun Content() { - LabeledNode(clickToResetLabel, Modifier.clickable { retainedLifecycleCleared.set(false) }) - } - - companion object { - val clickToResetLabel = - NodeLabel("OnClearedHost", "RetainedComponentClearedTracker", "clickToReset") - } -} - -@ComposeRetainedScoped -@ComposeRetainedProvided -class RetainedComponentClearedTracker2 -@Inject -internal constructor( - composeRetainedLifecycle: ComposeRetainedLifecycle, - @OnClearedComposeHost2.RetainedLifecycleCleared - private val retainedLifecycleCleared: AtomicBoolean, -) { - - init { - composeRetainedLifecycle.addOnClearedListener { retainedLifecycleCleared.set(true) } - } - - @Composable - fun Content() { - LabeledNode(clickToResetLabel, Modifier.clickable { retainedLifecycleCleared.set(false) }) - } - - companion object { - val clickToResetLabel = - NodeLabel("OnClearedHost2", "RetainedComponentClearedTracker2", "clickToReset") - } -} - -interface HasComposable { - @Composable fun Composable() -} - -@ComposeComponentHost -class KeyInterferenceHost1 -@Inject -constructor(private val retainedScopedDep: KeyInterferenceRetainedState) : - Hilt_KeyInterferenceHost1(), HasComposable { - - @Composable - override fun Composable() { - LabeledNode( - retainedScopedStateLabel, - modifier = - Modifier.clickable { retainedScopedDep.value++ }.customInfo(retainedScopedDep.value) - ) - } - - companion object { - val retainedScopedStateLabel = - NodeLabel("KeyInterferenceHost1", "KeyInterferenceRetainedScoped.state") - } -} - -@ComposeComponentHost -class KeyInterferenceHost2 -@Inject -constructor(private val retainedScopedDep: KeyInterferenceRetainedState) : - Hilt_KeyInterferenceHost2(), HasComposable { - @Composable - override fun Composable() { - LabeledNode( - retainedScopedStateLabel, - Modifier.clickable { retainedScopedDep.value++ }.customInfo(retainedScopedDep.value), - ) - } - - companion object { - val retainedScopedStateLabel = - NodeLabel("KeyInterferenceHost2", "KeyInterferenceRetainedScoped.state") - } -} - -@ComposeRetainedScoped -@ComposeRetainedProvided -class KeyInterferenceRetainedState @Inject constructor() { - var value by mutableStateOf(0) -} - -/** - * Used to store a count which is unique to a given type. - * - * For example, this can be used to track how many instances have been created for a given type: - * during initialization of the type call [getAndIncrementCountFor] to count the newly created - * instance: - * ``` - * class MyType @Inject constructor(typeBasedCounter: TypeBasedCounter) { - * val instanceNum = typeBasedCounter.getAndIncrementCountFor() - * } - * ``` - */ -@Singleton -class TypeBasedCounter @Inject constructor() { - val classToInstanceCount = mutableMapOf, Int>() - - inline fun getAndIncrementCountFor(): Int { - val instanceCount = classToInstanceCount.getOrPut(T::class.java) { 0 } - classToInstanceCount[T::class.java] = instanceCount + 1 - return instanceCount - } -} diff --git a/javatests/dagger/hilt/android/compose/RememberComponentHostTest_AndroidManifest.xml b/javatests/dagger/hilt/android/compose/RememberComponentHostTest_AndroidManifest.xml deleted file mode 100644 index f5fe96e01d2..00000000000 --- a/javatests/dagger/hilt/android/compose/RememberComponentHostTest_AndroidManifest.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/javatests/dagger/hilt/android/compose/res/values/ids.xml b/javatests/dagger/hilt/android/compose/res/values/ids.xml deleted file mode 100644 index 0d179796c59..00000000000 --- a/javatests/dagger/hilt/android/compose/res/values/ids.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD b/javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD deleted file mode 100644 index 2869db81181..00000000000 --- a/javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Tests for the @dagger.hilt.android.compose.ComposeComponentHost annotation -# processor. - -load("//java/dagger/testing/compile:macros.bzl", "kt_compiler_test") - -package(default_visibility = ["//:src"]) - -kt_compiler_test( - name = "ComposeComponentHostProcessorTest", - srcs = ["ComposeComponentHostProcessorTest.kt"], - compiler_deps = [ - "//java/dagger/hilt/android/compose:compose_component_host", - "//third_party/java/jsr330_inject", - "//third_party/java/androidx/compose/runtime", - ], - deps = [ - "//:dagger_with_compiler/hilt/testing:compiler", - "//java/dagger/hilt/android/processor/internal/compose/composecomponenthost:processor_lib", - "//java/dagger/internal/codegen/xprocessing", - "//java/dagger/internal/codegen/xprocessing:xprocessing-testing", - "//third_party/java/compile_testing", - "//third_party/java/junit", - "//third_party/java/truth", - ], -) diff --git a/javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessorTest.kt b/javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessorTest.kt deleted file mode 100644 index 6fca3671f93..00000000000 --- a/javatests/dagger/hilt/android/processor/internal/compose/composecomponenthost/ComposeComponentHostProcessorTest.kt +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composecomponenthost - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.util.Source -import com.google.common.truth.Truth.assertThat -import dagger.hilt.android.testing.compile.HiltCompilerTests -import javax.tools.Diagnostic -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -@ExperimentalProcessingApi -@RunWith(JUnit4::class) -class ComposeComponentHostProcessorTest { - - @get:Rule val tempFolderRule = TemporaryFolder() - - @Test - fun compile_noInjectConstructor_failsCompilation() { - val hostFile = - Source.java( - "$TEST_PACKAGE.Host1", - """ - package $TEST_PACKAGE; - - import androidx.compose.runtime.Composable; - import dagger.hilt.android.compose.ComposeComponentHost; - - @ComposeComponentHost - final class Host1 extends Hilt_Host1 { - - Host1() {} - - @Composable - public void composable() {} - } - """ - .trimIndent() - ) - - kspCompiler(hostFile).compile() { subject -> - subject.hasErrorContaining( - "@ComposeComponentHost annotated classes should have an @Inject constructor" - ) - } - } - - @Test - fun compile_hasNoComposableFunctions_failsCompilation() { - val hostFile = - Source.java( - "$TEST_PACKAGE.Host1", - """ - package $TEST_PACKAGE; - - import dagger.hilt.android.compose.ComposeComponentHost; - import javax.inject.Inject; - - @ComposeComponentHost - final class Host1 extends Hilt_Host1 { - - @Inject - Host1() {} - - public void nonComposable() {} - } - """ - .trimIndent() - ) - - kspCompiler(hostFile).compile() { subject -> - subject.hasErrorContaining( - "@ComposeComponentHost annotated classes should have at least one @Composable " + - "function. Use a regular class with an @Inject constructor for classes without " + - "composables." - ) - } - } - - @Test - fun compile_noSuperType_failsCompilation() { - val hostFile = - Source.java( - "$TEST_PACKAGE.Host1", - """ - package $TEST_PACKAGE; - - import androidx.compose.runtime.Composable; - import dagger.hilt.android.compose.ComposeComponentHost; - import javax.inject.Inject; - - @ComposeComponentHost - final class Host1 { - - @Inject - Host1() {} - - @Composable - public void Composable() {} - } - """ - .trimIndent() - ) - - // TODO(b/288210593): Change this to KSP once this bug is fixed - HiltCompilerTests.compileWithKapt( - listOf(hostFile), - listOf(ComposeComponentHostProcessor()), - tempFolderRule - ) { result -> - assertThat(result.success).isFalse() - - val errors = result.diagnostics[Diagnostic.Kind.ERROR] - val expectedError = - "@ComposeComponentHost annotated class expected to extend Hilt_Host1. Found: Object" - val foundError = errors?.filter { it.msg.contains(expectedError) }?.firstOrNull() - assertThat(foundError).isNotNull() - } - } - - @Test - fun compile_wrongSuperType_failsCompilation() { - val hostSuperClass = - Source.java( - "$TEST_PACKAGE.HostSuperClass", - """ - package $TEST_PACKAGE; - - class HostSuperClass {} - """ - .trimIndent() - ) - - val hostFile = - Source.java( - "$TEST_PACKAGE.Host1", - """ - package $TEST_PACKAGE; - - import androidx.compose.runtime.Composable; - import dagger.hilt.android.compose.ComposeComponentHost; - import javax.inject.Inject; - - @ComposeComponentHost - final class Host1 extends HostSuperClass { - - @Inject - Host1() {} - - @Composable - public void Composable() {} - } - """ - .trimIndent() - ) - - // TODO(b/288210593): Change this to KSP once this bug is fixed - HiltCompilerTests.compileWithKapt( - listOf(hostSuperClass, hostFile), - listOf(ComposeComponentHostProcessor()), - tempFolderRule - ) { result -> - assertThat(result.success).isFalse() - - val errors = result.diagnostics[Diagnostic.Kind.ERROR] - val expectedError = - "@ComposeComponentHost annotated class expected to extend Hilt_Host1. Found: HostSuperClass" - val foundError = errors?.filter { it.msg.contains(expectedError) }?.firstOrNull() - assertThat(foundError).isNotNull() - } - } - - @Test - fun compile_ksp_correctHost_succeedsCompilation() { - val hostFile = - Source.java( - "$TEST_PACKAGE.Host1", - """ - package $TEST_PACKAGE; - - import androidx.compose.runtime.Composable; - import dagger.hilt.android.compose.ComposeComponentHost; - import javax.inject.Inject; - - @ComposeComponentHost - final class Host1 extends Hilt_Host1 { - - @Inject - Host1() {} - - @Composable - public void Composable() {} - } - """ - .trimIndent() - ) - - kspCompiler(hostFile).compile { subject -> subject.hasErrorCount(0) } - } - - @Test - fun compile_kapt_correctHost_succeedsCompilation() { - val hostFile = - Source.java( - "$TEST_PACKAGE.Host1", - """ - package $TEST_PACKAGE; - - import androidx.compose.runtime.Composable; - import dagger.hilt.android.compose.ComposeComponentHost; - import javax.inject.Inject; - - @ComposeComponentHost - final class Host1 extends Hilt_Host1 { - - @Inject - Host1() {} - - @Composable - public void Composable() {} - } - """ - .trimIndent() - ) - - // TODO(b/288210593): Change this to KSP once this bug is fixed - HiltCompilerTests.compileWithKapt( - listOf(hostFile), - listOf(ComposeComponentHostProcessor()), - tempFolderRule - ) { result -> - assertThat(result.success).isTrue() - } - } -} - -private fun kspCompiler( - firstSource: Source, - vararg additionalSources: Source -): HiltCompilerTests.HiltCompiler = - HiltCompilerTests.hiltCompiler(firstSource, *additionalSources) - .withAdditionalJavacProcessors(ComposeComponentHostProcessor()) - .withAdditionalKspProcessors(KspComposeComponentHostProcessor.Provider()) - -private const val TEST_PACKAGE = "dagger.hilt.android.processor.internal.compose.testing" diff --git a/javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD b/javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD deleted file mode 100644 index 6f37c44a562..00000000000 --- a/javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2023 The Dagger Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Description: -# Tests for the @dagger.hilt.android.compose.ComposeRetainedProvided -# annotation processor. - -load("//java/dagger/testing/compile:macros.bzl", "kt_compiler_test") - -package(default_visibility = ["//:src"]) - -kt_compiler_test( - name = "ComposeRetainedProvidedProcessorTest", - srcs = ["ComposeRetainedProvidedProcessorTest.kt"], - compiler_deps = [ - "//java/dagger/hilt/android/compose:compose_retained_provided", - "//third_party/java/jsr330_inject", - "//java/dagger/hilt/android/compose/scopes", - "//java/dagger/hilt/components", - ], - deps = [ - "//:dagger_with_compiler/hilt/testing:compiler", - "//java/dagger/hilt/android/processor/internal/compose/composeretainedprovided:processor_lib", - "//java/dagger/internal/codegen/xprocessing", - "//java/dagger/internal/codegen/xprocessing:xprocessing-testing", - "//third_party/java/compile_testing", - "//third_party/java/junit", - ], -) diff --git a/javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessorTest.kt b/javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessorTest.kt deleted file mode 100644 index f4b930c25af..00000000000 --- a/javatests/dagger/hilt/android/processor/internal/compose/composeretainedprovided/ComposeRetainedProvidedProcessorTest.kt +++ /dev/null @@ -1,715 +0,0 @@ -/* - * Copyright (C) 2023 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.android.processor.internal.compose.composeretainedprovided - -import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.XProcessingEnv -import dagger.hilt.android.testing.compile.HiltCompilerTests -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -@ExperimentalProcessingApi -@RunWith(JUnit4::class) -class ComposeRetainedProvidedProcessorTest { - - @Test - fun compile_composeRetainedProvidedOnInterface_failsCompilation() { - val providedType = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.InterfaceProvidedType", - """ - package $TEST_PACKAGE; - - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - - @ComposeRetainedScoped - @ComposeRetainedProvided - interface InterfaceProvidedType { - void method(); - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(providedType) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided can only be used with method and class types:" - ) - subject.hasErrorContaining("$TEST_PACKAGE.InterfaceProvidedType") - } - } - - @Test - fun compile_noInjectConstructor_failsCompilation() { - val providedType = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.NoInjectConstructor", - """ - package $TEST_PACKAGE; - - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - - @ComposeRetainedScoped - @ComposeRetainedProvided - class NoInjectConstructor { - NoInjectConstructor() {} - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(providedType) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes should have an @Inject constructor" - ) - subject.hasErrorContaining("$TEST_PACKAGE.NoInjectConstructor") - } - } - - @Test - fun compile_unscopedProvidedType_failsCompilation() { - val providedType = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.UnScopedProvidedType", - """ - package $TEST_PACKAGE; - - import dagger.hilt.android.compose.ComposeRetainedProvided; - import javax.inject.Inject; - - @ComposeRetainedProvided - class UnScopedProvidedType { - - @Inject - UnScopedProvidedType() {} - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(providedType) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes and methods must be annotated " + - "@ComposeRetainedScoped:" - ) - subject.hasErrorContaining("$TEST_PACKAGE.UnScopedProvidedType") - } - } - - @Test - fun compile_activityRetainedScopedProvidedType_failsCompilation() { - val providedType = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ActivityRetainedScopedProvidedType", - """ - package $TEST_PACKAGE; - - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.scopes.ActivityRetainedScoped; - import javax.inject.Inject; - - @ActivityRetainedScoped - @ComposeRetainedProvided - class ActivityRetainedScopedProvidedType { - - @Inject - ActivityRetainedScopedProvidedType() {} - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(providedType) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes and methods can only be annotated " + - "@ComposeRetainedScoped:" - ) - subject.hasErrorContaining( - "@ActivityRetainedScoped $TEST_PACKAGE.ActivityRetainedScopedProvidedType" - ) - } - } - - @Test - fun compile_moduleNotInstallInRetainedComponent_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.InstallInSingletonComponentModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - import dagger.hilt.components.SingletonComponent; - import dagger.hilt.InstallIn; - - @Module - @InstallIn(SingletonComponent.class) - class InstallInSingletonComponentModule { - - @Provides - @ComposeRetainedScoped - @ComposeRetainedProvided - NonInjectableType method() { - return new NonInjectableType(); - } - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated methods must be inside " + - "@InstallIn(ComposeRetainedComponent.class) annotated classes:" - ) - subject.hasErrorContaining("$TEST_PACKAGE.InstallInSingletonComponentModule") - } - } - - @Test - fun compile_providesMethodWithoutReturn_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - - @Provides - @ComposeRetainedScoped - @ComposeRetainedProvided - static void provideType() {} - } - """.trimIndent() - ) - - // Other Dagger processors verify this check, but ComposeRetainedProvidedMetadata relies on - // there being a return type, so it's tested here. - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining("@Provides methods must return a value") - } - } - - @Test - fun compile_providedTypeNotFromProvidesOrBoundMethod_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - - @ComposeRetainedScoped - @ComposeRetainedProvided - static NonInjectableType provideType() { - return new NonInjectableType(); - } - } - """.trimIndent() - ) - - - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated methods must also be annotated @Provides or @Binds:" - ) - subject.hasErrorContaining("provideType()") - } - } - - @Test - fun compile_providesIntoSetMethod_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - import dagger.multibindings.IntoSet; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Provides - @IntoSet - @ComposeRetainedScoped - @ComposeRetainedProvided - static NonInjectableType provideType() { - return new NonInjectableType(); - } - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "Multibindings are not supported for @ComposeRetainedProvided annotated methods:" - ) - subject.hasErrorContaining("provideType()") - } - } - - @Test - fun compile_providesIntoMapMethod_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - import dagger.multibindings.IntoMap; - import dagger.multibindings.StringKey; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Provides - @IntoMap - @StringKey("key") - @ComposeRetainedScoped - @ComposeRetainedProvided - static NonInjectableType provideType() { - return new NonInjectableType(); - } - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "Multibindings are not supported for @ComposeRetainedProvided annotated methods:" - ) - subject.hasErrorContaining("provideType()") - } - } - - @Test - fun compile_providesElementsIntoMethod_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - import dagger.multibindings.ElementsIntoSet; - import java.util.Set; - import java.util.HashSet; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Provides - @ElementsIntoSet - @ComposeRetainedScoped - @ComposeRetainedProvided - static Set provideType() { - Set elements = new HashSet<>(); - elements.add(new NonInjectableType()); - return elements; - } - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "Multibindings are not supported for @ComposeRetainedProvided annotated methods:" - ) - subject.hasErrorContaining("provideType()") - } - } - - @Test - fun compile_providesMethodNotRetainedScoped_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Provides - @ComposeRetainedProvided - static NonInjectableType provideType() { - return new NonInjectableType(); - } - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes and methods must be annotated " + - "@ComposeRetainedScoped:" - ) - subject.hasErrorContaining("provideType()") - } - } - - @Test - fun compile_providesMethodActivityRetainedScoped_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.scopes.ActivityRetainedScoped; - - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Provides - @ActivityRetainedScoped - @ComposeRetainedProvided - static NonInjectableType provideType() { - return new NonInjectableType(); - } - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(notInjectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes and methods can only be annotated " + - "@ComposeRetainedScoped:" - ) - subject.hasErrorContaining("@ActivityRetainedScoped provideType()") - } - } - - @Test - fun compile_bindsMethodNotRetainedScoped_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Binds; - import dagger.Module; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import javax.inject.Named; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Binds - @Named("name") - @ComposeRetainedProvided - abstract InjectableType bindType(InjectableType type); - } - """.trimIndent() - ) - - HiltCompilerTests.hiltCompiler(injectType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes and methods must be annotated " + - "@ComposeRetainedScoped:" - ) - subject.hasErrorContaining("bindType($TEST_PACKAGE.InjectableType)") - } - } - - @Test - fun compile_providesMethodUnScopedReturnedTypeIsRetainedScoped_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Provides - @ComposeRetainedProvided - static ScopedProvidedType provideType() { - return new ScopedProvidedType(); - } - } - """.trimIndent() - ) - - // Even though the underlying ScopedProvidedType is @ComposeRetainedScoped, the Module provides - // a different instance, so the method still needs to be scoped. - HiltCompilerTests.hiltCompiler(retainedScopedProvidedType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes and methods must be annotated " + - "@ComposeRetainedScoped:" - ) - subject.hasErrorContaining("provideType()") - } - } - - @Test - fun compile_bindsMethodReturnedTypeIsRetainedScoped_failsCompilation() { - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Binds; - import dagger.Module; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import javax.inject.Named; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Binds - @Named("name") - @ComposeRetainedProvided - abstract ScopedProvidedType bindType(ScopedProvidedType type); - } - """.trimIndent() - ) - - // Even though the underlying ScopedProvidedType is @ComposeRetainedScoped, the Module binds - // a different instance, so the method still needs to be scoped - HiltCompilerTests.hiltCompiler(retainedScopedProvidedType(), module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(1) - subject.hasErrorContaining( - "@ComposeRetainedProvided annotated classes and methods must be annotated " + - "@ComposeRetainedScoped:" - ) - subject.hasErrorContaining("bindType($TEST_PACKAGE.ScopedProvidedType)") - } - } - - @Test - fun compile_provideTypeHasMultipleQualifiers_failsCompilation() { - val blueQualifier = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.Blue", - """ - package $TEST_PACKAGE; - - import javax.inject.Qualifier; - - @Qualifier - @interface Blue {} - """.trimIndent() - ) - - val redQualifier = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.Red", - """ - package $TEST_PACKAGE; - - import javax.inject.Qualifier; - - @Qualifier - @interface Red {} - """.trimIndent() - ) - - val module = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ProvidedTypeModule", - """ - package $TEST_PACKAGE; - - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.compose.components.ComposeRetainedComponent; - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - - @InstallIn(ComposeRetainedComponent.class) - @Module - abstract class ProvidedTypeModule { - @Provides - @Blue - @Red - @ComposeRetainedScoped - @ComposeRetainedProvided - static NonInjectableType provideType() { - return new NonInjectableType(); - } - } - """.trimIndent() - ) - - // Other Dagger processors verify this check, but ComposeRetainedProvidedMetadata relies on - // there only being one qualifier, so it's tested here. - HiltCompilerTests.hiltCompiler(notInjectType(), blueQualifier, redQualifier, module) - .withAdditionalJavacProcessors(ComposeRetainedProvidedProcessor()) - .withAdditionalKspProcessors(KspComposeRetainedProvidedProcessor.Provider()) - .compile() { subject -> - subject.hasErrorCount(2) - subject.hasErrorContaining("@Provides methods may not use more than one @Qualifier") - .onSource(module) - .onLine(14) - subject.hasErrorContaining("@Provides methods may not use more than one @Qualifier") - .onSource(module) - .onLine(15) - } - } - - private fun notInjectType() = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.NonInjectableType", - """ - package $TEST_PACKAGE; - - class NonInjectableType {} - """.trimIndent() - ) - - private fun injectType() = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.InjectableType", - """ - package $TEST_PACKAGE; - - import javax.inject.Inject; - - class InjectableType { - - @Inject - InjectableType() {} - } - """.trimIndent() - ) - - private fun retainedScopedProvidedType() = HiltCompilerTests.javaSource( - "$TEST_PACKAGE.ScopedProvidedType", - """ - package $TEST_PACKAGE; - - import dagger.hilt.android.compose.ComposeRetainedProvided; - import dagger.hilt.android.compose.scopes.ComposeRetainedScoped; - import javax.inject.Inject; - - @ComposeRetainedScoped - @ComposeRetainedProvided - class ScopedProvidedType { - - @Inject - ScopedProvidedType() {} - } - """.trimIndent() - ) -} - -private const val TEST_PACKAGE = "dagger.hilt.android.processor.internal.compose.testing"