mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-12 00:28:20 -04:00
perf: Phase 4 Android performance optimizations
- frequentEmojis: computed List → cached StateFlow (avoids parse+sort on every recomposition) - SdkNodeRepositoryImpl: SharingStarted.Eagerly → WhileSubscribed(5_000) for ourNodeInfo, myId, localStats - PacketRepositoryImpl: deduplicate flatMapLatest chains via shared combine(myNodeNumFlow, currentDb) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.map
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -61,12 +62,15 @@ class PacketRepositoryImpl(
|
||||
.map { it?.myNodeNum ?: 0 }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override fun getWaypoints(): Flow<List<DataPacket>> = myNodeNumFlow
|
||||
.flatMapLatest { num -> dbManager.currentDb.flatMapLatest { db -> db.packetDao().getAllWaypointsFlow(num) } }
|
||||
/** Cached upstream combining myNodeNum + currentDb — avoids creating duplicate flatMapLatest chains. */
|
||||
private val numAndDb = combine(myNodeNumFlow, dbManager.currentDb) { num, db -> num to db }
|
||||
|
||||
override fun getWaypoints(): Flow<List<DataPacket>> = numAndDb
|
||||
.flatMapLatest { (num, db) -> db.packetDao().getAllWaypointsFlow(num) }
|
||||
.map { list -> list.map { it.data } }
|
||||
|
||||
override fun getContacts(): Flow<Map<String, DataPacket>> = myNodeNumFlow
|
||||
.flatMapLatest { num -> dbManager.currentDb.flatMapLatest { db -> db.packetDao().getContactKeys(num) } }
|
||||
override fun getContacts(): Flow<Map<String, DataPacket>> = numAndDb
|
||||
.flatMapLatest { (num, db) -> db.packetDao().getContactKeys(num) }
|
||||
.map { map -> map.mapValues { it.value.data } }
|
||||
|
||||
override fun getContactsPaged(): Flow<PagingData<DataPacket>> = Pager(
|
||||
@@ -87,17 +91,17 @@ class PacketRepositoryImpl(
|
||||
override suspend fun getUnreadCount(contact: String): Int =
|
||||
withContext(dispatchers.io) { dbManager.currentDb.value.packetDao().getUnreadCount(currentMyNodeNum, contact) }
|
||||
|
||||
override fun getUnreadCountFlow(contact: String): Flow<Int> = myNodeNumFlow
|
||||
.flatMapLatest { num -> dbManager.currentDb.flatMapLatest { db -> db.packetDao().getUnreadCountFlow(num, contact) } }
|
||||
override fun getUnreadCountFlow(contact: String): Flow<Int> = numAndDb
|
||||
.flatMapLatest { (num, db) -> db.packetDao().getUnreadCountFlow(num, contact) }
|
||||
|
||||
override fun getFirstUnreadMessageUuid(contact: String): Flow<Long?> = myNodeNumFlow
|
||||
.flatMapLatest { num -> dbManager.currentDb.flatMapLatest { db -> db.packetDao().getFirstUnreadMessageUuid(num, contact) } }
|
||||
override fun getFirstUnreadMessageUuid(contact: String): Flow<Long?> = numAndDb
|
||||
.flatMapLatest { (num, db) -> db.packetDao().getFirstUnreadMessageUuid(num, contact) }
|
||||
|
||||
override fun hasUnreadMessages(contact: String): Flow<Boolean> = myNodeNumFlow
|
||||
.flatMapLatest { num -> dbManager.currentDb.flatMapLatest { db -> db.packetDao().hasUnreadMessages(num, contact) } }
|
||||
override fun hasUnreadMessages(contact: String): Flow<Boolean> = numAndDb
|
||||
.flatMapLatest { (num, db) -> db.packetDao().hasUnreadMessages(num, contact) }
|
||||
|
||||
override fun getUnreadCountTotal(): Flow<Int> = myNodeNumFlow
|
||||
.flatMapLatest { num -> dbManager.currentDb.flatMapLatest { db -> db.packetDao().getUnreadCountTotal(num) } }
|
||||
override fun getUnreadCountTotal(): Flow<Int> = numAndDb
|
||||
.flatMapLatest { (num, db) -> db.packetDao().getUnreadCountTotal(num) }
|
||||
|
||||
override suspend fun clearUnreadCount(contact: String, timestamp: Long) =
|
||||
withContext(dispatchers.io) { dbManager.currentDb.value.packetDao().clearUnreadCount(currentMyNodeNum, contact, timestamp) }
|
||||
@@ -463,8 +467,8 @@ class PacketRepositoryImpl(
|
||||
suspend fun updateReaction(reaction: RoomReaction) =
|
||||
withContext(dispatchers.io) { dbManager.currentDb.value.packetDao().update(reaction) }
|
||||
|
||||
override fun getFilteredCountFlow(contactKey: String): Flow<Int> = myNodeNumFlow
|
||||
.flatMapLatest { num -> dbManager.currentDb.flatMapLatest { db -> db.packetDao().getFilteredCountFlow(num, contactKey) } }
|
||||
override fun getFilteredCountFlow(contactKey: String): Flow<Int> = numAndDb
|
||||
.flatMapLatest { (num, db) -> db.packetDao().getFilteredCountFlow(num, contactKey) }
|
||||
|
||||
override suspend fun getFilteredCount(contactKey: String): Int =
|
||||
withContext(dispatchers.io) { dbManager.currentDb.value.packetDao().getFilteredCount(currentMyNodeNum, contactKey) }
|
||||
|
||||
@@ -99,15 +99,15 @@ class SdkNodeRepositoryImpl(
|
||||
|
||||
override val ourNodeInfo: StateFlow<Node?> =
|
||||
combine(_nodeDBbyNum, _myNodeNum) { db, myNum -> myNum?.let { db[it] } }
|
||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(5_000), null)
|
||||
|
||||
override val myId: StateFlow<String?> =
|
||||
ourNodeInfo.map { it?.user?.id }
|
||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(5_000), null)
|
||||
|
||||
override val localStats: StateFlow<LocalStats> =
|
||||
localStatsDataSource.localStatsFlow
|
||||
.stateIn(scope, SharingStarted.Eagerly, LocalStats())
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(5_000), LocalStats())
|
||||
|
||||
override fun updateLocalStats(stats: LocalStats) {
|
||||
scope.launch { localStatsDataSource.setLocalStats(stats) }
|
||||
|
||||
@@ -401,7 +401,7 @@ fun MessageScreen(
|
||||
onSendMessage = { text, key -> viewModel.sendMessage(text, key) },
|
||||
onReply = { message -> replyingToPacketId = message?.packetId },
|
||||
),
|
||||
quickEmojis = viewModel.frequentEmojis,
|
||||
quickEmojis = viewModel.frequentEmojis.collectAsStateWithLifecycle().value,
|
||||
)
|
||||
// Show FAB if we can scroll towards the newest messages (index 0).
|
||||
if (listState.canScrollBackward) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.util.ioDispatcher
|
||||
@@ -105,17 +106,21 @@ class MessageViewModel(
|
||||
}
|
||||
.cachedIn(viewModelScope)
|
||||
|
||||
val frequentEmojis: List<String>
|
||||
get() =
|
||||
customEmojiPrefs.customEmojiFrequency.value
|
||||
?.split(",")
|
||||
?.associate { entry ->
|
||||
entry.split("=", limit = 2).takeIf { it.size == 2 }?.let { it[0] to it[1].toInt() } ?: ("" to 0)
|
||||
}
|
||||
?.toList()
|
||||
?.sortedByDescending { it.second }
|
||||
?.map { it.first }
|
||||
?.take(6) ?: listOf("👍", "👎", "😂", "🔥", "❤️", "😮")
|
||||
private val defaultEmojis = listOf("👍", "👎", "😂", "🔥", "❤️", "😮")
|
||||
|
||||
val frequentEmojis: StateFlow<List<String>> =
|
||||
customEmojiPrefs.customEmojiFrequency
|
||||
.map { raw ->
|
||||
raw?.split(",")
|
||||
?.associate { entry ->
|
||||
entry.split("=", limit = 2).takeIf { it.size == 2 }?.let { it[0] to it[1].toInt() } ?: ("" to 0)
|
||||
}
|
||||
?.toList()
|
||||
?.sortedByDescending { it.second }
|
||||
?.map { it.first }
|
||||
?.take(6) ?: defaultEmojis
|
||||
}
|
||||
.stateInWhileSubscribed(defaultEmojis)
|
||||
|
||||
val homoglyphEncodingEnabled = homoglyphEncodingPrefs.homoglyphEncodingEnabled
|
||||
|
||||
|
||||
@@ -172,8 +172,7 @@ class MessageViewModelTest {
|
||||
fun testFrequentEmojis() = runTest {
|
||||
customEmojiFrequencyFlow.value = "👍=10,👎=5,😂=20"
|
||||
|
||||
// frequentEmojis is a property, not a flow.
|
||||
val emojis = viewModel.frequentEmojis
|
||||
val emojis = viewModel.frequentEmojis.value
|
||||
assertEquals(listOf("😂", "👍", "👎"), emojis)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user