From 7744a42e1c2661e59bb1bc6b5a3796354780dc00 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Thu, 8 Jan 2026 17:40:26 -0600
Subject: [PATCH] refactor(deps): inject CoroutineDispatchers (#4170)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
.../com/geeksville/mesh/MeshServiceClient.kt | 16 ++++++-----
.../geeksville/mesh/android/ServiceClient.kt | 8 +++---
.../repository/radio/RadioInterfaceService.kt | 8 ++----
.../mesh/repository/radio/TCPInterface.kt | 16 +++++++----
.../mesh/ui/contact/ContactsViewModel.kt | 8 ++----
.../DeviceHardwareLocalDataSource.kt | 18 +++++++-----
.../FirmwareReleaseLocalDataSource.kt | 18 +++++++-----
.../repository/DeviceHardwareRepository.kt | 8 +++---
.../data/repository/LocationRepository.kt | 8 +++---
core/database/build.gradle.kts | 1 +
.../core/database/DatabaseManager.kt | 28 ++++++++++---------
core/network/build.gradle.kts | 3 +-
.../network/DeviceHardwareRemoteDataSource.kt | 14 ++++++----
.../FirmwareReleaseRemoteDataSource.kt | 14 ++++++----
.../composeResources/values/strings.xml | 1 -
.../org/meshtastic/core/ui/util/FormatAgo.kt | 22 +++++----------
16 files changed, 102 insertions(+), 89 deletions(-)
diff --git a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt
index 94cd1a191..102980251 100644
--- a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt
+++ b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh
import android.content.Context
@@ -31,6 +30,7 @@ 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.launch
import org.meshtastic.core.service.IMeshService
import org.meshtastic.core.service.ServiceRepository
import javax.inject.Inject
@@ -75,10 +75,12 @@ constructor(
super.onStart(owner)
Logger.d { "Lifecycle: ON_START" }
- try {
- bindMeshService()
- } catch (ex: BindFailedException) {
- Logger.e { "Bind of MeshService failed: ${ex.message}" }
+ owner.lifecycleScope.launch {
+ try {
+ bindMeshService()
+ } catch (ex: BindFailedException) {
+ Logger.e { "Bind of MeshService failed: ${ex.message}" }
+ }
}
}
@@ -93,7 +95,7 @@ constructor(
// endregion
@Suppress("TooGenericExceptionCaught")
- private fun bindMeshService() {
+ private suspend fun bindMeshService() {
Logger.d { "Binding to mesh service!" }
try {
MeshService.startService(context)
diff --git a/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt b/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt
index 3216f7daa..65c7373d4 100644
--- a/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt
+++ b/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh.android
import android.content.ComponentName
@@ -25,6 +24,7 @@ import android.os.IBinder
import android.os.IInterface
import co.touchlab.kermit.Logger
import com.geeksville.mesh.util.exceptionReporter
+import kotlinx.coroutines.delay
import java.io.Closeable
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@@ -64,7 +64,7 @@ open class ServiceClient(private val stubFactory: (IBinder) -> T
}
}
- fun connect(c: Context, intent: Intent, flags: Int) {
+ suspend fun connect(c: Context, intent: Intent, flags: Int) {
context = c
if (isClosed) {
isClosed = false
@@ -73,7 +73,7 @@ open class ServiceClient(private val stubFactory: (IBinder) -> T
// Try
// a short sleep to see if that helps
Logger.e { "Needed to use the second bind attempt hack" }
- Thread.sleep(500) // was 200ms, but received an autobug from a Galaxy Note4, android 6.0.1
+ delay(500) // was 200ms, but received an autobug from a Galaxy Note4, android 6.0.1
if (!c.bindService(intent, connection, flags)) {
throw BindFailedException()
}
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 b34756481..2d86d46f7 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
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh.repository.radio
import android.app.Application
@@ -31,7 +30,6 @@ import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.util.ignoreException
import com.geeksville.mesh.util.toRemoteExceptions
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.BufferOverflow
@@ -99,7 +97,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 + SupervisorJob())
+ var serviceScope = CoroutineScope(dispatchers.io + SupervisorJob())
private var radioIf: IRadioInterface = NopInterface("")
@@ -292,7 +290,7 @@ constructor(
// cancel any old jobs and get ready for the new ones
serviceScope.cancel("stopping interface")
- serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+ serviceScope = CoroutineScope(dispatchers.io + SupervisorJob())
if (logSends) {
sentPacketsLog.close()
diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt
index 68333275e..3b587705b 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh.repository.radio
import co.touchlab.kermit.Logger
@@ -23,9 +22,9 @@ import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.util.Exceptions
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
+import org.meshtastic.core.di.CoroutineDispatchers
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.IOException
@@ -34,8 +33,13 @@ import java.net.InetAddress
import java.net.Socket
import java.net.SocketTimeoutException
-class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @Assisted private val address: String) :
- StreamInterface(service) {
+class TCPInterface
+@AssistedInject
+constructor(
+ service: RadioInterfaceService,
+ private val dispatchers: CoroutineDispatchers,
+ @Assisted private val address: String,
+) : StreamInterface(service) {
companion object {
const val MAX_RETRIES_ALLOWED = Int.MAX_VALUE
@@ -143,7 +147,7 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
}
// Create a socket to make the connection with the server
- private suspend fun startConnect() = withContext(Dispatchers.IO) {
+ private suspend fun startConnect() = withContext(dispatchers.io) {
val attemptStart = System.currentTimeMillis()
Logger.i { "[$address] TCP connection attempt starting..." }
diff --git a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt
index 7db9d03db..1806b6dde 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh.ui.contact
import androidx.lifecycle.ViewModel
@@ -30,7 +29,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import org.jetbrains.compose.resources.getString
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository
@@ -163,8 +161,8 @@ constructor(
longName = longName,
lastMessageTime = getShortDate(data.time),
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
- unreadCount = runBlocking(Dispatchers.IO) { packetRepository.getUnreadCount(contactKey) },
- messageCount = runBlocking(Dispatchers.IO) { packetRepository.getMessageCount(contactKey) },
+ unreadCount = packetRepository.getUnreadCount(contactKey),
+ messageCount = packetRepository.getMessageCount(contactKey),
isMuted = settings[contactKey]?.isMuted == true,
isUnmessageable = user.isUnmessagable,
nodeColors =
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareLocalDataSource.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareLocalDataSource.kt
index 00b36aba0..13d61526a 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareLocalDataSource.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/DeviceHardwareLocalDataSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,27 +14,31 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.core.data.datasource
import dagger.Lazy
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.database.dao.DeviceHardwareDao
import org.meshtastic.core.database.entity.DeviceHardwareEntity
import org.meshtastic.core.database.entity.asEntity
+import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.NetworkDeviceHardware
import javax.inject.Inject
-class DeviceHardwareLocalDataSource @Inject constructor(private val deviceHardwareDaoLazy: Lazy) {
+class DeviceHardwareLocalDataSource
+@Inject
+constructor(
+ private val deviceHardwareDaoLazy: Lazy,
+ private val dispatchers: CoroutineDispatchers,
+) {
private val deviceHardwareDao by lazy { deviceHardwareDaoLazy.get() }
- suspend fun insertAllDeviceHardware(deviceHardware: List) = withContext(Dispatchers.IO) {
+ suspend fun insertAllDeviceHardware(deviceHardware: List) = withContext(dispatchers.io) {
deviceHardware.forEach { deviceHardware -> deviceHardwareDao.insert(deviceHardware.asEntity()) }
}
- suspend fun deleteAllDeviceHardware() = withContext(Dispatchers.IO) { deviceHardwareDao.deleteAll() }
+ suspend fun deleteAllDeviceHardware() = withContext(dispatchers.io) { deviceHardwareDao.deleteAll() }
suspend fun getByHwModel(hwModel: Int): DeviceHardwareEntity? =
- withContext(Dispatchers.IO) { deviceHardwareDao.getByHwModel(hwModel) }
+ withContext(dispatchers.io) { deviceHardwareDao.getByHwModel(hwModel) }
}
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseLocalDataSource.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseLocalDataSource.kt
index 5fbcca789..dff3b0171 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseLocalDataSource.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/FirmwareReleaseLocalDataSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,36 +14,40 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.core.data.datasource
import dagger.Lazy
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.database.dao.FirmwareReleaseDao
import org.meshtastic.core.database.entity.FirmwareReleaseEntity
import org.meshtastic.core.database.entity.FirmwareReleaseType
import org.meshtastic.core.database.entity.asDeviceVersion
import org.meshtastic.core.database.entity.asEntity
+import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.NetworkFirmwareRelease
import javax.inject.Inject
-class FirmwareReleaseLocalDataSource @Inject constructor(private val firmwareReleaseDaoLazy: Lazy) {
+class FirmwareReleaseLocalDataSource
+@Inject
+constructor(
+ private val firmwareReleaseDaoLazy: Lazy,
+ private val dispatchers: CoroutineDispatchers,
+) {
private val firmwareReleaseDao by lazy { firmwareReleaseDaoLazy.get() }
suspend fun insertFirmwareReleases(
firmwareReleases: List,
releaseType: FirmwareReleaseType,
- ) = withContext(Dispatchers.IO) {
+ ) = withContext(dispatchers.io) {
firmwareReleases.forEach { firmwareRelease ->
firmwareReleaseDao.insert(firmwareRelease.asEntity(releaseType))
}
}
- suspend fun deleteAllFirmwareReleases() = withContext(Dispatchers.IO) { firmwareReleaseDao.deleteAll() }
+ suspend fun deleteAllFirmwareReleases() = withContext(dispatchers.io) { firmwareReleaseDao.deleteAll() }
suspend fun getLatestRelease(releaseType: FirmwareReleaseType): FirmwareReleaseEntity? =
- withContext(Dispatchers.IO) {
+ withContext(dispatchers.io) {
val releases = firmwareReleaseDao.getReleasesByType(releaseType)
if (releases.isEmpty()) {
return@withContext null
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt
index f527ae6c8..30cc4c31a 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,17 +14,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.core.data.repository
import co.touchlab.kermit.Logger
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.data.datasource.BootloaderOtaQuirksJsonDataSource
import org.meshtastic.core.data.datasource.DeviceHardwareJsonDataSource
import org.meshtastic.core.data.datasource.DeviceHardwareLocalDataSource
import org.meshtastic.core.database.entity.DeviceHardwareEntity
import org.meshtastic.core.database.entity.asExternalModel
+import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.BootloaderOtaQuirk
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.network.DeviceHardwareRemoteDataSource
@@ -41,6 +40,7 @@ constructor(
private val localDataSource: DeviceHardwareLocalDataSource,
private val jsonDataSource: DeviceHardwareJsonDataSource,
private val bootloaderOtaQuirksJsonDataSource: BootloaderOtaQuirksJsonDataSource,
+ private val dispatchers: CoroutineDispatchers,
) {
/**
@@ -58,7 +58,7 @@ constructor(
*/
@Suppress("LongMethod")
suspend fun getDeviceHardwareByModel(hwModel: Int, forceRefresh: Boolean = false): Result =
- withContext(Dispatchers.IO) {
+ withContext(dispatchers.io) {
Logger.d {
"DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=$hwModel, forceRefresh=$forceRefresh)"
}
diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt
index 35d087bf3..a1b7b8a5a 100644
--- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt
+++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.core.data.repository
import android.Manifest.permission.ACCESS_COARSE_LOCATION
@@ -28,13 +27,13 @@ import androidx.core.location.LocationManagerCompat
import androidx.core.location.LocationRequestCompat
import androidx.core.location.altitude.AltitudeConverterCompat
import co.touchlab.kermit.Logger
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import org.meshtastic.core.analytics.platform.PlatformAnalytics
+import org.meshtastic.core.di.CoroutineDispatchers
import javax.inject.Inject
import javax.inject.Singleton
@@ -45,6 +44,7 @@ constructor(
private val context: Application,
private val locationManager: dagger.Lazy,
private val analytics: PlatformAnalytics,
+ private val dispatchers: CoroutineDispatchers,
) {
/** Status of whether the app is actively subscribed to location changes. */
@@ -97,7 +97,7 @@ constructor(
this@requestLocationUpdates,
provider,
locationRequest,
- Dispatchers.IO.asExecutor(),
+ dispatchers.io.asExecutor(),
locationListener,
)
}
diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts
index ce58ce582..00c914148 100644
--- a/core/database/build.gradle.kts
+++ b/core/database/build.gradle.kts
@@ -49,6 +49,7 @@ configure {
}
dependencies {
+ implementation(projects.core.di)
implementation(projects.core.model)
implementation(projects.core.proto)
implementation(projects.core.strings)
diff --git a/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt b/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt
index 4c8979467..27f1a26fb 100644
--- a/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt
+++ b/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt
@@ -22,7 +22,6 @@ import android.content.SharedPreferences
import androidx.room.Room
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -33,6 +32,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
+import org.meshtastic.core.di.CoroutineDispatchers
import java.io.File
import java.security.MessageDigest
import javax.inject.Inject
@@ -40,9 +40,9 @@ import javax.inject.Singleton
/** Manages per-device Room database instances for node data, with LRU eviction. */
@Singleton
-class DatabaseManager @Inject constructor(private val app: Application) {
+class DatabaseManager @Inject constructor(private val app: Application, private val dispatchers: CoroutineDispatchers) {
val prefs: SharedPreferences = app.getSharedPreferences("db-manager-prefs", Context.MODE_PRIVATE)
- private val managerScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+ private val managerScope = CoroutineScope(SupervisorJob() + dispatchers.default)
private val mutex = Mutex()
@@ -72,25 +72,27 @@ class DatabaseManager @Inject constructor(private val app: Application) {
private val dbCache = mutableMapOf() // key = dbName
/** Initialize the active database for [address]. */
- suspend fun init(address: String?) = switchActiveDatabase(address)
+ suspend fun init(address: String?) {
+ switchActiveDatabase(address)
+ }
/** Switch active database to the one associated with [address]. Serialized via mutex. */
suspend fun switchActiveDatabase(address: String?) = mutex.withLock {
val dbName = buildDbName(address)
- // Remember the previously active DB name (if any) so we can record its last-used time as well.
+ // Remember the previously active DB name (any) so we can record its last-used time as well.
val previousDbName = _currentDb.value?.openHelper?.databaseName
// Fast path: no-op if already on this address
if (_currentAddress.value == address && _currentDb.value != null) {
markLastUsed(dbName)
- return
+ return@withLock
}
// Build/open Room DB off the main thread
val db =
dbCache[dbName]
- ?: withContext(Dispatchers.IO) { buildRoomDb(app, dbName) }.also { dbCache[dbName] = it }
+ ?: withContext(dispatchers.io) { buildRoomDb(app, dbName) }.also { dbCache[dbName] = it }
_currentDb.value = db
_currentAddress.value = address
@@ -100,10 +102,10 @@ class DatabaseManager @Inject constructor(private val app: Application) {
previousDbName?.let { markLastUsed(it) }
// Defer LRU eviction so switch is not blocked by filesystem work
- managerScope.launch(Dispatchers.IO) { enforceCacheLimit(activeDbName = dbName) }
+ managerScope.launch(dispatchers.io) { enforceCacheLimit(activeDbName = dbName) }
// One-time cleanup: remove legacy DB if present and not active
- managerScope.launch(Dispatchers.IO) { cleanupLegacyDbIfNeeded(activeDbName = dbName) }
+ managerScope.launch(dispatchers.io) { cleanupLegacyDbIfNeeded(activeDbName = dbName) }
Logger.i { "Switched active DB to ${anonymizeDbName(dbName)} for address ${anonymizeAddress(address)}" }
}
@@ -144,7 +146,7 @@ class DatabaseManager @Inject constructor(private val app: Application) {
anonymizeDbName(it)
}}"
}
- if (deviceDbs.size <= limit) return
+ if (deviceDbs.size <= limit) return@withLock
val usageSnapshot = deviceDbs.associateWith { lastUsed(it) }
Logger.d {
"LRU lastUsed(ms): ${usageSnapshot.entries.joinToString(", ") { (name, ts) ->
@@ -173,16 +175,16 @@ class DatabaseManager @Inject constructor(private val app: Application) {
_cacheLimit.value = clamped
// Enforce asynchronously with current active DB protected
val active = _currentDb.value?.openHelper?.databaseName ?: defaultDbName()
- managerScope.launch(Dispatchers.IO) { enforceCacheLimit(activeDbName = active) }
+ managerScope.launch(dispatchers.io) { enforceCacheLimit(activeDbName = active) }
}
private suspend fun cleanupLegacyDbIfNeeded(activeDbName: String) = mutex.withLock {
- if (prefs.getBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, false)) return
+ if (prefs.getBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, false)) return@withLock
val legacy = DatabaseConstants.LEGACY_DB_NAME
if (legacy == activeDbName) {
// Never delete the active DB; mark as cleaned to avoid repeated checks
prefs.edit().putBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, true).apply()
- return
+ return@withLock
}
val legacyFile = getDbFile(app, legacy)
if (legacyFile != null) {
diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts
index d1c4414ad..54a7fd9fb 100644
--- a/core/network/build.gradle.kts
+++ b/core/network/build.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -48,6 +48,7 @@ configure {
}
dependencies {
+ implementation(projects.core.di)
implementation(projects.core.model)
implementation(libs.coil.network.core)
diff --git a/core/network/src/main/kotlin/org/meshtastic/core/network/DeviceHardwareRemoteDataSource.kt b/core/network/src/main/kotlin/org/meshtastic/core/network/DeviceHardwareRemoteDataSource.kt
index 1b436fc3f..826de8c12 100644
--- a/core/network/src/main/kotlin/org/meshtastic/core/network/DeviceHardwareRemoteDataSource.kt
+++ b/core/network/src/main/kotlin/org/meshtastic/core/network/DeviceHardwareRemoteDataSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,16 +14,20 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.core.network
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.NetworkDeviceHardware
import org.meshtastic.core.network.service.ApiService
import javax.inject.Inject
-class DeviceHardwareRemoteDataSource @Inject constructor(private val apiService: ApiService) {
+class DeviceHardwareRemoteDataSource
+@Inject
+constructor(
+ private val apiService: ApiService,
+ private val dispatchers: CoroutineDispatchers,
+) {
suspend fun getAllDeviceHardware(): List =
- withContext(Dispatchers.IO) { apiService.getDeviceHardware() }
+ withContext(dispatchers.io) { apiService.getDeviceHardware() }
}
diff --git a/core/network/src/main/kotlin/org/meshtastic/core/network/FirmwareReleaseRemoteDataSource.kt b/core/network/src/main/kotlin/org/meshtastic/core/network/FirmwareReleaseRemoteDataSource.kt
index fa25da247..056cdce43 100644
--- a/core/network/src/main/kotlin/org/meshtastic/core/network/FirmwareReleaseRemoteDataSource.kt
+++ b/core/network/src/main/kotlin/org/meshtastic/core/network/FirmwareReleaseRemoteDataSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-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
@@ -14,16 +14,20 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package org.meshtastic.core.network
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.NetworkFirmwareReleases
import org.meshtastic.core.network.service.ApiService
import javax.inject.Inject
-class FirmwareReleaseRemoteDataSource @Inject constructor(private val apiService: ApiService) {
+class FirmwareReleaseRemoteDataSource
+@Inject
+constructor(
+ private val apiService: ApiService,
+ private val dispatchers: CoroutineDispatchers,
+) {
suspend fun getFirmwareReleases(): NetworkFirmwareReleases =
- withContext(Dispatchers.IO) { apiService.getFirmwareReleases() }
+ withContext(dispatchers.io) { apiService.getFirmwareReleases() }
}
diff --git a/core/strings/src/commonMain/composeResources/values/strings.xml b/core/strings/src/commonMain/composeResources/values/strings.xml
index af3f2b548..28d543fca 100644
--- a/core/strings/src/commonMain/composeResources/values/strings.xml
+++ b/core/strings/src/commonMain/composeResources/values/strings.xml
@@ -1086,5 +1086,4 @@
Estimated area: \u00b1%1$s (\u00b1%2$s)
Estimated area: unknown accuracy
Mark as read
- now
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt
index c76d09bbf..248e0a2e2 100644
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/util/FormatAgo.kt
@@ -17,24 +17,16 @@
package org.meshtastic.core.ui.util
import android.text.format.DateUtils
-import com.meshtastic.core.strings.getString
-import org.meshtastic.core.strings.Res
-import org.meshtastic.core.strings.now
@Suppress("MagicNumber")
fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMillis()): String {
val timeInMillis = lastSeenUnix * 1000L
- val diff = currentTimeMillis - timeInMillis
- return if (diff < 60_000L) {
- getString(Res.string.now)
- } else {
- DateUtils.getRelativeTimeSpanString(
- timeInMillis,
- currentTimeMillis,
- DateUtils.MINUTE_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE,
- )
- .toString()
- }
+ return DateUtils.getRelativeTimeSpanString(
+ timeInMillis,
+ currentTimeMillis,
+ DateUtils.SECOND_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE,
+ )
+ .toString()
}