Skip to content

Commit f604494

Browse files
committed
Metro: the last thirteen workers off Hilt, plus a generic worker factory base
1 parent 6f74a07 commit f604494

23 files changed

Lines changed: 301 additions & 78 deletions

File tree

app/src/androidTest/kotlin/app/aaps/di/BaseTestApp.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,23 @@ open class BaseTestApp : Application(), HasAndroidInjector, MetroMemberInjector,
5959
// test application, so initialize a test WorkManager here — otherwise building the Hilt graph
6060
// (e.g. SyncModule.providesWorkManager → WorkManager.getInstance) throws "not initialized".
6161
//
62-
// The factory must be the Hilt one: @HiltWorker workers (e.g. QueueWorker) are built via
63-
// assisted injection and cannot be instantiated by WorkManager's default reflective factory
64-
// ("Could not instantiate ... NoSuchMethodException"), which would leave queued commands
65-
// (e.g. CommandSetProfile) forever unexecuted. The Hilt singleton component does not exist yet
66-
// at onCreate (HiltAndroidRule builds it per test), so resolve HiltWorkerFactory lazily via an
67-
// EntryPoint at worker-creation time — by then the graph is built. Returning its result (null
68-
// for non-@HiltWorker workers) lets WorkManager's built-in reflective fallback handle legacy
69-
// workers, exactly as MainApp's `setWorkerFactory(hiltWorkerFactory)` does in production.
62+
// Every worker is built by assisted injection, so WorkManager's default reflective factory
63+
// cannot instantiate any of them ("Could not instantiate ... NoSuchMethodException") - that
64+
// would leave queued commands (e.g. CommandSetProfile) forever unexecuted. So the same chain
65+
// production uses has to run here: Metro first, Hilt second. It used to be Hilt only, which
66+
// was right while workers were @HiltWorker; now that none are, Hilt alone returns null for
67+
// every worker and the reflective fallback fails.
68+
//
69+
// Neither graph exists yet at onCreate (HiltAndroidRule builds the Hilt component per test),
70+
// so both are resolved lazily at worker-creation time, by which point they are built.
7071
val configuration = Configuration.Builder()
7172
.setExecutor(SynchronousExecutor())
7273
.setWorkerFactory(object : WorkerFactory() {
7374
override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? =
74-
EntryPointAccessors.fromApplication(this@BaseTestApp, WorkerFactoryEntryPoint::class.java)
75-
.hiltWorkerFactory()
76-
.createWorker(appContext, workerClassName, workerParameters)
75+
metroGraphs().workerCreators()[workerClassName]?.create(appContext, workerParameters)
76+
?: EntryPointAccessors.fromApplication(this@BaseTestApp, WorkerFactoryEntryPoint::class.java)
77+
.hiltWorkerFactory()
78+
.createWorker(appContext, workerClassName, workerParameters)
7779
})
7880
.build()
7981
WorkManagerTestInitHelper.initializeTestWorkManager(this, configuration)

