diff --git a/sample/app/build.gradle b/sample/app/build.gradle index 78c475cb..52eed521 100644 --- a/sample/app/build.gradle +++ b/sample/app/build.gradle @@ -12,7 +12,7 @@ plugins { // This extension comes from our published plugin. appPlatform { enableComposeUi true - enableKotlinInject true + enableMetro true enableModuleStructure true enableMoleculePresenters true addImplModuleDependencies true diff --git a/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppComponent.kt b/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppComponent.kt deleted file mode 100644 index 54411d79..00000000 --- a/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppComponent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package software.amazon.app.platform.sample - -import android.app.Application -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn - -/** kotlin-inject component that is used in instrumented tests. */ -@Component -@MergeComponent(AppScope::class) -@SingleIn(AppScope::class) -abstract class TestAndroidAppComponent( - @get:Provides val application: Application, - @get:Provides val rootScopeProvider: RootScopeProvider, -) : TestAndroidAppComponentMerged diff --git a/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppGraph.kt b/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppGraph.kt new file mode 100644 index 00000000..fe521e80 --- /dev/null +++ b/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppGraph.kt @@ -0,0 +1,24 @@ +package software.amazon.app.platform.sample + +import android.app.Application +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Provides +import software.amazon.app.platform.scope.RootScopeProvider + +/** Metro graph that is used in instrumented tests. */ +@DependencyGraph(AppScope::class) +interface TestAndroidAppGraph { + /** The factory to create a new instance of [AndroidAppGraph]. */ + @DependencyGraph.Factory + fun interface Factory { + /** + * Creates a new [AndroidAppGraph] instance. [application] and [rootScopeProvider] are provided + * in the [AndroidAppGraph] and can be injected. + */ + fun create( + @Provides application: Application, + @Provides rootScopeProvider: RootScopeProvider, + ): TestAndroidAppGraph + } +} diff --git a/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidApplication.kt b/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidApplication.kt index 3c81492c..ea17d366 100644 --- a/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidApplication.kt +++ b/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidApplication.kt @@ -1,11 +1,13 @@ package software.amazon.app.platform.sample +import dev.zacsweers.metro.createGraphFactory + /** * Application class that is used in instrumented tests. Note that it provides a * [TestAndroidApplication] instead of [AndroidApplication]. */ class TestAndroidApplication : AndroidApplication() { - override fun component(demoApplication: DemoApplication): AppComponent { - return TestAndroidAppComponent::class.create(this, demoApplication) + override fun metroGraph(demoApplication: DemoApplication): AppGraph { + return createGraphFactory().create(this, demoApplication) } } diff --git a/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidAppComponent.kt b/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidAppComponent.kt deleted file mode 100644 index 9e654965..00000000 --- a/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidAppComponent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package software.amazon.app.platform.sample - -import android.app.Application -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn - -/** - * The final Android app component. Note that [application] is an Android specific type and classes - * living in the Android source folder can therefore inject [Application]. - * - * [rootScopeProvider] is provided in the [AndroidAppComponent] and can be injected. - */ -@Component -@MergeComponent(AppScope::class) -@SingleIn(AppScope::class) -abstract class AndroidAppComponent( - @get:Provides val application: Application, - @get:Provides val rootScopeProvider: RootScopeProvider, -) : AndroidAppComponentMerged diff --git a/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidAppGraph.kt b/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidAppGraph.kt new file mode 100644 index 00000000..ca0baece --- /dev/null +++ b/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidAppGraph.kt @@ -0,0 +1,29 @@ +package software.amazon.app.platform.sample + +import android.app.Application +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Provides +import software.amazon.app.platform.scope.RootScopeProvider + +/** + * The final Android app graph. + * + * Note that [Application] is an Android specific type and classes living in the Android source + * folder can therefore inject [Application]. + */ +@DependencyGraph(AppScope::class) +interface AndroidAppGraph { + /** The factory to create a new instance of [AndroidAppGraph]. */ + @DependencyGraph.Factory + fun interface Factory { + /** + * Creates a new [AndroidAppGraph] instance. [application] and [rootScopeProvider] are provided + * in the [AndroidAppGraph] and can be injected. + */ + fun create( + @Provides application: Application, + @Provides rootScopeProvider: RootScopeProvider, + ): AndroidAppGraph + } +} diff --git a/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidApplication.kt b/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidApplication.kt index aef95f60..be114b37 100644 --- a/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidApplication.kt +++ b/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidApplication.kt @@ -1,6 +1,7 @@ package software.amazon.app.platform.sample import android.app.Application +import dev.zacsweers.metro.createGraphFactory import software.amazon.app.platform.scope.RootScopeProvider import software.amazon.app.platform.scope.Scope @@ -16,12 +17,12 @@ open class AndroidApplication : Application(), RootScopeProvider { get() = demoApplication.rootScope override fun onCreate() { - demoApplication.create(component(demoApplication)) + demoApplication.create(metroGraph(demoApplication)) super.onCreate() } - /** Create the [AppComponent]. In UI tests we use a different instance. */ - protected open fun component(demoApplication: DemoApplication): AppComponent { - return AndroidAppComponent::class.create(this, demoApplication) + /** Create the [AppGraph]. In UI tests we use a different instance. */ + protected open fun metroGraph(demoApplication: DemoApplication): AppGraph { + return createGraphFactory().create(this, demoApplication) } } diff --git a/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivityViewModel.kt b/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivityViewModel.kt index 29a86882..733295d8 100644 --- a/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivityViewModel.kt +++ b/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivityViewModel.kt @@ -2,12 +2,12 @@ package software.amazon.app.platform.sample import android.app.Application import androidx.lifecycle.AndroidViewModel +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesTo import kotlinx.coroutines.flow.StateFlow import software.amazon.app.platform.sample.template.SampleAppTemplate import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.app.platform.scope.di.kotlinInjectComponent -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo +import software.amazon.app.platform.scope.di.metro.metroDependencyGraph /** * `ViewModel` that hosts the stream of templates and survives configuration changes. Note that we @@ -15,9 +15,8 @@ import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo */ class MainActivityViewModel(application: Application) : AndroidViewModel(application) { - private val component = - (application as RootScopeProvider).rootScope.kotlinInjectComponent() - private val templateProvider = component.templateProviderFactory.createTemplateProvider() + private val graph = (application as RootScopeProvider).rootScope.metroDependencyGraph() + private val templateProvider = graph.templateProviderFactory.createTemplateProvider() /** The stream of templates that are rendered by [MainActivity]. */ val templates: StateFlow = templateProvider.templates @@ -26,9 +25,9 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica templateProvider.cancel() } - /** Component interface to give us access to objects from the app component. */ + /** Graph interface to give us access to objects from the app graph. */ @ContributesTo(AppScope::class) - interface Component { + interface Graph { /** Gives access to the [TemplateProvider.Factory] from the object graph. */ val templateProviderFactory: TemplateProvider.Factory } diff --git a/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/AppComponent.kt b/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/AppComponent.kt deleted file mode 100644 index 8aa6fc27..00000000 --- a/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/AppComponent.kt +++ /dev/null @@ -1,31 +0,0 @@ -package software.amazon.app.platform.sample - -import me.tatarka.inject.annotations.IntoSet -import me.tatarka.inject.annotations.Provides -import software.amazon.app.platform.scope.Scoped -import software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo -import software.amazon.lastmile.kotlin.inject.anvil.ForScope -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn - -/** - * Shared interface for the app component. The final components live in the platform specific source - * folders in order to have access to platform specific code. - */ -@ContributesTo(AppScope::class) -@SingleIn(AppScope::class) -interface AppComponent { - /** All [Scoped] instances part of the app scope. */ - @ForScope(AppScope::class) val appScopedInstances: Set - - /** The coroutine scope that runs as long as the app scope is alive. */ - @ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped - - /** - * Provide at least one implementation in the scope, otherwise kotlin-inject will complain. The - * sample app actually doesn't have a [Scoped] instance in the app scope, that's why this is - * needed. - */ - @Provides @IntoSet @ForScope(AppScope::class) fun provideEmptyScoped(): Scoped = Scoped.NO_OP -} diff --git a/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/AppGraph.kt b/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/AppGraph.kt new file mode 100644 index 00000000..f6af3e7d --- /dev/null +++ b/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/AppGraph.kt @@ -0,0 +1,21 @@ +package software.amazon.app.platform.sample + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.ForScope +import dev.zacsweers.metro.Multibinds +import software.amazon.app.platform.scope.Scoped +import software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped + +/** + * Shared interface for the app graph. The final graphs live in the platform specific source folders + * in order to have access to platform specific code. + */ +@ContributesTo(AppScope::class) +interface AppGraph { + /** All [Scoped] instances part of the app scope. */ + @ForScope(AppScope::class) @Multibinds(allowEmpty = true) val appScopedInstances: Set + + /** The coroutine scope that runs as long as the app scope is alive. */ + @ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped +} diff --git a/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt b/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt index 90f5c366..2c435996 100644 --- a/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt +++ b/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt @@ -3,7 +3,7 @@ package software.amazon.app.platform.sample import software.amazon.app.platform.scope.RootScopeProvider import software.amazon.app.platform.scope.Scope import software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped -import software.amazon.app.platform.scope.di.addKotlinInjectComponent +import software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph import software.amazon.app.platform.scope.register /** @@ -18,19 +18,19 @@ class DemoApplication : RootScopeProvider { get() = checkNotNull(_rootScope) { "Must call create() first." } /** Creates the root scope and remembers the instance. */ - fun create(appComponent: AppComponent) { + fun create(appGraph: AppGraph) { check(_rootScope == null) { "create() should be called only once." } _rootScope = Scope.buildRootScope { - addKotlinInjectComponent(appComponent) + addMetroDependencyGraph(appGraph) - addCoroutineScopeScoped(appComponent.appScopeCoroutineScopeScoped) + addCoroutineScopeScoped(appGraph.appScopeCoroutineScopeScoped) } // Register instances after the rootScope has been set to avoid race conditions for Scoped // instances that may use the rootScope. - rootScope.register(appComponent.appScopedInstances) + rootScope.register(appGraph.appScopedInstances) } /** Destroys the root scope. */ diff --git a/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/TemplateProvider.kt b/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/TemplateProvider.kt index 28ef06fb..8cc340c0 100644 --- a/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/TemplateProvider.kt +++ b/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/TemplateProvider.kt @@ -1,8 +1,9 @@ package software.amazon.app.platform.sample +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.StateFlow -import me.tatarka.inject.annotations.Assisted -import me.tatarka.inject.annotations.Inject import software.amazon.app.platform.presenter.molecule.MoleculeScope import software.amazon.app.platform.presenter.molecule.MoleculeScopeFactory import software.amazon.app.platform.presenter.molecule.launchMoleculePresenter @@ -40,6 +41,16 @@ class TemplateProvider( moleculeScope.cancel() } + /** + * The assisted factory for Metro to create a new [TemplateProvider]. This factory is wrapped by + * [Factory], which should be used instead. + */ + @AssistedFactory + fun interface InternalFactory { + /** Create a new instance of [TemplateProvider] with the given [MoleculeScope]. */ + fun create(moleculeScope: MoleculeScope): TemplateProvider + } + /** Factory class to create a new instance of [TemplateProvider]. */ // Note that the Factory class technically is not required. But since TemplateProvider // contains a MoleculeScope that needs to be canceled explicitly, this Factory helps to @@ -47,14 +58,14 @@ class TemplateProvider( @Inject class Factory( private val moleculeScopeFactory: MoleculeScopeFactory, - private val templateProvider: (MoleculeScope) -> TemplateProvider, + private val templateProviderFactory: InternalFactory, ) { /** * Creates a new instance of [TemplateProvider]. Call [TemplateProvider.cancel] when the * instance not needed anymore to avoid leaking resources. */ fun createTemplateProvider(): TemplateProvider { - return templateProvider(moleculeScopeFactory.createMoleculeScope()) + return templateProviderFactory.create(moleculeScopeFactory.createMoleculeScope()) } } } diff --git a/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopApp.kt b/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopApp.kt index b701330a..fcf65811 100644 --- a/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopApp.kt +++ b/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopApp.kt @@ -4,29 +4,29 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesTo import software.amazon.app.platform.renderer.ComposeRendererFactory import software.amazon.app.platform.renderer.getComposeRenderer import software.amazon.app.platform.scope.RootScopeProvider import software.amazon.app.platform.scope.Scope -import software.amazon.app.platform.scope.di.kotlinInjectComponent -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo +import software.amazon.app.platform.scope.di.metro.metroDependencyGraph /** - * Responsible for creating the app component [component] and producing templates. Call [destroy] to - * clean up any resources. + * Responsible for creating the ap [graph] and producing templates. Call [destroy] to clean up any + * resources. * - * This class is reused in UI tests, but the tests use a different test specific [AppComponent]. + * This class is reused in UI tests, but the tests use a different test specific [AppGraph]. */ -class DesktopApp(private val component: (RootScopeProvider) -> AppComponent) : RootScopeProvider { +class DesktopApp(private val graph: (RootScopeProvider) -> AppGraph) : RootScopeProvider { override val rootScope: Scope get() = demoApplication.rootScope - private val demoApplication = DemoApplication().apply { create(component(this)) } + private val demoApplication = DemoApplication().apply { create(graph(this)) } private val templateProvider = - rootScope.kotlinInjectComponent().templateProviderFactory.createTemplateProvider() + rootScope.metroDependencyGraph().templateProviderFactory.createTemplateProvider() /** Call this composable function to start rendering templates on the screen. */ @Composable @@ -45,9 +45,9 @@ class DesktopApp(private val component: (RootScopeProvider) -> AppComponent) : R demoApplication.destroy() } - /** Component interface to give us access to objects from the app component. */ + /** Graph interface to give us access to objects from the app graph. */ @ContributesTo(AppScope::class) - interface Component { + interface Graph { /** Gives access to the [TemplateProvider.Factory] from the object graph. */ val templateProviderFactory: TemplateProvider.Factory } diff --git a/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopAppComponent.kt b/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopAppComponent.kt deleted file mode 100644 index 8995e9b9..00000000 --- a/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopAppComponent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package software.amazon.app.platform.sample - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn - -/** - * The final Desktop app component. Unlike the Android and iOS specific counterpart, this class - * doesn't have any platform specific types. - * - * [rootScopeProvider] is provided in the [DesktopAppComponent] and can be injected. - */ -@Component -@MergeComponent(AppScope::class) -@SingleIn(AppScope::class) -abstract class DesktopAppComponent(@get:Provides val rootScopeProvider: RootScopeProvider) : - DesktopAppComponentMerged diff --git a/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopAppGraph.kt b/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopAppGraph.kt new file mode 100644 index 00000000..528e55d0 --- /dev/null +++ b/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopAppGraph.kt @@ -0,0 +1,23 @@ +package software.amazon.app.platform.sample + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Provides +import software.amazon.app.platform.scope.RootScopeProvider + +/** + * The final Desktop app graph. Unlike the Android and iOS specific counterpart, this class doesn't + * have any platform specific types. + */ +@DependencyGraph(AppScope::class) +interface DesktopAppGraph { + /** The factory to create a new instance of [DesktopAppGraph]. */ + @DependencyGraph.Factory + fun interface Factory { + /** + * Creates a new [DesktopAppGraph] instance. [rootScopeProvider] is provided in the + * [DesktopAppGraph] and can be injected. + */ + fun create(@Provides rootScopeProvider: RootScopeProvider): DesktopAppGraph + } +} diff --git a/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/Main.kt b/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/Main.kt index afb6c08c..19a18286 100644 --- a/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/Main.kt +++ b/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/Main.kt @@ -2,10 +2,11 @@ package software.amazon.app.platform.sample import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import dev.zacsweers.metro.createGraphFactory /** The main function to launch the Desktop app. */ fun main() { - val desktopApp = DesktopApp { DesktopAppComponent::class.create(it) } + val desktopApp = DesktopApp { createGraphFactory().create(it) } application { Window( diff --git a/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/LoginUiTest.kt b/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/LoginUiTest.kt index 47dccbfe..9bbd3568 100644 --- a/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/LoginUiTest.kt +++ b/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/LoginUiTest.kt @@ -3,6 +3,7 @@ package software.amazon.app.platform.sample import androidx.compose.ui.test.ComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.runComposeUiTest +import dev.zacsweers.metro.createGraphFactory import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -21,8 +22,8 @@ class LoginUiTest { @BeforeTest fun before() { desktopApp = DesktopApp { - // Note that we use a different test specific component in UI tests. - TestDesktopAppComponent::class.create(it) + // Note that we use a different test specific graph in UI tests. + createGraphFactory().create(it) } // This is required for Desktop and iOS. On Android it's expected that the Application diff --git a/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestAnimationHelper.kt b/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestAnimationHelper.kt index 84cecc67..29853e7b 100644 --- a/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestAnimationHelper.kt +++ b/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestAnimationHelper.kt @@ -1,10 +1,10 @@ package software.amazon.app.platform.sample -import me.tatarka.inject.annotations.Inject +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject import software.amazon.app.platform.sample.user.AnimationHelper import software.amazon.app.platform.sample.user.DefaultAnimationsHelper -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding /** * This implementation replaces [DefaultAnimationsHelper] in UI tests to disable animations and make diff --git a/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppComponent.kt b/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppComponent.kt deleted file mode 100644 index 41bcff0c..00000000 --- a/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppComponent.kt +++ /dev/null @@ -1,15 +0,0 @@ -package software.amazon.app.platform.sample - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn - -/** kotlin-inject component that is used in UI tests. */ -@Component -@SingleIn(AppScope::class) -@MergeComponent(AppScope::class) -abstract class TestDesktopAppComponent(@get:Provides val rootScopeProvider: RootScopeProvider) : - TestDesktopAppComponentMerged diff --git a/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppGraph.kt b/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppGraph.kt new file mode 100644 index 00000000..182a47c3 --- /dev/null +++ b/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppGraph.kt @@ -0,0 +1,15 @@ +package software.amazon.app.platform.sample + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Provides +import software.amazon.app.platform.scope.RootScopeProvider + +/** Metro graph that is used in UI tests. */ +@DependencyGraph(AppScope::class) +interface TestDesktopAppGraph : DesktopApp.Graph { + @DependencyGraph.Factory + fun interface Factory { + fun create(@Provides rootScopeProvider: RootScopeProvider): TestDesktopAppGraph + } +} diff --git a/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/IosAppComponent.kt b/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/IosAppComponent.kt deleted file mode 100644 index d88df480..00000000 --- a/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/IosAppComponent.kt +++ /dev/null @@ -1,44 +0,0 @@ -package software.amazon.app.platform.sample - -import kotlin.reflect.KClass -import me.tatarka.inject.annotations.Provides -import platform.UIKit.UIApplication -import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn - -/** - * The final iOS app component. Note that [uiApplication] is an iOS specific type and classes living - * in the iOS source folder can therefore inject [UIApplication]. - * - * [rootScopeProvider] is provided in the [IosAppComponent] and can be injected. - */ -@MergeComponent(AppScope::class) -@SingleIn(AppScope::class) -abstract class IosAppComponent( - @get:Provides val uiApplication: UIApplication, - @get:Provides val rootScopeProvider: RootScopeProvider, -) { - /** Gives access to the [TemplateProvider.Factory] from the object graph. */ - abstract val templateProviderFactory: TemplateProvider.Factory -} - -/** - * Factory function to instantiate the component. This is necessary, because `iosMain` is a shared - * source folder and generated components live in the x64, arm64 and simulatorArm64 source folders. - */ -@MergeComponent.CreateComponent -expect fun KClass.createComponent( - uiApplication: UIApplication, - rootScopeProvider: RootScopeProvider, -): IosAppComponent - -/** This function is called from Swift to create a new component instance. */ -@Suppress("unused") -fun createIosAppComponent( - application: UIApplication, - rootScopeProvider: RootScopeProvider, -): AppComponent { - return IosAppComponent::class.createComponent(application, rootScopeProvider) as AppComponent -} diff --git a/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/IosAppGraph.kt b/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/IosAppGraph.kt new file mode 100644 index 00000000..c6b01540 --- /dev/null +++ b/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/IosAppGraph.kt @@ -0,0 +1,39 @@ +package software.amazon.app.platform.sample + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Provides +import dev.zacsweers.metro.createGraphFactory +import platform.UIKit.UIApplication +import software.amazon.app.platform.scope.RootScopeProvider + +/** + * The final iOS app graph. + * + * Note that [UIApplication] is an iOS specific type and classes living in the iOS source folder can + * therefore inject [UIApplication]. + */ +@DependencyGraph(AppScope::class) +interface IosAppGraph { + /** The factory to create a new instance of [IosAppGraph]. */ + @DependencyGraph.Factory + fun interface Factory { + /** + * Creates a new [IosAppGraph] instance. [uiApplication] and [rootScopeProvider] are provided in + * the [IosAppGraph] and can be injected. + */ + fun create( + @Provides uiApplication: UIApplication, + @Provides rootScopeProvider: RootScopeProvider, + ): IosAppGraph + } + + /** Gives access to the [TemplateProvider.Factory] from the object graph. */ + val templateProviderFactory: TemplateProvider.Factory +} + +/** This function is called from Swift to create a new graph instance. */ +@Suppress("unused") +fun createIosAppGraph(application: UIApplication, rootScopeProvider: RootScopeProvider): AppGraph { + return createGraphFactory().create(application, rootScopeProvider) +} diff --git a/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/MainViewController.kt b/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/MainViewController.kt index 53a78cfb..7ad471d5 100644 --- a/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/MainViewController.kt +++ b/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/MainViewController.kt @@ -9,7 +9,7 @@ import platform.UIKit.UIViewController import software.amazon.app.platform.renderer.ComposeRendererFactory import software.amazon.app.platform.renderer.Renderer import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.app.platform.scope.di.kotlinInjectComponent +import software.amazon.app.platform.scope.di.metro.metroDependencyGraph /** * This function is called from Swift to hook up the Compose Multiplatform UI. @@ -24,7 +24,7 @@ fun mainViewController(rootScopeProvider: RootScopeProvider): UIViewController = // Create a single instance. val templateProvider = remember { rootScopeProvider.rootScope - .kotlinInjectComponent() + .metroDependencyGraph() .templateProviderFactory .createTemplateProvider() } diff --git a/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/Main.kt b/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/Main.kt index 03e86312..092aa6f0 100644 --- a/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/Main.kt +++ b/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/Main.kt @@ -7,9 +7,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.ComposeViewport +import dev.zacsweers.metro.createGraphFactory import kotlinx.browser.document import software.amazon.app.platform.renderer.ComposeRendererFactory -import software.amazon.app.platform.scope.di.kotlinInjectComponent +import software.amazon.app.platform.scope.di.metro.metroDependencyGraph /** The entry point of our sample app. */ @OptIn(ExperimentalComposeUiApi::class) @@ -20,13 +21,13 @@ fun main() { @Composable private fun AppPlatform() { val application = remember { - DemoApplication().apply { create(WasmJsAppComponent::class.create(this)) } + DemoApplication().apply { createGraphFactory().create(this) } } // Create a single instance. val templateProvider = remember { application.rootScope - .kotlinInjectComponent() + .metroDependencyGraph() .templateProviderFactory .createTemplateProvider() } diff --git a/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/WasmJsAppComponent.kt b/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/WasmJsAppComponent.kt deleted file mode 100644 index 92617edd..00000000 --- a/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/WasmJsAppComponent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package software.amazon.app.platform.sample - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import software.amazon.app.platform.scope.RootScopeProvider -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn - -/** - * The final Wasm app component. Unlike the Android and iOS specific counterpart, this class doesn't - * have any platform specific types. - * - * [rootScopeProvider] is provided in the [WasmJsAppComponent] and can be injected. - */ -@Component -@MergeComponent(AppScope::class) -@SingleIn(AppScope::class) -abstract class WasmJsAppComponent(@get:Provides val rootScopeProvider: RootScopeProvider) : - WasmJsAppComponentMerged { - /** Gives access to the [TemplateProvider.Factory] from the object graph. */ - abstract val templateProviderFactory: TemplateProvider.Factory -} diff --git a/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/WasmJsAppGraph.kt b/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/WasmJsAppGraph.kt new file mode 100644 index 00000000..2b616677 --- /dev/null +++ b/sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/WasmJsAppGraph.kt @@ -0,0 +1,28 @@ +package software.amazon.app.platform.sample + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Provides +import software.amazon.app.platform.scope.RootScopeProvider + +/** + * The final Wasm app graph. + * + * Unlike the Android and iOS specific counterpart, this class doesn't have any platform specific + * types. + */ +@DependencyGraph(AppScope::class) +interface WasmJsAppGraph { + /** The factory to create a new instance of [WasmJsAppGraph]. */ + @DependencyGraph.Factory + fun interface Factory { + /** + * Creates a new [WasmJsAppGraph] instance. [[rootScopeProvider] is provided in the + * [WasmJsAppGraph] and can be injected. + */ + fun create(@Provides rootScopeProvider: RootScopeProvider): WasmJsAppGraph + } + + /** Gives access to the [TemplateProvider.Factory] from the object graph. */ + val templateProviderFactory: TemplateProvider.Factory +} diff --git a/sample/iosApp/iosApp/iOSApp.swift b/sample/iosApp/iosApp/iOSApp.swift index 1bd4d4c6..d8bcd0d7 100644 --- a/sample/iosApp/iosApp/iOSApp.swift +++ b/sample/iosApp/iosApp/iOSApp.swift @@ -12,7 +12,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, RootScopeProvider { } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - demoApplication.create(appComponent: IosAppComponentKt.createIosAppComponent(application: application, rootScopeProvider: demoApplication)) + demoApplication.create(appGraph: IosAppGraphKt.createIosAppGraph(application: application, rootScopeProvider: demoApplication)) return true } } diff --git a/sample/login/impl-robots/build.gradle b/sample/login/impl-robots/build.gradle index ea94914c..f88ed486 100644 --- a/sample/login/impl-robots/build.gradle +++ b/sample/login/impl-robots/build.gradle @@ -9,6 +9,6 @@ plugins { appPlatform { enableComposeUi true - enableKotlinInject true + enableMetro true enableModuleStructure true } diff --git a/sample/login/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRobot.kt b/sample/login/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRobot.kt index f23a26f9..adcb2485 100644 --- a/sample/login/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRobot.kt +++ b/sample/login/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRobot.kt @@ -3,9 +3,9 @@ package software.amazon.app.platform.sample.login import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import dev.zacsweers.metro.AppScope import software.amazon.app.platform.inject.robot.ContributesRobot import software.amazon.app.platform.robot.ComposeRobot -import software.amazon.lastmile.kotlin.inject.anvil.AppScope /** A test robot to verify interactions with the login screen written with Compose Multiplatform. */ @ContributesRobot(AppScope::class) diff --git a/sample/login/impl/build.gradle b/sample/login/impl/build.gradle index 87c0cb0b..3c665130 100644 --- a/sample/login/impl/build.gradle +++ b/sample/login/impl/build.gradle @@ -9,7 +9,7 @@ plugins { appPlatform { enableComposeUi true - enableKotlinInject true + enableMetro true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImpl.kt b/sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImpl.kt index b97ec64e..984a0546 100644 --- a/sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImpl.kt +++ b/sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImpl.kt @@ -6,14 +6,14 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject import kotlin.random.Random import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay -import me.tatarka.inject.annotations.Inject import software.amazon.app.platform.sample.login.LoginPresenter.Model import software.amazon.app.platform.sample.user.UserManager -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding /** Production implementation for [LoginPresenter]. */ @Inject diff --git a/sample/login/public/build.gradle b/sample/login/public/build.gradle index 6aac2b2c..909ad4a1 100644 --- a/sample/login/public/build.gradle +++ b/sample/login/public/build.gradle @@ -8,7 +8,6 @@ plugins { } appPlatform { - enableKotlinInject true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/navigation/impl/build.gradle b/sample/navigation/impl/build.gradle index adf57821..3f71fa78 100644 --- a/sample/navigation/impl/build.gradle +++ b/sample/navigation/impl/build.gradle @@ -8,7 +8,7 @@ plugins { } appPlatform { - enableKotlinInject true + enableMetro true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt b/sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt index 9a8ca9ea..29e91e60 100644 --- a/sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt +++ b/sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt @@ -4,7 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import me.tatarka.inject.annotations.Inject +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.Provider import software.amazon.app.platform.presenter.BaseModel import software.amazon.app.platform.presenter.molecule.MoleculePresenter import software.amazon.app.platform.sample.login.LoginPresenter @@ -12,10 +16,7 @@ import software.amazon.app.platform.sample.user.UserManager import software.amazon.app.platform.sample.user.UserPagePresenter import software.amazon.app.platform.sample.user.UserScope import software.amazon.app.platform.scope.Scope -import software.amazon.app.platform.scope.di.kotlinInjectComponent -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding -import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo +import software.amazon.app.platform.scope.di.metro.metroDependencyGraph /** * Production implementation of [NavigationPresenter]. @@ -27,7 +28,7 @@ import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo @ContributesBinding(AppScope::class) class NavigationPresenterImpl( private val userManager: UserManager, - private val loginPresenter: () -> LoginPresenter, + private val loginPresenter: Provider, ) : NavigationPresenter { @Composable @@ -39,11 +40,10 @@ class NavigationPresenterImpl( return presenter.present(Unit) } - // A user is logged in. Use the user component to get an instance of UserPagePresenter, which is + // A user is logged in. Use the user graph to get an instance of UserPagePresenter, which is // only // part of the user scope. - val userPresenter = - remember(scope) { scope.kotlinInjectComponent().userPresenter } + val userPresenter = remember(scope) { scope.metroDependencyGraph().userPresenter } return userPresenter.present(Unit) } @@ -54,11 +54,11 @@ class NavigationPresenterImpl( } /** - * This component interface gives us access to objects from the user scope. We cannot inject + * This graph interface gives us access to objects from the user scope. We cannot inject * `UserPresenter` in the constructor, because it's part of the user scope. */ @ContributesTo(UserScope::class) - interface UserComponent { + interface UserGraph { /** The [UserPagePresenter] provided by the user scope. */ val userPresenter: UserPagePresenter } diff --git a/sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt b/sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt index 7fa6cd12..deead5f8 100644 --- a/sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt +++ b/sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt @@ -30,8 +30,8 @@ class NavigationPresenterImplTest { userManager.login( userId = 1L, scope = this@runTest, - component = - object : NavigationPresenterImpl.UserComponent { + graph = + object : NavigationPresenterImpl.UserGraph { override val userPresenter: UserPagePresenter get() = FakeUserPagePresenter() }, diff --git a/sample/navigation/public/build.gradle b/sample/navigation/public/build.gradle index 6aac2b2c..909ad4a1 100644 --- a/sample/navigation/public/build.gradle +++ b/sample/navigation/public/build.gradle @@ -8,7 +8,6 @@ plugins { } appPlatform { - enableKotlinInject true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/templates/impl/build.gradle b/sample/templates/impl/build.gradle index 53979094..089dc94e 100644 --- a/sample/templates/impl/build.gradle +++ b/sample/templates/impl/build.gradle @@ -9,7 +9,7 @@ plugins { appPlatform { enableComposeUi true - enableKotlinInject true + enableMetro true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/templates/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/template/ComposeSampleAppTemplateRenderer.kt b/sample/templates/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/template/ComposeSampleAppTemplateRenderer.kt index 516f20cb..91d8a613 100644 --- a/sample/templates/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/template/ComposeSampleAppTemplateRenderer.kt +++ b/sample/templates/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/template/ComposeSampleAppTemplateRenderer.kt @@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier -import me.tatarka.inject.annotations.Inject +import dev.zacsweers.metro.Inject import software.amazon.app.platform.inject.ContributesRenderer import software.amazon.app.platform.presenter.BaseModel import software.amazon.app.platform.presenter.molecule.backgesture.BackGestureDispatcherPresenter diff --git a/sample/templates/public/build.gradle b/sample/templates/public/build.gradle index 5e18ac15..6c3adfb5 100644 --- a/sample/templates/public/build.gradle +++ b/sample/templates/public/build.gradle @@ -9,7 +9,7 @@ plugins { appPlatform { enableComposeUi true - enableKotlinInject true + enableMetro true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplatePresenter.kt b/sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplatePresenter.kt index 7e393f72..1a805e4e 100644 --- a/sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplatePresenter.kt +++ b/sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplatePresenter.kt @@ -1,8 +1,9 @@ package software.amazon.app.platform.sample.template import androidx.compose.runtime.Composable -import me.tatarka.inject.annotations.Assisted -import me.tatarka.inject.annotations.Inject +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.Inject import software.amazon.app.platform.presenter.BaseModel import software.amazon.app.platform.presenter.molecule.MoleculePresenter import software.amazon.app.platform.presenter.molecule.backgesture.BackGestureDispatcherPresenter @@ -33,13 +34,9 @@ class SampleAppTemplatePresenter( } } - /** - * A factory to instantiate a new [SampleAppTemplatePresenter] instance. This implementation hides - * that assisted injection with kotlin-inject is used. It's easier to inject this [Factory] than - * the lambda provided by kotlin-inject. - */ - @Inject - class Factory(private val factory: (MoleculePresenter) -> SampleAppTemplatePresenter) { + /** A factory to instantiate a new [SampleAppTemplatePresenter] instance. */ + @AssistedFactory + fun interface Factory { /** * Create a new [SampleAppTemplatePresenter]. The given [presenter] will be wrapped and its * models are transformed into a [SampleAppTemplate] with [SampleAppTemplate.FullScreenTemplate] @@ -48,8 +45,6 @@ class SampleAppTemplatePresenter( */ fun createSampleAppTemplatePresenter( presenter: MoleculePresenter - ): SampleAppTemplatePresenter { - return factory(presenter) - } + ): SampleAppTemplatePresenter } } diff --git a/sample/user/impl-robots/build.gradle b/sample/user/impl-robots/build.gradle index ea94914c..f88ed486 100644 --- a/sample/user/impl-robots/build.gradle +++ b/sample/user/impl-robots/build.gradle @@ -9,6 +9,6 @@ plugins { appPlatform { enableComposeUi true - enableKotlinInject true + enableMetro true enableModuleStructure true } diff --git a/sample/user/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageRobot.kt b/sample/user/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageRobot.kt index 6f3e09a5..e469fdf2 100644 --- a/sample/user/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageRobot.kt +++ b/sample/user/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageRobot.kt @@ -4,10 +4,10 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick -import me.tatarka.inject.annotations.Inject +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Inject import software.amazon.app.platform.inject.robot.ContributesRobot import software.amazon.app.platform.robot.ComposeRobot -import software.amazon.lastmile.kotlin.inject.anvil.AppScope /** * A test robot to verify interactions with the user page screen written with Compose Multiplatform. diff --git a/sample/user/impl/build.gradle b/sample/user/impl/build.gradle index 5cf7b982..e65410fa 100644 --- a/sample/user/impl/build.gradle +++ b/sample/user/impl/build.gradle @@ -9,7 +9,7 @@ plugins { appPlatform { enableComposeUi true - enableKotlinInject true + enableMetro true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/user/impl/src/androidMain/kotlin/software/amazon/app/platform/sample/user/AndroidAnimationsHelper.kt b/sample/user/impl/src/androidMain/kotlin/software/amazon/app/platform/sample/user/AndroidAnimationsHelper.kt index 8740ac8f..b09895ab 100644 --- a/sample/user/impl/src/androidMain/kotlin/software/amazon/app/platform/sample/user/AndroidAnimationsHelper.kt +++ b/sample/user/impl/src/androidMain/kotlin/software/amazon/app/platform/sample/user/AndroidAnimationsHelper.kt @@ -2,9 +2,9 @@ package software.amazon.app.platform.sample.user import android.app.Application import android.provider.Settings -import me.tatarka.inject.annotations.Inject -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject /** * Android implementation of [AnimationHelper] which queries the device state to determine whether diff --git a/sample/user/impl/src/appleAndDesktopMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt b/sample/user/impl/src/appleAndDesktopMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt index e63c6486..9e4759cf 100644 --- a/sample/user/impl/src/appleAndDesktopMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt +++ b/sample/user/impl/src/appleAndDesktopMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt @@ -1,8 +1,8 @@ package software.amazon.app.platform.sample.user -import me.tatarka.inject.annotations.Inject -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject /** * Default implementation of [AnimationHelper] that always keeps animations enabled. This diff --git a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/AnimationHelper.kt b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/AnimationHelper.kt index 3810558b..83518090 100644 --- a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/AnimationHelper.kt +++ b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/AnimationHelper.kt @@ -5,8 +5,7 @@ package software.amazon.app.platform.sample.user * * The interesting part is that this interface is part of the `:impl` module, because it doesn't * need to be shared with other modules. The implementations live in the platform specific folders - * like `androidMain`. kotlin-inject-anvil will use the right implementation on each platform - * automatically. + * like `androidMain`. Metro will use the right implementation on each platform automatically. */ interface AnimationHelper { diff --git a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/SessionTimeout.kt b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/SessionTimeout.kt index b765650e..6bcd30ef 100644 --- a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/SessionTimeout.kt +++ b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/SessionTimeout.kt @@ -1,5 +1,7 @@ package software.amazon.app.platform.sample.user +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -8,12 +10,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update -import me.tatarka.inject.annotations.Inject +import software.amazon.app.platform.inject.metro.ContributesScoped import software.amazon.app.platform.scope.Scope import software.amazon.app.platform.scope.Scoped import software.amazon.app.platform.scope.coroutine.launch -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn /** * This class logs out the user after a certain delay. @@ -25,7 +25,7 @@ import software.amazon.lastmile.kotlin.inject.anvil.SingleIn */ @Inject @SingleIn(UserScope::class) -@ContributesBinding(UserScope::class) +@ContributesScoped(UserScope::class) class SessionTimeout(private val userManager: UserManager, animationHelper: AnimationHelper) : Scoped { diff --git a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserComponent.kt b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserGraph.kt similarity index 60% rename from sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserComponent.kt rename to sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserGraph.kt index 10d387f3..5d5f83b4 100644 --- a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserComponent.kt +++ b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserGraph.kt @@ -1,39 +1,39 @@ package software.amazon.app.platform.sample.user +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.ForScope +import dev.zacsweers.metro.GraphExtension +import dev.zacsweers.metro.Multibinds +import dev.zacsweers.metro.Provides +import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob -import me.tatarka.inject.annotations.Provides import software.amazon.app.platform.scope.Scoped import software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped import software.amazon.app.platform.scope.coroutine.IoCoroutineDispatcher -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesSubcomponent -import software.amazon.lastmile.kotlin.inject.anvil.ForScope -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn -/** - * The kotlin-inject component for the user scope. This is a subcomponent of the AppScope component. - */ -@ContributesSubcomponent(UserScope::class) -@SingleIn(UserScope::class) -interface UserComponent { +/** The Metro graph for the user scope. This is a graph extension of the AppScope graph. */ +@GraphExtension(UserScope::class) +interface UserGraph { /** - * The factory instantiates a new instance of [UserComponent]. This interface will be implemented - * by the AppScope component. + * The factory instantiates a new instance of [UserGraph]. This interface will be implemented by + * the AppScope graph. */ - @ContributesSubcomponent.Factory(AppScope::class) + @GraphExtension.Factory + @ContributesTo(AppScope::class) interface Factory { /** - * Creates a new instance of [UserComponent]. The provided [user] argument will be added to the - * component and the [User] can be injected in the classes part of the [UserScope]. + * Creates a new instance of [UserGraph]. The provided [user] argument will be added to the + * graph and the [User] can be injected in the classes part of the [UserScope]. */ - fun createUserComponent(user: User): UserComponent + fun createUserGraph(@Provides user: User): UserGraph } /** All [Scoped] instances part of the user scope. */ - @ForScope(UserScope::class) val userScopedInstances: Set + @ForScope(UserScope::class) @Multibinds(allowEmpty = true) val userScopedInstances: Set /** The coroutine scope that runs as long as the user scope is alive. */ @ForScope(UserScope::class) val userScopeCoroutineScopeScoped: CoroutineScopeScoped diff --git a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt index 67dd029b..834267cf 100644 --- a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt +++ b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt @@ -2,30 +2,30 @@ package software.amazon.app.platform.sample.user import app_platform.sample.user.impl.generated.resources.Res import app_platform.sample.user.impl.generated.resources.allDrawableResources +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn import kotlin.random.Random import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import me.tatarka.inject.annotations.Inject import org.jetbrains.compose.resources.ExperimentalResourceApi import software.amazon.app.platform.scope.RootScopeProvider import software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped -import software.amazon.app.platform.scope.di.addKotlinInjectComponent +import software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph import software.amazon.app.platform.scope.register -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding -import software.amazon.lastmile.kotlin.inject.anvil.SingleIn /** * Production implementation of [UserManager]. * - * This class is responsible for creating the [UserScope] and [UserComponent]. + * This class is responsible for creating the [UserScope] and [UserGraph]. */ @Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class UserManagerImpl( private val rootScopeProvider: RootScopeProvider, - private val userComponentFactory: UserComponent.Factory, + private val userGraphFactory: UserGraph.Factory, ) : UserManager { private val _user = MutableStateFlow(null) @@ -46,13 +46,13 @@ class UserManagerImpl( ), ) - val userComponent = userComponentFactory.createUserComponent(user) + val userGraph = userGraphFactory.createUserGraph(user) val userScope = rootScopeProvider.rootScope.buildChild("user-$userId") { - addKotlinInjectComponent(userComponent) + addMetroDependencyGraph(userGraph) - addCoroutineScopeScoped(userComponent.userScopeCoroutineScopeScoped) + addCoroutineScopeScoped(userGraph.userScopeCoroutineScopeScoped) } user.scope = userScope @@ -61,7 +61,7 @@ class UserManagerImpl( // Register instances after the userScope has been set to avoid race conditions for Scoped // instances that may use the userScope. - userScope.register(userComponent.userScopedInstances) + userScope.register(userGraph.userScopedInstances) } override fun logout() { diff --git a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageDetailPresenter.kt b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageDetailPresenter.kt index 18925a23..0f13ba3b 100644 --- a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageDetailPresenter.kt +++ b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageDetailPresenter.kt @@ -7,8 +7,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import dev.zacsweers.metro.Inject import kotlin.time.Duration -import me.tatarka.inject.annotations.Inject import software.amazon.app.platform.presenter.BaseModel import software.amazon.app.platform.presenter.molecule.MoleculePresenter import software.amazon.app.platform.sample.template.animation.AnimationContentKey diff --git a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageListPresenter.kt b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageListPresenter.kt index 264b58e1..27d32fd7 100644 --- a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageListPresenter.kt +++ b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageListPresenter.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import me.tatarka.inject.annotations.Inject +import dev.zacsweers.metro.Inject import software.amazon.app.platform.presenter.BaseModel import software.amazon.app.platform.presenter.molecule.MoleculePresenter import software.amazon.app.platform.sample.user.UserPageListPresenter.Input diff --git a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImpl.kt b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImpl.kt index 7902a346..e5b70843 100644 --- a/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImpl.kt +++ b/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImpl.kt @@ -1,14 +1,14 @@ package software.amazon.app.platform.sample.user import androidx.compose.runtime.Composable -import me.tatarka.inject.annotations.Inject +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject import software.amazon.app.platform.presenter.BaseModel import software.amazon.app.platform.presenter.molecule.backgesture.BackHandlerPresenter import software.amazon.app.platform.presenter.template.ModelDelegate import software.amazon.app.platform.renderer.Renderer import software.amazon.app.platform.sample.template.SampleAppTemplate import software.amazon.app.platform.sample.user.UserPagePresenter.Model -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding /** * Production implementation of [UserPagePresenter]. diff --git a/sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/UserManagerImplTest.kt b/sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/UserManagerImplTest.kt index 63cfb0e8..ff7a69a7 100644 --- a/sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/UserManagerImplTest.kt +++ b/sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/UserManagerImplTest.kt @@ -16,8 +16,8 @@ import software.amazon.app.platform.scope.Scope import software.amazon.app.platform.scope.Scoped import software.amazon.app.platform.scope.buildTestScope import software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped -import software.amazon.app.platform.scope.di.addKotlinInjectComponent -import software.amazon.app.platform.scope.di.kotlinInjectComponent +import software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph +import software.amazon.app.platform.scope.di.metro.metroDependencyGraph class UserManagerImplTest { @@ -66,13 +66,13 @@ class UserManagerImplTest { private fun userManager(rootScopeProvider: RootScopeProvider): UserManagerImpl = UserManagerImpl( rootScopeProvider, - rootScopeProvider.rootScope.kotlinInjectComponent(), + rootScopeProvider.rootScope.metroDependencyGraph(), ) - private fun TestScope.appComponent(): Any = - object : UserComponent.Factory { - override fun createUserComponent(user: User): UserComponent { - return object : UserComponent { + private fun TestScope.appGraph(): Any = + object : UserGraph.Factory { + override fun createUserGraph(user: User): UserGraph { + return object : UserGraph { override val userScopedInstances: Set = emptySet() override val userScopeCoroutineScopeScoped: CoroutineScopeScoped = CoroutineScopeScoped(coroutineContext + Job() + CoroutineName("TestUserScope")) @@ -80,10 +80,10 @@ class UserManagerImplTest { } } - private fun TestScope.rootScopeProvider(appComponent: Any = appComponent()): RootScopeProvider { + private fun TestScope.rootScopeProvider(appGraph: Any = appGraph()): RootScopeProvider { return object : RootScopeProvider { override val rootScope: Scope = - Scope.buildTestScope(this@rootScopeProvider) { addKotlinInjectComponent(appComponent) } + Scope.buildTestScope(this@rootScopeProvider) { addMetroDependencyGraph(appGraph) } } } } diff --git a/sample/user/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt b/sample/user/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt index a56ee171..1a5dda39 100644 --- a/sample/user/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt +++ b/sample/user/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt @@ -1,8 +1,8 @@ package software.amazon.app.platform.sample.user -import me.tatarka.inject.annotations.Inject -import software.amazon.lastmile.kotlin.inject.anvil.AppScope -import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject /** * Default implementation of [AnimationHelper] that always keeps animations enabled. This diff --git a/sample/user/public/build.gradle b/sample/user/public/build.gradle index 6aac2b2c..909ad4a1 100644 --- a/sample/user/public/build.gradle +++ b/sample/user/public/build.gradle @@ -8,7 +8,6 @@ plugins { } appPlatform { - enableKotlinInject true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/User.kt b/sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/User.kt index 6e54a79e..becc67ae 100644 --- a/sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/User.kt +++ b/sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/User.kt @@ -12,7 +12,7 @@ interface User { /** * The scope is tied to the lifecycle of the user. It hosts a user specific `CoroutineScope` and - * kotlin-inject component. The scope is destroyed on logout. + * Metro graph. The scope is destroyed on logout. */ val scope: Scope diff --git a/sample/user/testing/build.gradle b/sample/user/testing/build.gradle index 6aac2b2c..6585a9e7 100644 --- a/sample/user/testing/build.gradle +++ b/sample/user/testing/build.gradle @@ -8,7 +8,7 @@ plugins { } appPlatform { - enableKotlinInject true + enableMetro true enableModuleStructure true enableMoleculePresenters true } diff --git a/sample/user/testing/src/commonMain/kotlin/software/amazon/app/platform/sample/user/FakeUserManager.kt b/sample/user/testing/src/commonMain/kotlin/software/amazon/app/platform/sample/user/FakeUserManager.kt index 03891904..11f448d2 100644 --- a/sample/user/testing/src/commonMain/kotlin/software/amazon/app/platform/sample/user/FakeUserManager.kt +++ b/sample/user/testing/src/commonMain/kotlin/software/amazon/app/platform/sample/user/FakeUserManager.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import software.amazon.app.platform.scope.Scope import software.amazon.app.platform.scope.buildTestScope -import software.amazon.app.platform.scope.di.addKotlinInjectComponent +import software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph /** * Fake implementation of [UserManager], which is useful in unit tests. @@ -18,15 +18,12 @@ class FakeUserManager(override val user: MutableStateFlow = MutableStateF user.value = FakeUser(userId = userId) } - /** - * Overloaded function to change the coroutine scope and kotlin-inject component for the - * [FakeUser]. - */ - fun login(userId: Long, scope: TestScope, component: Any) { + /** Overloaded function to change the coroutine scope and Metro graph for the [FakeUser]. */ + fun login(userId: Long, scope: TestScope, graph: Any) { user.value = FakeUser( userId = userId, - scope = Scope.buildTestScope(scope) { addKotlinInjectComponent(component) }, + scope = Scope.buildTestScope(scope) { addMetroDependencyGraph(graph) }, ) }