From 1c43addeca7510db550a131c825bfb2e6a9f6f71 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:57:48 +0600 Subject: [PATCH] Resolve Injekt dependencies directly from the Metro graph (#3965) The previous setup had a circular dependency between Injekt registration and extension loading. In regard to #3931, I've moved the culprit ExtensionManager out of Injekt altogether, alongside CoverCache, PreferenceStore and TrackPreferences, none of which a source needs. --- .../eu/kanade/domain/manga/model/Manga.kt | 2 +- .../widget/AppThemePreferenceWidget.kt | 5 - app/src/main/java/eu/kanade/tachiyomi/App.kt | 14 +-- .../extension/installer/Installer.kt | 4 +- app/src/main/java/mihon/app/di/AppGraph.kt | 8 ++ .../app/di/injekt/MetroInjektRegistrar.kt | 100 ++++++++++++++++++ .../mihon/app/di/injekt/MetroInteropModule.kt | 49 --------- 7 files changed, 114 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/mihon/app/di/injekt/MetroInjektRegistrar.kt delete mode 100644 app/src/main/java/mihon/app/di/injekt/MetroInteropModule.kt diff --git a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt index d204bca91..5eb4fed81 100644 --- a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt @@ -72,7 +72,7 @@ fun Manga.copyFrom(other: SManga): Manga { ) } -fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean { +fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get().appGraph.coverCache): Boolean { return coverCache.getCustomCoverFile(id).exists() } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt index c5dbd1a06..fc76caf94 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat -import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.model.AppTheme import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.theme.TachiyomiTheme @@ -48,13 +47,10 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable import mihon.icons.materialsymbols.MaterialSymbols import mihon.icons.materialsymbols.roundedfilled.CheckCircle -import tachiyomi.core.common.preference.InMemoryPreferenceStore import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.secondaryItemAlpha -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.fullType @Composable internal fun AppThemePreferenceWidget( @@ -261,7 +257,6 @@ fun AppThemePreviewItem( @Composable private fun AppThemesListPreview() { var appTheme by remember { mutableStateOf(AppTheme.DEFAULT) } - Injekt.addSingleton(fullType(), UiPreferences(InMemoryPreferenceStore())) TachiyomiTheme(appTheme = appTheme) { Surface { AppThemesList( diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 8df696d4b..29a17c8b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -52,7 +52,7 @@ import logcat.AndroidLogcatLogger import logcat.LogPriority import logcat.LogcatLogger import mihon.app.di.AppGraph -import mihon.app.di.injekt.MetroInteropModule +import mihon.app.di.injekt.MetroInjektRegistrar import mihon.core.metro.GraphProvider import mihon.core.migration.Migration import mihon.core.migration.Migrator @@ -66,7 +66,7 @@ import tachiyomi.domain.source.service.SourceManager import tachiyomi.i18n.MR import tachiyomi.presentation.widget.WidgetManager import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.addSingleton +import uy.kohesive.injekt.api.InjektScope import java.security.Security class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory, GraphProvider { @@ -93,8 +93,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor @Inject private lateinit var widgetManager: WidgetManager - @Inject private lateinit var injektMetroInteropModule: MetroInteropModule - @Inject private lateinit var migrations: Set private val disableIncognitoReceiver = DisableIncognitoReceiver() @@ -110,8 +108,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor if (packageName != process) WebView.setDataDirectorySuffix(process) } + Injekt = InjektScope(MetroInjektRegistrar(application = this, graphProvider = this)) graph.inject(this) - setupInjekt() TelemetryConfig.init(applicationContext) @@ -185,12 +183,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor initializeMigrator() } - private fun setupInjekt() { - Injekt.addSingleton(this) - Injekt.addSingleton(this) - Injekt.importModule(injektMetroInteropModule) - } - private fun initializeMigrator() { val preference = preferenceStore.getInt(Preference.appStateKey("last_version_code"), 0) logcat { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt index 7a22871e2..794b0885a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt @@ -5,7 +5,7 @@ import android.net.Uri import androidx.annotation.CallSuper import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.model.InstallStep -import uy.kohesive.injekt.injectLazy +import mihon.app.di.appGraph import java.util.Collections import java.util.concurrent.CopyOnWriteArraySet import kotlin.concurrent.atomics.AtomicReference @@ -17,7 +17,7 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi @OptIn(ExperimentalAtomicApi::class) abstract class Installer(private val service: Service) { - private val extensionManager: ExtensionManager by injectLazy() + private val extensionManager: ExtensionManager by lazy { service.appGraph.extensionManager } private var waitingInstall = AtomicReference(null) private val queue = Collections.synchronizedList(mutableListOf()) diff --git a/app/src/main/java/mihon/app/di/AppGraph.kt b/app/src/main/java/mihon/app/di/AppGraph.kt index e31f57af1..808f11dea 100644 --- a/app/src/main/java/mihon/app/di/AppGraph.kt +++ b/app/src/main/java/mihon/app/di/AppGraph.kt @@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.data.cache.ChapterCache +import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadJob import eu.kanade.tachiyomi.data.download.DownloadManager @@ -29,6 +30,7 @@ import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.util.ExtensionInstallActivity +import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegateImpl @@ -39,8 +41,10 @@ import eu.kanade.tachiyomi.ui.setting.track.BaseOAuthLoginActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.CrashLogUtil import kotlinx.serialization.json.Json +import kotlinx.serialization.protobuf.ProtoBuf import mihon.core.metro.IsDebugBuild import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow +import nl.adaptivity.xmlutil.serialization.XML import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.ResetCategoryFlags @@ -101,10 +105,14 @@ interface AppGraph : ViewModelGraph { val trackerManager: TrackerManager val extensionManager: ExtensionManager val chapterCache: ChapterCache + val coverCache: CoverCache val downloadCache: DownloadCache val json: Json + val protoBuf: ProtoBuf + val xml: XML val networkHelper: NetworkHelper + val javaScriptEngine: JavaScriptEngine val getFavorites: GetFavorites val getCategories: GetCategories diff --git a/app/src/main/java/mihon/app/di/injekt/MetroInjektRegistrar.kt b/app/src/main/java/mihon/app/di/injekt/MetroInjektRegistrar.kt new file mode 100644 index 000000000..69878c13b --- /dev/null +++ b/app/src/main/java/mihon/app/di/injekt/MetroInjektRegistrar.kt @@ -0,0 +1,100 @@ +package mihon.app.di.injekt + +import android.app.Application +import android.content.Context +import eu.kanade.tachiyomi.network.JavaScriptEngine +import eu.kanade.tachiyomi.network.NetworkHelper +import kotlinx.serialization.json.Json +import kotlinx.serialization.protobuf.ProtoBuf +import mihon.app.di.AppGraph +import mihon.core.metro.GraphProvider +import nl.adaptivity.xmlutil.serialization.XML +import uy.kohesive.injekt.api.InjektModule +import uy.kohesive.injekt.api.InjektRegistrar +import uy.kohesive.injekt.api.InjektionException +import uy.kohesive.injekt.api.TypeReference +import java.lang.reflect.Type + +class MetroInjektRegistrar( + private val application: Application, + private val graphProvider: GraphProvider, +) : InjektRegistrar { + + private val graph: AppGraph inline get() = graphProvider.graph + + private val bindings = mapOf Any>( + Application::class.java to { application }, + Context::class.java to { application }, + + Json::class.java to { graph.json }, + ProtoBuf::class.java to { graph.protoBuf }, + XML::class.java to { graph.xml }, + + NetworkHelper::class.java to { graph.networkHelper }, + JavaScriptEngine::class.java to { graph.javaScriptEngine }, + ) + + override fun getInstance(forType: Type): R = getInstanceOrNull(forType) + ?: throw InjektionException("$forType is not exposed to Injekt, add it to ${this::class.simpleName}") + + override fun getInstanceOrElse(forType: Type, default: R): R = getInstanceOrNull(forType) ?: default + + override fun getInstanceOrElse(forType: Type, default: () -> R): R = + getInstanceOrNull(forType) ?: default() + + @Suppress("UNCHECKED_CAST") + override fun getInstanceOrNull(forType: Type): R? = bindings[forType]?.invoke() as R? + + override fun getKeyedInstance(forType: Type, key: K): R = unsupported("Keyed injection") + + override fun getKeyedInstanceOrElse(forType: Type, key: K, default: R): R = + unsupported("Keyed injection") + + override fun getKeyedInstanceOrElse(forType: Type, key: K, default: () -> R): R = + unsupported("Keyed injection") + + @Suppress("RedundantNullableReturnType") + override fun getKeyedInstanceOrNull(forType: Type, key: K): R? = unsupported("Keyed injection") + + override fun getLogger(expectedLoggerType: Type, byName: String): R = unsupported("Logger injection") + + override fun getLogger(expectedLoggerType: Type, forClass: Class): R = + unsupported("Logger injection") + + override fun addSingleton(forType: TypeReference, singleInstance: T) = readOnly() + + override fun addSingletonFactory(forType: TypeReference, factoryCalledOnce: () -> R) = readOnly() + + override fun addFactory(forType: TypeReference, factoryCalledEveryTime: () -> R) = readOnly() + + override fun addPerThreadFactory(forType: TypeReference, factoryCalledOncePerThread: () -> R) = + readOnly() + + override fun addPerKeyFactory(forType: TypeReference, factoryCalledPerKey: (K) -> R) = + readOnly() + + override fun addPerThreadPerKeyFactory( + forType: TypeReference, + factoryCalledPerKeyPerThread: (K) -> R, + ) = readOnly() + + override fun addLoggerFactory( + forLoggerType: TypeReference, + factoryByName: (String) -> R, + factoryByClass: (Class) -> R, + ) = readOnly() + + override fun addAlias( + existingRegisteredType: TypeReference, + otherAncestorOrInterface: TypeReference, + ) = readOnly() + + override fun hasFactory(forType: TypeReference): Boolean = forType.type in bindings + + override fun importModule(submodule: InjektModule) = readOnly() + + private fun readOnly(): Nothing = throw UnsupportedOperationException("Mihon's Injekt instance is read-only") + + private fun unsupported(feature: String): Nothing = + throw UnsupportedOperationException("$feature is not supported by Mihon's Injekt instance") +} diff --git a/app/src/main/java/mihon/app/di/injekt/MetroInteropModule.kt b/app/src/main/java/mihon/app/di/injekt/MetroInteropModule.kt deleted file mode 100644 index 7758f8f6f..000000000 --- a/app/src/main/java/mihon/app/di/injekt/MetroInteropModule.kt +++ /dev/null @@ -1,49 +0,0 @@ -package mihon.app.di.injekt - -import dev.zacsweers.metro.Inject -import eu.kanade.domain.track.service.TrackPreferences -import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.extension.ExtensionManager -import eu.kanade.tachiyomi.network.JavaScriptEngine -import eu.kanade.tachiyomi.network.NetworkHelper -import kotlinx.serialization.json.Json -import kotlinx.serialization.protobuf.ProtoBuf -import nl.adaptivity.xmlutil.serialization.XML -import tachiyomi.core.common.preference.PreferenceStore -import uy.kohesive.injekt.api.InjektModule -import uy.kohesive.injekt.api.InjektRegistrar -import uy.kohesive.injekt.api.addSingleton - -@Inject -class MetroInteropModule( - private val json: Json, - private val protoBuf: ProtoBuf, - private val xml: XML, - - private val networkHelper: NetworkHelper, - private val javaScriptEngine: JavaScriptEngine, - - private val preferenceStore: PreferenceStore, - private val trackPreferences: TrackPreferences, - - private val extensionManager: ExtensionManager, - - private val coverCache: CoverCache, -) : InjektModule { - - override fun InjektRegistrar.registerInjectables() { - addSingleton(json) - addSingleton(protoBuf) - addSingleton(xml) - - addSingleton(networkHelper) - addSingleton(javaScriptEngine) - - addSingleton(preferenceStore) - addSingleton(trackPreferences) - - addSingleton(extensionManager) - - addSingleton(coverCache) - } -}