mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-13 03:17:48 -04:00
Decouple mesh service bind from MainActivity (#2743)
This commit is contained in:
@@ -45,18 +45,11 @@ import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.geeksville.mesh.android.BindFailedException
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.ServiceClient
|
||||
import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.model.BluetoothViewModel
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.DEEP_LINK_BASE_URI
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.ServiceRepository
|
||||
import com.geeksville.mesh.service.startService
|
||||
import com.geeksville.mesh.ui.MainMenuAction
|
||||
import com.geeksville.mesh.ui.MainScreen
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
@@ -66,7 +59,6 @@ import com.geeksville.mesh.ui.sharing.toSharedContact
|
||||
import com.geeksville.mesh.util.LanguageUtils
|
||||
import com.geeksville.mesh.util.getPackageInfoCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Job
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -76,7 +68,8 @@ class MainActivity :
|
||||
private val bluetoothViewModel: BluetoothViewModel by viewModels()
|
||||
private val model: UIViewModel by viewModels()
|
||||
|
||||
@Inject internal lateinit var serviceRepository: ServiceRepository
|
||||
// This is aware of the Activity lifecycle and handles binding to the mesh service.
|
||||
@Inject internal lateinit var meshServiceClient: MeshServiceClient
|
||||
|
||||
private var showAppIntro by mutableStateOf(false)
|
||||
|
||||
@@ -225,46 +218,6 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private var serviceSetupJob: Job? = null
|
||||
|
||||
private val mesh =
|
||||
object : ServiceClient<IMeshService>(IMeshService.Stub::asInterface) {
|
||||
override fun onConnected(service: IMeshService) {
|
||||
serviceSetupJob?.cancel()
|
||||
serviceSetupJob =
|
||||
lifecycleScope.handledLaunch {
|
||||
serviceRepository.setMeshService(service)
|
||||
debug("connected to mesh service, connectionState=${model.connectionState.value}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
serviceSetupJob?.cancel()
|
||||
serviceRepository.setMeshService(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindMeshService() {
|
||||
debug("Binding to mesh service!")
|
||||
try {
|
||||
MeshService.startService(this)
|
||||
} catch (ex: Exception) {
|
||||
errormsg("Failed to start service from activity - but ignoring because bind will work ${ex.message}")
|
||||
}
|
||||
|
||||
mesh.connect(this, MeshService.createIntent(), BIND_AUTO_CREATE + BIND_ABOVE_CLIENT)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
try {
|
||||
bindMeshService()
|
||||
} catch (ex: BindFailedException) {
|
||||
errormsg("Bind of MeshService failed${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSettingsPage() {
|
||||
createSettingsIntent().send()
|
||||
}
|
||||
|
||||
114
app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt
Normal file
114
app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.appcompat.app.AppCompatActivity.BIND_ABOVE_CLIENT
|
||||
import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.geeksville.mesh.android.BindFailedException
|
||||
import com.geeksville.mesh.android.ServiceClient
|
||||
import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.ServiceRepository
|
||||
import com.geeksville.mesh.service.startService
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import kotlinx.coroutines.Job
|
||||
import javax.inject.Inject
|
||||
|
||||
/** A Activity-lifecycle-aware [ServiceClient] that binds [MeshService] once the Activity is started. */
|
||||
@ActivityScoped
|
||||
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,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
) : ServiceClient<IMeshService>(IMeshService.Stub::asInterface),
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
// TODO Use the default binding for @ActivityScoped
|
||||
private val lifecycleOwner: LifecycleOwner = activity as LifecycleOwner
|
||||
|
||||
// TODO Inject this for ease of testing
|
||||
private var serviceSetupJob: Job? = null
|
||||
|
||||
init {
|
||||
debug("Adding self as LifecycleObserver for $lifecycleOwner")
|
||||
lifecycleOwner.lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
// region ServiceClient overrides
|
||||
|
||||
override fun onConnected(service: IMeshService) {
|
||||
serviceSetupJob?.cancel()
|
||||
serviceSetupJob =
|
||||
lifecycleOwner.lifecycleScope.handledLaunch {
|
||||
serviceRepository.setMeshService(service)
|
||||
debug("connected to mesh service, connectionState=${serviceRepository.connectionState.value}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
serviceSetupJob?.cancel()
|
||||
serviceRepository.setMeshService(null)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region DefaultLifecycleObserver overrides
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
super.onStart(owner)
|
||||
debug("Lifecycle: ON_START")
|
||||
|
||||
try {
|
||||
bindMeshService()
|
||||
} catch (ex: BindFailedException) {
|
||||
errormsg("Bind of MeshService failed: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
super.onDestroy(owner)
|
||||
debug("Lifecycle: ON_DESTROY")
|
||||
|
||||
owner.lifecycle.removeObserver(this)
|
||||
debug("Removed self as LifecycleObserver to $lifecycleOwner")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private fun bindMeshService() {
|
||||
debug("Binding to mesh service!")
|
||||
try {
|
||||
MeshService.startService(activity)
|
||||
} catch (ex: Exception) {
|
||||
errormsg("Failed to start service from activity - but ignoring because bind will work: ${ex.message}")
|
||||
}
|
||||
|
||||
connect(activity, MeshService.createIntent(), BIND_AUTO_CREATE + BIND_ABOVE_CLIENT)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user