feat(di): enable Koin compile-time safety (#7127)

This commit is contained in:
James Rich authored and GitHub committed 2026-09-11 11:39:04 +00:00
1 parent 277d94033c
commit 2ea6c2e2fb
24 files changed
+838 -446

No files matched your search

@@ -16,6 +16,7 @@
*/
package org.meshtastic.app.di
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
import org.meshtastic.feature.discovery.ai.AlgorithmicSummaryProvider
@@ -29,6 +30,7 @@ import org.meshtastic.feature.messaging.translation.NoOpMessageTranslator
/** Provides keyword-only fallback AI assistant for the F-Droid flavor (no on-device model). */
@Module
@Configuration
class FdroidAiModule {
@Single fun aiDocAssistant(fallback: KeywordFallbackAssistant): AIDocAssistant = fallback
@@ -17,6 +17,7 @@
package org.meshtastic.app.di
import android.content.Context
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.Module
import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
@@ -29,6 +30,7 @@ import org.meshtastic.core.repository.AppFunctionsPrefs
/** Provides AppFunctions integration for the Google flavor. */
@Module
@Configuration
class AppFunctionsModule {
@Single
fun meshtasticAppFunctions(provider: AiFunctionProvider): MeshtasticAppFunctions = MeshtasticAppFunctions(provider)
@@ -19,6 +19,7 @@ package org.meshtastic.app.di
import android.content.Context
import okio.FileSystem
import okio.Path.Companion.toOkioPath
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
import org.meshtastic.app.ai.GeminiNanoDocAssistant
@@ -41,6 +42,7 @@ import org.meshtastic.feature.messaging.translation.MessageTranslationService
/** Provides the on-device Gemini Nano AI assistant for the Google flavor. */
@Module
@Configuration
class GoogleAiModule {
@Single
fun aiDocAssistant(
@@ -23,11 +23,13 @@ import androidx.datastore.preferences.preferencesDataStoreFile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
import org.meshtastic.core.di.CoroutineDispatchers
@Module
@Configuration
@ComponentScan("org.meshtastic.app.map")
class GoogleMapsKoinModule {
@@ -44,6 +44,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.koin.android.ext.android.get
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.workmanager.factory.KoinWorkerFactory
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.plugin.module.dsl.startKoin
import org.meshtastic.app.di.AndroidKoinApp
@@ -247,6 +248,10 @@ open class MeshUtilApplication :
)
}
/**
* Dead unless WorkManager falls back to on-demand init: [workManagerFactory] initializes it eagerly during
* [startKoin]. Constructed rather than resolved because nothing declares a [WorkerFactory] in the graph.
*/
override val workManagerConfiguration: Configuration
get() = Configuration.Builder().setWorkerFactory(get()).build()
get() = Configuration.Builder().setWorkerFactory(KoinWorkerFactory()).build()
}
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.app.di
import org.koin.plugin.module.dsl.koinApplication
import org.meshtastic.feature.docs.translation.DocTranslationService
import org.meshtastic.feature.docs.translation.NoOpDocTranslator
import org.meshtastic.feature.messaging.translation.MessageTranslationService
import org.meshtastic.feature.messaging.translation.NoOpMessageTranslator
import kotlin.test.Test
import kotlin.test.assertIs
class FdroidBindingWinnerTest {
@Test
fun `flavor bindings win over the shared graph`() {
// The flavor modules are @Configuration, which loads them before the ones listed in @KoinApplication, and
// Koin is last-wins. KoinVerificationTest only checks definitions exist, never which one survives, so a
// core-level default added later would silently take these over. Only the Fdroid no-ops are asserted:
// the Google flavor's MlKitMessageTranslator builds a RemoteModelManager in a field initializer.
val app = koinApplication<AndroidKoinApp>()
try {
val koin = app.koin
assertIs<NoOpMessageTranslator>(koin.get<MessageTranslationService>())
assertIs<NoOpDocTranslator>(koin.get<DocTranslationService>())
} finally {
app.close()
}
}
}