app/src/main/kotlin/app/aaps/di/CoreObjectsModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ import app.aaps.core.objects.runningMode.RunningModeGuard
115115
import app.aaps.core.objects.wizard.BolusWizard
116116
import app.aaps.core.objects.wizard.QuickWizard
117117
import app.aaps.core.ui.search.SearchableProvider
118+
import app.aaps.core.interfaces.aps.AutosensData
118119
import app.aaps.core.utils.receivers.DataInbox
119120
import app.aaps.plugins.sync.wear.WearPlugin
120121
import app.aaps.plugins.sync.nsclientV3.clientcontrol.AuthorizedClientsRepository
@@ -279,6 +280,8 @@ class CoreObjectsModule {
279280
@Provides @Singleton fun provideStoreDataForDb(graphs: MetroGraphs): StoreDataForDb = graphs.storeDataForDb
280281
@Provides @Singleton fun provideSceneExecutor(graphs: MetroGraphs): SceneExecutor = graphs.sceneExecutor
281282
@Provides @Singleton fun provideDataInbox(graphs: MetroGraphs): DataInbox = graphs.dataInbox
283+
// Unscoped on purpose - a fresh value object per caller, as the @Binds it replaces was.
284+
@Provides fun provideAutosensData(graphs: MetroGraphs): AutosensData = graphs.autosensData
282285
@Provides @Singleton fun provideActivePlugin(graphs: MetroGraphs): ActivePlugin = graphs.activePlugin
283286
@Provides @Singleton fun providePluginPermissions(graphs: MetroGraphs): PluginPermissions = graphs.pluginPermissions
284287
// MainApp injects the concrete class to hand it the merged plugin list, and the androidTest

app/src/main/kotlin/app/aaps/di/metro/AppRootGraph.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import app.aaps.plugins.sync.tidepool.auth.AuthFlowOut
2626
import app.aaps.implementation.scenes.SceneExecutor
2727
import app.aaps.core.interfaces.plugin.ActivePlugin
2828
import app.aaps.core.interfaces.plugin.PluginPermissions
29+
import app.aaps.core.interfaces.aps.AutosensData
2930
import app.aaps.core.utils.receivers.DataInbox
3031
import app.aaps.implementation.plugin.PluginStore
3132
import app.aaps.core.interfaces.sync.XDripBroadcast
@@ -282,6 +283,7 @@ interface AppRootGraph : MetroViewModelMultibindings {
282283
val storeDataForDb: StoreDataForDb
283284
val sceneExecutor: SceneExecutor
284285
val dataInbox: DataInbox
286+
val autosensData: AutosensData
285287
val activePlugin: ActivePlugin
286288
val pluginPermissions: PluginPermissions
287289
val pluginStore: PluginStore

app/src/main/kotlin/app/aaps/di/metro/AppWorkersGraph.kt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ package app.aaps.di.metro
22

33
import androidx.work.ListenableWorker
44
import app.aaps.core.objects.workflow.MetroWorkerCreator
5+
import app.aaps.plugins.sync.smsCommunicator.SmsCommunicatorPlugin
6+
import app.aaps.plugins.sync.xdrip.workers.XdripDataSyncWorker
7+
import app.aaps.plugins.sync.nsclientV3.workers.LoadTreatmentsWorker
8+
import app.aaps.plugins.sync.nsclientV3.workers.LoadStatusWorker
9+
import app.aaps.plugins.sync.nsclientV3.workers.LoadSettingsWorker
10+
import app.aaps.plugins.sync.nsclientV3.workers.LoadProfileStoreWorker
11+
import app.aaps.plugins.sync.nsclientV3.workers.LoadLastModificationWorker
12+
import app.aaps.plugins.sync.nsclientV3.workers.LoadFoodsWorker
13+
import app.aaps.plugins.sync.nsclientV3.workers.LoadDeviceStatusWorker
14+
import app.aaps.plugins.sync.nsclientV3.workers.LoadBgWorker
15+
import app.aaps.plugins.sync.nsclientV3.workers.DataSyncWorker
16+
import app.aaps.workflow.PrepareGraphDataWorker
17+
import app.aaps.workflow.PostCalculationWorker
518
import app.aaps.core.objects.workflow.WorkerKey
619
import app.aaps.implementation.maintenance.ImportExportPrefsImpl
720
import app.aaps.implementation.receivers.KeepAliveWorker
@@ -76,4 +89,69 @@ interface AppWorkersGraph {
7689
fun bindApsResultExportWorker(
7790
f: ImportExportPrefsImpl.ApsResultExportWorker.Factory
7891
): MetroWorkerCreator = f
92+
93+
@Provides
94+
@IntoMap
95+
@WorkerKey(DataSyncWorker::class)
96+
fun bindDataSyncWorker(f: DataSyncWorker.Factory): MetroWorkerCreator = f
97+
98+
@Provides
99+
@IntoMap
100+
@WorkerKey(LoadBgWorker::class)
101+
fun bindLoadBgWorker(f: LoadBgWorker.Factory): MetroWorkerCreator = f
102+
103+
@Provides
104+
@IntoMap
105+
@WorkerKey(LoadDeviceStatusWorker::class)
106+
fun bindLoadDeviceStatusWorker(f: LoadDeviceStatusWorker.Factory): MetroWorkerCreator = f
107+
108+
@Provides
109+
@IntoMap
110+
@WorkerKey(LoadFoodsWorker::class)
111+
fun bindLoadFoodsWorker(f: LoadFoodsWorker.Factory): MetroWorkerCreator = f
112+
113+
@Provides
114+
@IntoMap
115+
@WorkerKey(LoadLastModificationWorker::class)
116+
fun bindLoadLastModificationWorker(f: LoadLastModificationWorker.Factory): MetroWorkerCreator = f
117+
118+
@Provides
119+
@IntoMap
120+
@WorkerKey(LoadProfileStoreWorker::class)
121+
fun bindLoadProfileStoreWorker(f: LoadProfileStoreWorker.Factory): MetroWorkerCreator = f
122+
123+
@Provides
124+
@IntoMap
125+
@WorkerKey(LoadSettingsWorker::class)
126+
fun bindLoadSettingsWorker(f: LoadSettingsWorker.Factory): MetroWorkerCreator = f
127+
128+
@Provides
129+
@IntoMap
130+
@WorkerKey(LoadStatusWorker::class)
131+
fun bindLoadStatusWorker(f: LoadStatusWorker.Factory): MetroWorkerCreator = f
132+
133+
@Provides
134+
@IntoMap
135+
@WorkerKey(LoadTreatmentsWorker::class)
136+
fun bindLoadTreatmentsWorker(f: LoadTreatmentsWorker.Factory): MetroWorkerCreator = f
137+
138+
@Provides
139+
@IntoMap
140+
@WorkerKey(XdripDataSyncWorker::class)
141+
fun bindXdripDataSyncWorker(f: XdripDataSyncWorker.Factory): MetroWorkerCreator = f
142+
143+
@Provides
144+
@IntoMap
145+
@WorkerKey(PostCalculationWorker::class)
146+
fun bindPostCalculationWorker(f: PostCalculationWorker.Factory): MetroWorkerCreator = f
147+
148+
@Provides
149+
@IntoMap
150+
@WorkerKey(PrepareGraphDataWorker::class)
151+
fun bindPrepareGraphDataWorker(f: PrepareGraphDataWorker.Factory): MetroWorkerCreator = f
152+
153+
@Provides
154+
@IntoMap
155+
@WorkerKey(SmsCommunicatorPlugin.SmsCommunicatorWorker::class)
156+
fun bindSmsCommunicatorWorker(f: SmsCommunicatorPlugin.SmsCommunicatorWorker.Factory): MetroWorkerCreator = f
79157
}

app/src/main/kotlin/app/aaps/di/metro/MetroGraphs.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import app.aaps.core.interfaces.nsclient.StoreDataForDb
2525
import app.aaps.plugins.sync.tidepool.auth.AuthFlowOut
2626
import app.aaps.implementation.scenes.SceneExecutor
2727
import app.aaps.core.interfaces.plugin.PluginPermissions
28+
import app.aaps.core.interfaces.aps.AutosensData
2829
import app.aaps.core.utils.receivers.DataInbox
2930
import app.aaps.implementation.plugin.PluginStore
3031
import app.aaps.core.interfaces.sync.XDripBroadcast
@@ -312,6 +313,7 @@ class MetroGraphs @Inject constructor(
312313
val storeDataForDb: StoreDataForDb get() = root.storeDataForDb
313314
val sceneExecutor: SceneExecutor get() = root.sceneExecutor
314315
val dataInbox: DataInbox get() = root.dataInbox
316+
val autosensData: AutosensData get() = root.autosensData
315317
val activePlugin: ActivePlugin get() = root.activePlugin
316318
val pluginPermissions: PluginPermissions get() = root.pluginPermissions
317319
val pluginStore: PluginStore get() = root.pluginStore
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package app.aaps.di.metro
2+
3+
import app.aaps.implementation.maintenance.ImportExportPrefsImpl
4+
import app.aaps.implementation.receivers.KeepAliveWorker
5+
import app.aaps.implementation.scenes.SceneExpiryWorker
6+
import app.aaps.plugins.aps.loop.runningMode.RunningModeExpiryWorker
7+
import app.aaps.plugins.sync.nsclientV3.workers.DataSyncWorker
8+
import app.aaps.plugins.sync.nsclientV3.workers.LoadBgWorker
9+
import app.aaps.plugins.sync.nsclientV3.workers.LoadDeviceStatusWorker
10+
import app.aaps.plugins.sync.nsclientV3.workers.LoadFoodsWorker
11+
import app.aaps.plugins.sync.nsclientV3.workers.LoadLastModificationWorker
12+
import app.aaps.plugins.sync.nsclientV3.workers.LoadProfileStoreWorker
13+
import app.aaps.plugins.sync.nsclientV3.workers.LoadSettingsWorker
14+
import app.aaps.plugins.sync.nsclientV3.workers.LoadStatusWorker
15+
import app.aaps.plugins.sync.nsclientV3.workers.LoadTreatmentsWorker
16+
import app.aaps.plugins.sync.smsCommunicator.SmsCommunicatorPlugin
17+
import app.aaps.plugins.sync.xdrip.workers.XdripDataSyncWorker
18+
import app.aaps.workflow.PostCalculationWorker
19+
import app.aaps.workflow.PrepareGraphDataWorker
20+
import com.google.common.truth.Truth.assertThat
21+
import org.junit.jupiter.api.Test
22+
23+
/**
24+
* The app-wide workers Metro builds, keyed by class.
25+
*
26+
* `MetroWorkerFactory` looks a worker up here and, on a miss, hands it to Hilt. That fallback is what
27+
* let workers move a few at a time - and it is also why a mistake here is silent: a worker that lost
28+
* its `@HiltWorker` but never reached this map is not a crash, it is a worker WorkManager can no longer
29+
* construct. The job simply stops running and nothing in the build says so. For the nsclientV3 loaders
30+
* that would mean Nightscout data quietly never syncing.
31+
*
32+
* Each worker registers itself - `@ContributesIntoMap` plus `@WorkerKey` on its nested
33+
* `@AssistedFactory` - so nothing central lists them any more, and this is the only place that states
34+
* which ones exist. Note the contribution targets `AppScope` while this map lives on a graph extension:
35+
* that it arrives here at all is the thing worth asserting.
36+
*
37+
* Same shape as `SourceGraphTest` does for the twelve source workers.
38+
*/
39+
class AppWorkersGraphTest {
40+
41+
@Test
42+
fun `every app worker wired to Metro is registered under its own class`() {
43+
assertThat(testRoot().workersGraph.workerCreators.keys).containsExactly(
44+
RunningModeExpiryWorker::class,
45+
KeepAliveWorker::class,
46+
SceneExpiryWorker::class,
47+
ImportExportPrefsImpl.CsvExportWorker::class,
48+
ImportExportPrefsImpl.ApsResultExportWorker::class,
49+
// :workflow - recalculate and redraw the overview graph.
50+
PostCalculationWorker::class,
51+
PrepareGraphDataWorker::class,
52+
// :plugins:sync - the nine nsclientV3 loaders, plus xdrip and SMS.
53+
DataSyncWorker::class,
54+
LoadBgWorker::class,
55+
LoadDeviceStatusWorker::class,
56+
LoadFoodsWorker::class,
57+
LoadLastModificationWorker::class,
58+
LoadProfileStoreWorker::class,
59+
LoadSettingsWorker::class,
60+
LoadStatusWorker::class,
61+
LoadTreatmentsWorker::class,
62+
XdripDataSyncWorker::class,
63+
SmsCommunicatorPlugin.SmsCommunicatorWorker::class
64+
)
65+
}
66+
}

core/objects/src/androidMain/kotlin/app/aaps/core/objects/workflow/MetroWorkerCreator.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,24 @@ fun interface MetroWorkerCreator {
2727
fun create(context: Context, params: WorkerParameters): ListenableWorker
2828
}
2929

30+
/**
31+
* What a worker's nested `@AssistedFactory` extends, so it does not have to restate `create`.
32+
*
33+
* The shape Metro's own Android sample uses. Naming the worker as the type argument is what makes the
34+
* return type right by construction - with a bare [MetroWorkerCreator] each factory repeats the
35+
* signature and could declare the wrong worker type, which still compiles because the map only asks for
36+
* a [ListenableWorker].
37+
*
38+
* ```kotlin
39+
* @AssistedFactory
40+
* abstract class Factory : WorkerInstanceFactory<LoadBgWorker>()
41+
* ```
42+
*/
43+
abstract class WorkerInstanceFactory<T : ListenableWorker> : MetroWorkerCreator {
44+
45+
abstract override fun create(context: Context, params: WorkerParameters): T
46+
}
47+
3048
/**
3149
* The map key for the worker multibinding.
3250
*

implementation/src/main/kotlin/app/aaps/implementation/di/ImplementationModule.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ class ImplementationModule {
110110
@Binds fun bindLocalAlertUtilsInterface(localAlertUtils: LocalAlertUtilsImpl): LocalAlertUtils
111111
@Binds fun bindNotificationManager(notificationManagerImpl: NotificationManagerImpl): NotificationManager
112112
@Binds fun bindsProfileFunction(profileFunctionImpl: ProfileFunctionImpl): ProfileFunction
113-
@Binds fun bindsAutosensData(autosensDataObject: AutosensDataObject): AutosensData
114113
@Binds fun bindsAPSResult(determineBasalResult: DetermineBasalResult): APSResult
115114
@Binds fun bindsPumpEnactResult(pumpEnactResultObject: PumpEnactResultObject): PumpEnactResult
116115
}

implementation/src/main/kotlin/app/aaps/implementation/iob/AutosensDataObject.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ import app.aaps.core.interfaces.utils.DateUtil
88
import app.aaps.core.keys.DoubleKey
99
import app.aaps.core.keys.interfaces.Preferences
1010
import java.util.Locale
11+
import dev.zacsweers.metro.AppScope
12+
import dev.zacsweers.metro.ContributesBinding
1113
import javax.inject.Inject
1214
import kotlin.math.min
1315

16+
// Deliberately NOT @SingleIn: the @Binds this replaces had no scope. AutosensData is a value object
17+
// computed per run, so each caller is meant to get its own.
18+
@ContributesBinding(AppScope::class)
1419
class AutosensDataObject @Inject constructor(
1520
private val aapsLogger: AAPSLogger,
1621
private val preferences: Preferences,

plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/workers/DataSyncWorker.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package app.aaps.plugins.sync.nsclientV3.workers
22

33
import android.content.Context
4-
import androidx.hilt.work.HiltWorker
54
import androidx.work.WorkerParameters
65
import androidx.work.workDataOf
76
import app.aaps.core.data.time.T
@@ -11,15 +10,16 @@ import app.aaps.core.interfaces.nsclient.NSClientRepository
1110
import app.aaps.core.interfaces.plugin.ActivePlugin
1211
import app.aaps.core.interfaces.utils.fabric.FabricPrivacy
1312
import app.aaps.core.objects.workflow.LoggingWorker
13+
import app.aaps.core.objects.workflow.WorkerInstanceFactory
1414
import app.aaps.plugins.sync.nsclientV3.DataSyncSelectorV3
1515
import app.aaps.plugins.sync.nsclientV3.NSClientV3Plugin
16-
import dagger.assisted.Assisted
17-
import dagger.assisted.AssistedInject
16+
import dev.zacsweers.metro.Assisted
17+
import dev.zacsweers.metro.AssistedFactory
18+
import dev.zacsweers.metro.AssistedInject
1819
import kotlinx.coroutines.Dispatchers
1920
import kotlinx.coroutines.TimeoutCancellationException
2021
import kotlinx.coroutines.withTimeout
2122

22-
@HiltWorker
2323
class DataSyncWorker @AssistedInject constructor(
2424
@Assisted context: Context,
2525
@Assisted params: WorkerParameters,
@@ -63,4 +63,8 @@ class DataSyncWorker @AssistedInject constructor(
6363

6464
private val UPLOAD_TIMEOUT_MS = T.mins(30).msecs()
6565
}
66-
}
66+
67+
/** Metro builds the worker through this - WorkManager supplies context and params. */
68+
@AssistedFactory
69+
abstract class Factory : WorkerInstanceFactory<DataSyncWorker>()
70+
}

0 commit comments

Comments
 (0)