From d2b15c73b5ede9a2dabf5ef849dab4d80d60ef12 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Fri, 12 Dec 2025 08:23:35 -0600 Subject: [PATCH] refactor(di): Introduce @ProcessLifecycle qualifier for di (#3978) Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../com/geeksville/mesh/ApplicationModule.kt | 8 +++++-- .../com/geeksville/mesh/MeshServiceClient.kt | 17 +++++-------- .../bluetooth/BluetoothRepository.kt | 3 ++- .../repository/radio/RadioInterfaceService.kt | 3 ++- .../mesh/repository/usb/UsbRepository.kt | 3 ++- .../core/data/repository/NodeRepository.kt | 3 ++- .../meshtastic/core/di/ProcessLifecycle.kt | 24 +++++++++++++++++++ 7 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 core/di/src/main/kotlin/org/meshtastic/core/di/ProcessLifecycle.kt diff --git a/app/src/main/java/com/geeksville/mesh/ApplicationModule.kt b/app/src/main/java/com/geeksville/mesh/ApplicationModule.kt index e47b6e687..5c546f476 100644 --- a/app/src/main/java/com/geeksville/mesh/ApplicationModule.kt +++ b/app/src/main/java/com/geeksville/mesh/ApplicationModule.kt @@ -27,6 +27,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.meshtastic.core.common.BuildConfigProvider +import org.meshtastic.core.di.ProcessLifecycle import org.meshtastic.core.service.MeshServiceNotifications import javax.inject.Singleton @@ -37,10 +38,13 @@ interface ApplicationModule { @Binds fun bindMeshServiceNotifications(impl: MeshServiceNotificationsImpl): MeshServiceNotifications companion object { - @Provides fun provideProcessLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get() + @Provides @ProcessLifecycle + fun provideProcessLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get() @Provides - fun provideProcessLifecycle(processLifecycleOwner: LifecycleOwner): Lifecycle = processLifecycleOwner.lifecycle + @ProcessLifecycle + fun provideProcessLifecycle(@ProcessLifecycle processLifecycleOwner: LifecycleOwner): Lifecycle = + processLifecycleOwner.lifecycle @Singleton @Provides diff --git a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt index 5654b3262..8bced3f24 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt @@ -17,7 +17,7 @@ package com.geeksville.mesh -import android.app.Activity +import android.content.Context import androidx.appcompat.app.AppCompatActivity.BIND_ABOVE_CLIENT import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE import androidx.lifecycle.DefaultLifecycleObserver @@ -28,6 +28,7 @@ import com.geeksville.mesh.android.ServiceClient import com.geeksville.mesh.concurrent.handledLaunch import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.startService +import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.scopes.ActivityScoped import kotlinx.coroutines.Job import org.meshtastic.core.service.IMeshService @@ -40,18 +41,12 @@ import javax.inject.Inject class MeshServiceClient @Inject constructor( - /** - * Ideally, this would be broken up into Context and LifecycleOwner. However, ApplicationModule defines its own - * LifecycleOwner which overrides the default binding for @ActivityScoped. The solution to this is to add a - * qualifier to the LifecycleOwner provider in ApplicationModule. - */ - private val activity: Activity, + @ActivityContext private val context: Context, private val serviceRepository: ServiceRepository, ) : ServiceClient(IMeshService.Stub::asInterface), DefaultLifecycleObserver { - // TODO Use the default binding for @ActivityScoped - private val lifecycleOwner: LifecycleOwner = activity as LifecycleOwner + private val lifecycleOwner: LifecycleOwner = context as LifecycleOwner // TODO Inject this for ease of testing private var serviceSetupJob: Job? = null @@ -106,11 +101,11 @@ constructor( private fun bindMeshService() { Timber.d("Binding to mesh service!") try { - MeshService.startService(activity) + MeshService.startService(context) } catch (ex: Exception) { Timber.e("Failed to start service from activity - but ignoring because bind will work: ${ex.message}") } - connect(activity, MeshService.createIntent(activity), BIND_AUTO_CREATE + BIND_ABOVE_CLIENT) + connect(context, MeshService.createIntent(context), BIND_AUTO_CREATE + BIND_ABOVE_CLIENT) } } diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt index 6d75d142c..0544556b2 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt @@ -41,6 +41,7 @@ import no.nordicsemi.kotlin.ble.client.distinctByPeripheral import no.nordicsemi.kotlin.ble.core.Manager import org.meshtastic.core.common.hasBluetoothPermission import org.meshtastic.core.di.CoroutineDispatchers +import org.meshtastic.core.di.ProcessLifecycle import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -56,7 +57,7 @@ constructor( private val application: Application, private val bluetoothBroadcastReceiverLazy: Lazy, private val dispatchers: CoroutineDispatchers, - private val processLifecycle: Lifecycle, + @ProcessLifecycle private val processLifecycle: Lifecycle, private val centralManager: CentralManager, ) { private val _state = diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt index d163ae593..995db651b 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt @@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.meshtastic.core.analytics.platform.PlatformAnalytics import org.meshtastic.core.di.CoroutineDispatchers +import org.meshtastic.core.di.ProcessLifecycle import org.meshtastic.core.model.util.anonymize import org.meshtastic.core.prefs.radio.RadioPrefs import org.meshtastic.core.service.ConnectionState @@ -71,7 +72,7 @@ constructor( private val dispatchers: CoroutineDispatchers, private val bluetoothRepository: BluetoothRepository, private val networkRepository: NetworkRepository, - private val processLifecycle: Lifecycle, + @ProcessLifecycle private val processLifecycle: Lifecycle, private val radioPrefs: RadioPrefs, private val interfaceFactory: InterfaceFactory, private val analytics: PlatformAnalytics, diff --git a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt index 95d7d8a4c..cf01422d3 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbRepository.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.meshtastic.core.di.CoroutineDispatchers +import org.meshtastic.core.di.ProcessLifecycle import javax.inject.Inject import javax.inject.Singleton @@ -47,7 +48,7 @@ class UsbRepository constructor( private val application: Application, private val dispatchers: CoroutineDispatchers, - private val processLifecycle: Lifecycle, + @ProcessLifecycle private val processLifecycle: Lifecycle, private val usbBroadcastReceiverLazy: dagger.Lazy, private val usbManagerLazy: dagger.Lazy, private val usbSerialProberLazy: dagger.Lazy, diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt index ccb246098..6453b3069 100644 --- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt +++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/NodeRepository.kt @@ -40,6 +40,7 @@ import org.meshtastic.core.database.entity.NodeEntity import org.meshtastic.core.database.model.Node import org.meshtastic.core.database.model.NodeSortOption import org.meshtastic.core.di.CoroutineDispatchers +import org.meshtastic.core.di.ProcessLifecycle import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.util.onlineTimeThreshold import org.meshtastic.proto.MeshProtos @@ -51,7 +52,7 @@ import javax.inject.Singleton class NodeRepository @Inject constructor( - processLifecycle: Lifecycle, + @ProcessLifecycle processLifecycle: Lifecycle, private val nodeInfoReadDataSource: NodeInfoReadDataSource, private val nodeInfoWriteDataSource: NodeInfoWriteDataSource, private val dispatchers: CoroutineDispatchers, diff --git a/core/di/src/main/kotlin/org/meshtastic/core/di/ProcessLifecycle.kt b/core/di/src/main/kotlin/org/meshtastic/core/di/ProcessLifecycle.kt new file mode 100644 index 000000000..76311b20a --- /dev/null +++ b/core/di/src/main/kotlin/org/meshtastic/core/di/ProcessLifecycle.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 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 . + */ + +package org.meshtastic.core.di + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class ProcessLifecycle