Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sample/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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<TestAndroidAppGraph.Factory>().create(this, demoApplication)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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<AndroidAppGraph.Factory>().create(this, demoApplication)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@ 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
* use [application] to get access to the root scope.
*/
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {

private val component =
(application as RootScopeProvider).rootScope.kotlinInjectComponent<Component>()
private val templateProvider = component.templateProviderFactory.createTemplateProvider()
private val graph = (application as RootScopeProvider).rootScope.metroDependencyGraph<Graph>()
private val templateProvider = graph.templateProviderFactory.createTemplateProvider()

/** The stream of templates that are rendered by [MainActivity]. */
val templates: StateFlow<SampleAppTemplate> = templateProvider.templates
Expand All @@ -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
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<Scoped>

/** The coroutine scope that runs as long as the app scope is alive. */
@ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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. */
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,21 +41,31 @@ 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
// highlight that the created instance contains resources that must be cleaned up.
@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())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Component>().templateProviderFactory.createTemplateProvider()
rootScope.metroDependencyGraph<Graph>().templateProviderFactory.createTemplateProvider()

/** Call this composable function to start rendering templates on the screen. */
@Composable
Expand All @@ -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
}
Expand Down

This file was deleted.

Loading
Loading