From 9bf4b237dd0e86d5c26c98605391f177e5ae191d Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sat, 15 Nov 2025 22:33:41 -0600 Subject: [PATCH] refactor(coroutines): Use SupervisorJobs (#3714) Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../repository/radio/RadioInterfaceService.kt | 6 +- .../core/datastore/di/DataStoreModule.kt | 107 +++++++++++------- 2 files changed, 66 insertions(+), 47 deletions(-) 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 f78516889..c85a27069 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 @@ -31,7 +31,7 @@ import com.geeksville.mesh.util.ignoreException import com.geeksville.mesh.util.toRemoteExceptions import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow @@ -105,7 +105,7 @@ constructor( val mockInterfaceAddress: String by lazy { toInterfaceAddress(InterfaceId.MOCK, "") } /** We recreate this scope each time we stop an interface */ - var serviceScope = CoroutineScope(Dispatchers.IO + Job()) + var serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var radioIf: IRadioInterface = NopInterface("") @@ -298,7 +298,7 @@ constructor( // cancel any old jobs and get ready for the new ones serviceScope.cancel("stopping interface") - serviceScope = CoroutineScope(Dispatchers.IO + Job()) + serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) if (logSends) { sentPacketsLog.close() diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt index 051954933..47e2c8664 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/di/DataStoreModule.kt @@ -48,68 +48,87 @@ import org.meshtastic.core.datastore.serializer.ModuleConfigSerializer import org.meshtastic.proto.AppOnlyProtos.ChannelSet import org.meshtastic.proto.LocalOnlyProtos.LocalConfig import org.meshtastic.proto.LocalOnlyProtos.LocalModuleConfig +import javax.inject.Qualifier import javax.inject.Singleton private const val USER_PREFERENCES_NAME = "user_preferences" +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class DataStoreScope + @InstallIn(SingletonComponent::class) @Module object DataStoreModule { + + @Provides + @Singleton + @DataStoreScope + fun provideDataStoreScope(): CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + @Singleton @Provides - fun providePreferencesDataStore(@ApplicationContext appContext: Context): DataStore = - PreferenceDataStoreFactory.create( - corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }), - migrations = - listOf( - SharedPreferencesMigration(context = appContext, sharedPreferencesName = USER_PREFERENCES_NAME), - SharedPreferencesMigration( - context = appContext, - sharedPreferencesName = "ui-prefs", - keysToMigrate = - setOf( - KEY_APP_INTRO_COMPLETED, - KEY_THEME, - KEY_NODE_SORT, - KEY_INCLUDE_UNKNOWN, - KEY_ONLY_ONLINE, - KEY_ONLY_DIRECT, - KEY_SHOW_IGNORED, - ), + fun providePreferencesDataStore( + @ApplicationContext appContext: Context, + @DataStoreScope scope: CoroutineScope, + ): DataStore = PreferenceDataStoreFactory.create( + corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }), + migrations = + listOf( + SharedPreferencesMigration(context = appContext, sharedPreferencesName = USER_PREFERENCES_NAME), + SharedPreferencesMigration( + context = appContext, + sharedPreferencesName = "ui-prefs", + keysToMigrate = + setOf( + KEY_APP_INTRO_COMPLETED, + KEY_THEME, + KEY_NODE_SORT, + KEY_INCLUDE_UNKNOWN, + KEY_ONLY_ONLINE, + KEY_ONLY_DIRECT, + KEY_SHOW_IGNORED, ), ), - scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), - produceFile = { appContext.preferencesDataStoreFile(USER_PREFERENCES_NAME) }, - ) + ), + scope = scope, + produceFile = { appContext.preferencesDataStoreFile(USER_PREFERENCES_NAME) }, + ) @Singleton @Provides - fun provideLocalConfigDataStore(@ApplicationContext appContext: Context): DataStore = - DataStoreFactory.create( - serializer = LocalConfigSerializer, - produceFile = { appContext.dataStoreFile("local_config.pb") }, - corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalConfig.getDefaultInstance() }), - scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), - ) + fun provideLocalConfigDataStore( + @ApplicationContext appContext: Context, + @DataStoreScope scope: CoroutineScope, + ): DataStore = DataStoreFactory.create( + serializer = LocalConfigSerializer, + produceFile = { appContext.dataStoreFile("local_config.pb") }, + corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { LocalConfig.getDefaultInstance() }), + scope = scope, + ) @Singleton @Provides - fun provideModuleConfigDataStore(@ApplicationContext appContext: Context): DataStore = - DataStoreFactory.create( - serializer = ModuleConfigSerializer, - produceFile = { appContext.dataStoreFile("module_config.pb") }, - corruptionHandler = - ReplaceFileCorruptionHandler(produceNewData = { LocalModuleConfig.getDefaultInstance() }), - scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), - ) + fun provideModuleConfigDataStore( + @ApplicationContext appContext: Context, + @DataStoreScope scope: CoroutineScope, + ): DataStore = DataStoreFactory.create( + serializer = ModuleConfigSerializer, + produceFile = { appContext.dataStoreFile("module_config.pb") }, + corruptionHandler = + ReplaceFileCorruptionHandler(produceNewData = { LocalModuleConfig.getDefaultInstance() }), + scope = scope, + ) @Singleton @Provides - fun provideChannelSetDataStore(@ApplicationContext appContext: Context): DataStore = - DataStoreFactory.create( - serializer = ChannelSetSerializer, - produceFile = { appContext.dataStoreFile("channel_set.pb") }, - corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { ChannelSet.getDefaultInstance() }), - scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), - ) + fun provideChannelSetDataStore( + @ApplicationContext appContext: Context, + @DataStoreScope scope: CoroutineScope, + ): DataStore = DataStoreFactory.create( + serializer = ChannelSetSerializer, + produceFile = { appContext.dataStoreFile("channel_set.pb") }, + corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { ChannelSet.getDefaultInstance() }), + scope = scope, + ) }