From e17638363fbb4f94dca280e8e0c834ecf1b3852c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 03:03:14 +0000 Subject: [PATCH 1/4] Initial plan From b3a7442bf1ef7753f860751fbe0acf74a5f4648b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 03:08:50 +0000 Subject: [PATCH 2/4] Optimize Firebase chat repository and HomeViewModel for better performance Co-authored-by: ARCOOON <78073305+ARCOOON@users.noreply.github.com> --- .../data/firebase/FirebaseChatRepository.kt | 87 +++++++++++-------- gradlew | 0 .../devusercode/ui/screens/home/HomeScreen.kt | 13 ++- .../ui/screens/home/HomeViewModel.kt | 11 ++- 4 files changed, 69 insertions(+), 42 deletions(-) mode change 100644 => 100755 gradlew diff --git a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt index 8fa4884..532838c 100644 --- a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt +++ b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt @@ -9,6 +9,8 @@ import com.devusercode.data.local.mapper.toEntity import com.google.firebase.database.DataSnapshot import com.google.firebase.database.FirebaseDatabase import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.tasks.await import kotlinx.coroutines.withContext @@ -36,48 +38,61 @@ class FirebaseChatRepository( return@withContext fresh } - private suspend fun fetchFromNetwork(currentUid: String): List { - val convSnap = - db - .getReference("users") - .child(currentUid) - .child("conversations") - .get() - .await() - val cids = convSnap.children.mapNotNull { it.key } - val res = mutableListOf() - - for (cid in cids) { - val parts = - db - .getReference("conversations") - .child(cid) - .child("participants") - .get() - .await() - val otherUid = parts.children.mapNotNull { it.key }.firstOrNull { it != currentUid } ?: continue - val otherSnap = + private suspend fun fetchFromNetwork(currentUid: String): List = + withContext(Dispatchers.IO) { + val convSnap = db .getReference("users") - .child(otherUid) - .get() - .await() - val user = otherSnap.toUser(otherUid) - val last = - db - .getReference("conversations") - .child(cid) - .child("lastMessage") + .child(currentUid) + .child("conversations") .get() .await() - val text = last.child("text").getValue(String::class.java) - val time = last.child("time").getValue(Long::class.java) + val cids = convSnap.children.mapNotNull { it.key } - res += UserPair(user, cid, text, time) - } + // Batch all Firebase calls concurrently instead of sequentially + cids.mapNotNull { cid -> + async { + runCatching { + val parts = + db + .getReference("conversations") + .child(cid) + .child("participants") + .get() + .await() + val otherUid = parts.children.mapNotNull { it.key }.firstOrNull { it != currentUid } ?: return@async null - return res - } + // Fetch user data and last message in parallel + val otherSnapDeferred = + async { + db + .getReference("users") + .child(otherUid) + .get() + .await() + } + val lastDeferred = + async { + db + .getReference("conversations") + .child(cid) + .child("lastMessage") + .get() + .await() + } + + val otherSnap = otherSnapDeferred.await() + val last = lastDeferred.await() + + val user = otherSnap.toUser(otherUid) + val text = last.child("text").getValue(String::class.java) + val time = last.child("time").getValue(Long::class.java) + + UserPair(user, cid, text, time) + }.getOrNull() + } + }.awaitAll().filterNotNull() + } private fun DataSnapshot.toUser(uid: String): User { val name = diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt b/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt index b1fe924..093199c 100644 --- a/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt +++ b/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt @@ -68,10 +68,17 @@ fun HomeScreen( title = { Text("UpChat") }, actions = { IconButton(onClick = { menu = true }) { Icon(Icons.Default.MoreVert, contentDescription = null) } - DropdownMenu(expanded = menu, onDismissRequest = { }) { - DropdownMenuItem(text = { Text("Profile") }, onClick = { onProfile() }) - DropdownMenuItem(text = { Text("Settings") }, onClick = { onSettings() }) + DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) { + DropdownMenuItem(text = { Text("Profile") }, onClick = { + menu = false + onProfile() + }) + DropdownMenuItem(text = { Text("Settings") }, onClick = { + menu = false + onSettings() + }) DropdownMenuItem(text = { Text("Logout") }, onClick = { + menu = false vm.doLogout { // If NavHost passed a navigator, use it; else no-op onLoggedOutNavigateToAuth?.invoke(Routes.AUTH) diff --git a/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt b/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt index b33fbd6..886bb0f 100644 --- a/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt +++ b/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt @@ -65,14 +65,19 @@ class HomeViewModel private fun watchPresence(uids: List) { watchers.values.forEach { it.cancel() } watchers.clear() - uids.forEach { id -> + uids.forEachIndexed { index, id -> watchers[id] = viewModelScope.launch { observePresence(id).collect { (online, lastSeen) -> _state.update { st -> + // Optimize: Only update the specific user at the given index val updated = - st.conversations.map { p -> - if (p.user.uid == id) p.copy(user = p.user.copy(online = online, lastSeenEpochMs = lastSeen)) else p + st.conversations.toMutableList().apply { + val targetIndex = indexOfFirst { it.user.uid == id } + if (targetIndex >= 0) { + val pair = get(targetIndex) + set(targetIndex, pair.copy(user = pair.user.copy(online = online, lastSeenEpochMs = lastSeen))) + } } st.copy(conversations = updated) } From ead81fc366c1fa1abbb99bf8bcf95e7416ade7f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 03:10:42 +0000 Subject: [PATCH 3/4] Address code review feedback: remove unused forEachIndexed and add error logging Co-authored-by: ARCOOON <78073305+ARCOOON@users.noreply.github.com> --- .../devusercode/data/firebase/FirebaseChatRepository.kt | 7 +++++++ .../java/com/devusercode/ui/screens/home/HomeViewModel.kt | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt index 532838c..f4adb8e 100644 --- a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt +++ b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt @@ -1,5 +1,6 @@ package com.devusercode.data.firebase +import android.util.Log import com.devusercode.core.domain.chat.model.UserPair import com.devusercode.core.domain.chat.repo.ChatRepository import com.devusercode.core.domain.user.model.User @@ -18,6 +19,10 @@ class FirebaseChatRepository( private val db: FirebaseDatabase, private val conversationDao: ConversationDao, ) : ChatRepository { + companion object { + private const val TAG = "FirebaseChatRepository" + } + override suspend fun listOpenConversations(currentUid: String): List = withContext(Dispatchers.IO) { // 1) Try cache first @@ -89,6 +94,8 @@ class FirebaseChatRepository( val time = last.child("time").getValue(Long::class.java) UserPair(user, cid, text, time) + }.onFailure { e -> + Log.e(TAG, "Failed to fetch conversation $cid", e) }.getOrNull() } }.awaitAll().filterNotNull() diff --git a/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt b/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt index 886bb0f..ddda191 100644 --- a/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt +++ b/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt @@ -65,7 +65,7 @@ class HomeViewModel private fun watchPresence(uids: List) { watchers.values.forEach { it.cancel() } watchers.clear() - uids.forEachIndexed { index, id -> + uids.forEach { id -> watchers[id] = viewModelScope.launch { observePresence(id).collect { (online, lastSeen) -> From 40baffc2f77c7d2cb3d43dd209471e9af52df2eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 7 Jan 2026 03:12:36 +0000 Subject: [PATCH 4/4] Remove redundant mapNotNull to eliminate double null filtering Co-authored-by: ARCOOON <78073305+ARCOOON@users.noreply.github.com> --- .../com/devusercode/data/firebase/FirebaseChatRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt index f4adb8e..07546a6 100644 --- a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt +++ b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt @@ -55,7 +55,7 @@ class FirebaseChatRepository( val cids = convSnap.children.mapNotNull { it.key } // Batch all Firebase calls concurrently instead of sequentially - cids.mapNotNull { cid -> + cids.map { cid -> async { runCatching { val parts =