From c489717ad10ef68ac0b41f1066ace1d60e5c9c7d Mon Sep 17 00:00:00 2001 From: andrekir Date: Fri, 20 Oct 2023 18:31:13 -0300 Subject: [PATCH] refactor: convert `NodeDB` to repository --- .../java/com/geeksville/mesh/MainActivity.kt | 10 ---- .../mesh/database/dao/MyNodeInfoDao.kt | 2 +- .../java/com/geeksville/mesh/model/NodeDB.kt | 58 ++++++++++++++----- .../mesh/model/RadioConfigViewModel.kt | 24 ++------ .../java/com/geeksville/mesh/model/UIState.kt | 25 ++++---- .../datastore/RadioConfigRepository.kt | 48 ++++++--------- .../geeksville/mesh/service/MeshService.kt | 15 +++-- .../mesh/service/ServiceRepository.kt | 13 +++++ .../geeksville/mesh/ui/ContactsFragment.kt | 4 +- .../geeksville/mesh/ui/MessagesFragment.kt | 2 +- .../geeksville/mesh/ui/SettingsFragment.kt | 2 +- .../com/geeksville/mesh/ui/UsersFragment.kt | 2 +- .../com/geeksville/mesh/ui/map/MapFragment.kt | 6 +- 13 files changed, 110 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 45792d499..73eecb6b5 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -180,10 +180,6 @@ class MainActivity : AppCompatActivity(), Logging { override fun createFragment(position: Int): Fragment = tabInfos[position].content } - private val isInTestLab: Boolean by lazy { - (application as GeeksvilleApplication).isInTestLab - } - override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) @@ -364,7 +360,6 @@ class MainActivity : AppCompatActivity(), Logging { debug("Getting latest DeviceConfig from service") try { val info: MyNodeInfo? = service.myNodeInfo // this can be null - model.setMyNodeInfo(info) if (info != null) { val isOld = info.minAppVersion > BuildConfig.VERSION_CODE @@ -381,9 +376,6 @@ class MainActivity : AppCompatActivity(), Logging { else { // If our app is too old/new, we probably don't understand the new DeviceConfig messages, so we don't read them until here - // model.setLocalConfig(LocalOnlyProtos.LocalConfig.parseFrom(service.deviceConfig)) - // model.setChannels(ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels))) - model.updateNodesFromDevice() // we have a connection to our device now, do the channel change @@ -526,8 +518,6 @@ class MainActivity : AppCompatActivity(), Logging { // We don't start listening for packets until after we are connected to the service registerMeshReceiver() - model.setMyNodeInfo(service.myNodeInfo) // Note: this could be NULL! - val connectionState = MeshService.ConnectionState.valueOf(service.connectionState()) diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/MyNodeInfoDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/MyNodeInfoDao.kt index ddd444b7d..e81c2afb4 100644 --- a/app/src/main/java/com/geeksville/mesh/database/dao/MyNodeInfoDao.kt +++ b/app/src/main/java/com/geeksville/mesh/database/dao/MyNodeInfoDao.kt @@ -14,7 +14,7 @@ interface MyNodeInfoDao { fun getMyNodeInfo(): Flow @Insert(onConflict = OnConflictStrategy.REPLACE) - fun setMyNodeInfo(myInfo: MyNodeInfo?) + fun setMyNodeInfo(myInfo: MyNodeInfo) @Query("DELETE FROM MyNodeInfo") fun clearMyNodeInfo() diff --git a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt index fc930d39a..be1ad97e3 100644 --- a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt +++ b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt @@ -1,23 +1,50 @@ package com.geeksville.mesh.model -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asLiveData import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshUser +import com.geeksville.mesh.MyNodeInfo import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.Position +import com.geeksville.mesh.database.dao.MyNodeInfoDao +import com.geeksville.mesh.database.dao.NodeInfoDao +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NodeDB @Inject constructor( + private val myNodeInfoDao: MyNodeInfoDao, + private val nodeInfoDao: NodeInfoDao, +) { + + fun myNodeInfoFlow(): Flow = myNodeInfoDao.getMyNodeInfo() + private suspend fun setMyNodeInfo(myInfo: MyNodeInfo) = withContext(Dispatchers.IO) { + myNodeInfoDao.setMyNodeInfo(myInfo) + } + + fun nodeInfoFlow(): Flow> = nodeInfoDao.getNodes() + suspend fun upsert(node: NodeInfo) = withContext(Dispatchers.IO) { + nodeInfoDao.upsert(node) + } + + suspend fun installNodeDB(mi: MyNodeInfo, nodes: List) { + myNodeInfoDao.clearMyNodeInfo() + nodeInfoDao.clearNodeInfo() + nodeInfoDao.putAll(nodes) + setMyNodeInfo(mi) // set MyNodeInfo last + } -/// NodeDB lives inside the UIViewModel, but it needs a backpointer to reach the service -class NodeDB(private val ui: UIViewModel) { private val testPositions = arrayOf( Position(32.776665, -96.796989, 35, 123), // dallas Position(32.960758, -96.733521, 35, 456), // richardson - Position(32.912901, -96.781776, 35, 789) // north dallas + Position(32.912901, -96.781776, 35, 789), // north dallas ) - val testNodeNoPosition = NodeInfo( + private val testNodeNoPosition = NodeInfo( 8, MeshUser( "+16508765308".format(8), @@ -29,7 +56,7 @@ class NodeDB(private val ui: UIViewModel) { null ) - private val testNodes = testPositions.mapIndexed { index, it -> + private val testNodes = (listOf(testNodeNoPosition) + testPositions.mapIndexed { index, it -> NodeInfo( 9 + index, MeshUser( @@ -41,22 +68,26 @@ class NodeDB(private val ui: UIViewModel) { ), it ) - }.associateBy { it.user?.id!! } + }).associateBy { it.user?.id!! } private val seedWithTestNodes = false // The unique userId of our node - private val _myId = MutableLiveData(if (seedWithTestNodes) "+16508765309" else null) - val myId: LiveData get() = _myId + private val _myId = MutableStateFlow(if (seedWithTestNodes) "+16508765309" else null) + val myId: StateFlow get() = _myId fun setMyId(myId: String?) { _myId.value = myId } + // A map from nodeNum to NodeInfo + private val _nodeDBbyNum = MutableStateFlow>(mapOf()) + val nodeDBbyNum: StateFlow> get() = _nodeDBbyNum + val nodesByNum get() = nodeDBbyNum.value + // A map from userId to NodeInfo private val _nodes = MutableStateFlow(if (seedWithTestNodes) testNodes else mapOf()) - val nodes: LiveData> = _nodes.asLiveData() - val nodesByNum get() = nodes.value?.values?.associateBy { it.num } + val nodes: StateFlow> get() = _nodes fun setNodes(nodes: Map) { _nodes.value = nodes @@ -64,5 +95,6 @@ class NodeDB(private val ui: UIViewModel) { fun setNodes(list: List) { setNodes(list.associateBy { it.user?.id!! }) + _nodeDBbyNum.value = list.associateBy { it.num } } } diff --git a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt index 53a8f104e..5935cfc5a 100644 --- a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt @@ -23,8 +23,6 @@ import com.geeksville.mesh.database.entity.MeshLog import com.geeksville.mesh.deviceProfile import com.geeksville.mesh.moduleConfig import com.geeksville.mesh.repository.datastore.RadioConfigRepository -import com.geeksville.mesh.repository.radio.RadioInterfaceService -import com.geeksville.mesh.service.MeshService.ConnectionState import com.geeksville.mesh.ui.ConfigRoute import com.geeksville.mesh.ui.ResponseState import com.google.protobuf.MessageLite @@ -59,7 +57,6 @@ data class RadioConfigState( @HiltViewModel class RadioConfigViewModel @Inject constructor( private val app: Application, - radioInterfaceService: RadioInterfaceService, private val radioConfigRepository: RadioConfigRepository, meshLogRepository: MeshLogRepository, ) : ViewModel(), Logging { @@ -68,12 +65,10 @@ class RadioConfigViewModel @Inject constructor( private val meshService: IMeshService? get() = radioConfigRepository.meshService // Connection state to our radio device - private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED) - val connectionState: StateFlow get() = _connectionState + val connectionState get() = radioConfigRepository.connectionState // A map from nodeNum to NodeInfo - private val _nodes = MutableStateFlow>(mapOf()) - val nodes: StateFlow> = _nodes + val nodes: StateFlow> get() = radioConfigRepository.nodeDBbyNum private val _myNodeInfo = MutableStateFlow(null) val myNodeInfo get() = _myNodeInfo @@ -86,21 +81,10 @@ class RadioConfigViewModel @Inject constructor( val currentDeviceProfile get() = _currentDeviceProfile.value init { - radioInterfaceService.connectionState.onEach { state -> - _connectionState.value = when { - state.isConnected -> ConnectionState.CONNECTED - else -> ConnectionState.DISCONNECTED - } - }.launchIn(viewModelScope) - radioConfigRepository.myNodeInfoFlow().onEach { _myNodeInfo.value = it }.launchIn(viewModelScope) - radioConfigRepository.nodeInfoFlow().onEach { list -> - _nodes.value = list.associateBy { it.num } - }.launchIn(viewModelScope) - radioConfigRepository.deviceProfileFlow.onEach { _currentDeviceProfile.value = it }.launchIn(viewModelScope) @@ -114,8 +98,8 @@ class RadioConfigViewModel @Inject constructor( debug("RadioConfigViewModel created") } - val myNodeNum get() = _myNodeInfo.value?.myNodeNum - val maxChannels get() = _myNodeInfo.value?.maxChannels ?: 8 + val myNodeNum get() = myNodeInfo.value?.myNodeNum + val maxChannels get() = myNodeInfo.value?.maxChannels ?: 8 private val ourNodeInfo: NodeInfo? get() = nodes.value[myNodeNum] override fun onCleared() { diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index c035f3a1b..70bb62ab4 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -112,7 +112,7 @@ class UIViewModel @Inject constructor( var actionBarMenu: Menu? = null val meshService: IMeshService? get() = radioConfigRepository.meshService - val nodeDB = NodeDB(this) + val nodeDB = radioConfigRepository.nodeDB val bondedAddress get() = radioInterfaceService.getBondedDeviceAddress() val selectedBluetooth get() = radioInterfaceService.getDeviceAddress()?.getOrNull(0) == 'x' @@ -138,6 +138,10 @@ class UIViewModel @Inject constructor( private val _quickChatActions = MutableStateFlow>(emptyList()) val quickChatActions: StateFlow> = _quickChatActions + // hardware info about our local device (can be null) + private val _myNodeInfo = MutableStateFlow(null) + val myNodeInfo: StateFlow get() = _myNodeInfo + private val _ourNodeInfo = MutableStateFlow(null) val ourNodeInfo: StateFlow = _ourNodeInfo @@ -152,6 +156,10 @@ class UIViewModel @Inject constructor( radioInterfaceService.clearErrorMessage() }.launchIn(viewModelScope) + radioConfigRepository.myNodeInfoFlow().onEach { + _myNodeInfo.value = it + }.launchIn(viewModelScope) + radioConfigRepository.nodeInfoFlow().onEach(nodeDB::setNodes) .launchIn(viewModelScope) @@ -356,15 +364,8 @@ class UIViewModel @Inject constructor( // managed mode disables all access to configuration val isManaged: Boolean get() = config.device.isManaged - /// hardware info about our local device (can be null) - private val _myNodeInfo = MutableLiveData() - val myNodeInfo: LiveData get() = _myNodeInfo - val myNodeNum get() = _myNodeInfo.value?.myNodeNum - val maxChannels = myNodeInfo.value?.maxChannels ?: 8 - - fun setMyNodeInfo(info: MyNodeInfo?) { - _myNodeInfo.value = info - } + val myNodeNum get() = myNodeInfo.value?.myNodeNum + val maxChannels get() = myNodeInfo.value?.maxChannels ?: 8 override fun onCleared() { super.onCleared() @@ -445,7 +446,7 @@ class UIViewModel @Inject constructor( val myNodeNum = myNodeNum ?: return@launch // Capture the current node value while we're still on main thread - val nodes = nodeDB.nodes.value ?: emptyMap() + val nodes = nodeDB.nodes.value val positionToPos: (MeshProtos.Position?) -> Position? = { meshPosition -> meshPosition?.let { Position(it) }.takeIf { @@ -602,7 +603,7 @@ class UIViewModel @Inject constructor( if (data?.portnumValue == Portnums.PortNum.TRACEROUTE_APP_VALUE) { val parsed = MeshProtos.RouteDiscovery.parseFrom(data.payload) - fun nodeName(num: Int) = nodeDB.nodesByNum?.get(num)?.user?.longName + fun nodeName(num: Int) = nodeDB.nodesByNum[num]?.user?.longName ?: app.getString(R.string.unknown_username) _tracerouteResponse.value = buildString { diff --git a/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt index 99f6338e5..0fa029442 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt @@ -11,66 +11,52 @@ import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig import com.geeksville.mesh.MyNodeInfo import com.geeksville.mesh.NodeInfo -import com.geeksville.mesh.database.dao.MyNodeInfoDao -import com.geeksville.mesh.database.dao.NodeInfoDao import com.geeksville.mesh.deviceProfile +import com.geeksville.mesh.model.NodeDB import com.geeksville.mesh.model.getChannelUrl +import com.geeksville.mesh.service.MeshService.ConnectionState import com.geeksville.mesh.service.ServiceRepository -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.withContext import javax.inject.Inject /** * Class responsible for radio configuration data. - * Combines access to [MyNodeInfo] & [NodeInfo] Room databases - * and [ChannelSet], [LocalConfig] & [LocalModuleConfig] data stores. + * Combines access to [nodeDB], [ChannelSet], [LocalConfig] & [LocalModuleConfig]. */ class RadioConfigRepository @Inject constructor( private val serviceRepository: ServiceRepository, - private val myNodeInfoDao: MyNodeInfoDao, - private val nodeInfoDao: NodeInfoDao, + val nodeDB: NodeDB, private val channelSetRepository: ChannelSetRepository, private val localConfigRepository: LocalConfigRepository, private val moduleConfigRepository: ModuleConfigRepository, ) { val meshService: IMeshService? get() = serviceRepository.meshService - suspend fun clearNodeDB() = withContext(Dispatchers.IO) { - myNodeInfoDao.clearMyNodeInfo() - nodeInfoDao.clearNodeInfo() - } + // Connection state to our radio device + val connectionState get() = serviceRepository.connectionState + fun setConnectionState(state: ConnectionState) = serviceRepository.setConnectionState(state) /** * Flow representing the [MyNodeInfo] database. */ - fun myNodeInfoFlow(): Flow = myNodeInfoDao.getMyNodeInfo() + fun myNodeInfoFlow(): Flow = nodeDB.myNodeInfoFlow() suspend fun getMyNodeInfo(): MyNodeInfo? = myNodeInfoFlow().firstOrNull() - suspend fun setMyNodeInfo(myInfo: MyNodeInfo?) = withContext(Dispatchers.IO) { - myNodeInfoDao.setMyNodeInfo(myInfo) - } + val nodeDBbyNum: StateFlow> get() = nodeDB.nodeDBbyNum + val nodeDBbyID: StateFlow> get() = nodeDB.nodes /** * Flow representing the [NodeInfo] database. */ - fun nodeInfoFlow(): Flow> = nodeInfoDao.getNodes() + fun nodeInfoFlow(): Flow> = nodeDB.nodeInfoFlow() suspend fun getNodes(): List? = nodeInfoFlow().firstOrNull() - suspend fun upsert(node: NodeInfo) = withContext(Dispatchers.IO) { - nodeInfoDao.upsert(node) - } - - suspend fun putAll(nodes: List) = withContext(Dispatchers.IO) { - nodeInfoDao.putAll(nodes) - } - - suspend fun installNodeDB(mi: MyNodeInfo?, nodes: List) { - clearNodeDB() - putAll(nodes) - setMyNodeInfo(mi) // set MyNodeInfo last + suspend fun upsert(node: NodeInfo) = nodeDB.upsert(node) + suspend fun installNodeDB(mi: MyNodeInfo, nodes: List) { + nodeDB.installNodeDB(mi, nodes) } /** @@ -149,13 +135,13 @@ class RadioConfigRepository @Inject constructor( */ val deviceProfileFlow: Flow = combine( myNodeInfoFlow(), - nodeInfoFlow(), + nodeDBbyNum, channelSetFlow, localConfigFlow, moduleConfigFlow, ) { myInfo, nodes, channels, localConfig, localModuleConfig -> deviceProfile { - nodes.firstOrNull { it.num == myInfo?.myNodeNum }?.user?.let { + nodes[myInfo?.myNodeNum]?.user?.let { longName = it.longName shortName = it.shortName } diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index c3f2aba71..b95c5df23 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -144,7 +144,9 @@ class MeshService : Service(), Logging { /// A mapping of receiver class name to package name - used for explicit broadcasts private val clientPackages = mutableMapOf() private val serviceNotifications = MeshServiceNotifications(this) - private val serviceBroadcasts = MeshServiceBroadcasts(this, clientPackages) { connectionState } + private val serviceBroadcasts = MeshServiceBroadcasts(this, clientPackages) { + connectionState.also { radioConfigRepository.setConnectionState(it) } + } private val serviceJob = Job() private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) private var connectionState = ConnectionState.DISCONNECTED @@ -1053,7 +1055,7 @@ class MeshService : Service(), Logging { ) } - // Have our timeout fire in the approprate number of seconds + // Have our timeout fire in the appropriate number of seconds sleepTimeout = serviceScope.handledLaunch { try { // If we have a valid timeout, wait that long (+30 seconds) otherwise, just wait 30 seconds @@ -1432,9 +1434,9 @@ class MeshService : Service(), Logging { insertMeshLog(packetToSave) // This was our config request - if (newMyNodeInfo == null || newNodes.isEmpty()) + if (newMyNodeInfo == null || newNodes.isEmpty()) { errormsg("Did not receive a valid config") - else { + } else { discardNodeDB() debug("Installing new node DB") myNodeInfo = newMyNodeInfo // Install myNodeInfo as current @@ -1448,7 +1450,7 @@ class MeshService : Service(), Logging { myNodeInfo = newMyNodeInfo // we might have just updated myNodeInfo serviceScope.handledLaunch { - radioConfigRepository.installNodeDB(newMyNodeInfo, nodeDBbyID.values.toList()) + radioConfigRepository.installNodeDB(newMyNodeInfo!!, nodeDBbyID.values.toList()) } sendAnalytics() @@ -1460,8 +1462,9 @@ class MeshService : Service(), Logging { } onHasSettings() } - } else + } else { warn("Ignoring stale config complete") + } } private fun requestConfig(config: AdminProtos.AdminMessage.ConfigType) { diff --git a/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt b/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt index 3353ddd57..77c30c36f 100644 --- a/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt @@ -1,9 +1,14 @@ package com.geeksville.mesh.service import com.geeksville.mesh.IMeshService +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject import javax.inject.Singleton +/** + * Repository class for managing the [IMeshService] instance and connection state + */ @Singleton class ServiceRepository @Inject constructor() { var meshService: IMeshService? = null @@ -12,4 +17,12 @@ class ServiceRepository @Inject constructor() { fun setMeshService(service: IMeshService?) { meshService = service } + + // Connection state to our radio device + private val _connectionState = MutableStateFlow(MeshService.ConnectionState.DISCONNECTED) + val connectionState: StateFlow get() = _connectionState + + fun setConnectionState(connectionState: MeshService.ConnectionState) { + _connectionState.value = connectionState + } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt index 749fc0e1f..6939690ac 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt @@ -87,7 +87,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { val toBroadcast = contact.to == DataPacket.ID_BROADCAST // grab usernames from NodeInfo - val nodes = model.nodeDB.nodes.value!! + val nodes = model.nodeDB.nodes.value val node = nodes[if (fromLocal) contact.to else contact.from] //grab channel names from DeviceConfig @@ -212,7 +212,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { contactsAdapter.onChannelsChanged() } - model.nodeDB.nodes.observe(viewLifecycleOwner) { + model.nodeDB.nodes.asLiveData().observe(viewLifecycleOwner) { contactsAdapter.notifyDataSetChanged() } diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index abeda1621..d5c1cbf4d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -93,7 +93,7 @@ class MessagesFragment : Fragment(), Logging { override fun onBindViewHolder(holder: ViewHolder, position: Int) { val packet = messages[position] val msg = packet.data - val nodes = model.nodeDB.nodes.value!! + val nodes = model.nodeDB.nodes.value val node = nodes[msg.from] // Determine if this is my message (originated on this device) val isLocal = msg.from == DataPacket.ID_LOCAL diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 6239ef3b8..02e43996b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -321,7 +321,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } // Also watch myNodeInfo because it might change later - model.myNodeInfo.observe(viewLifecycleOwner) { + model.myNodeInfo.asLiveData().observe(viewLifecycleOwner) { updateNodeInfo() } diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index a1dca9ee3..5d807de28 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -322,7 +322,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { values }.toTypedArray() - model.nodeDB.nodes.observe(viewLifecycleOwner) { + model.nodeDB.nodes.asLiveData().observe(viewLifecycleOwner) { nodesAdapter.onNodesChanged(it.perhapsReindexBy(model.myNodeNum)) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt index 506e7425f..633b992f8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.fragment.app.activityViewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.geeksville.mesh.BuildConfig import com.geeksville.mesh.DataPacket @@ -187,7 +188,7 @@ fun MapView(model: UIViewModel = viewModel()) { requestPermissionAndToggleLauncher.launch(context.getLocationPermissions()) } - val nodes by model.nodeDB.nodes.observeAsState(emptyMap()) + val nodes by model.nodeDB.nodes.collectAsStateWithLifecycle() val waypoints by model.waypoints.observeAsState(emptyMap()) var showDownloadButton: Boolean by remember { mutableStateOf(false) } @@ -256,8 +257,7 @@ fun MapView(model: UIViewModel = viewModel()) { } fun getUsername(id: String?) = if (id == DataPacket.ID_LOCAL) context.getString(R.string.you) - else model.nodeDB.nodes.value?.get(id)?.user?.longName - ?: context.getString(R.string.unknown_username) + else model.nodeDB.nodes.value[id]?.user?.longName ?: context.getString(R.string.unknown_username) fun MapView.onWaypointChanged(waypoints: Collection): List { return waypoints.mapNotNull { waypoint ->