Skip to content

Conversation

@ProtossManse
Copy link
Collaborator

@ProtossManse ProtossManse commented Jan 29, 2026

🔗 관련 이슈

Summary by CodeRabbit

릴리스 노트

  • Refactor
    • 어르신 정보의 로컬 저장소 시스템을 개선하여 더욱 안정적인 데이터 보관을 제공합니다.
    • 비동기 작업 처리를 최적화하여 앱의 응답성과 성능을 향상시켰습니다.
    • 상태 관리 체계를 간소화하여 더 나은 사용자 경험을 제공합니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

Walkthrough

ElderIdRepository를 메모리 기반에서 Android DataStore 기반 저장소로 리팩토링하고, 동기 메서드를 suspend 함수와 Flow로 전환했습니다. 이에 따라 관련된 ViewModels과 UI 화면들도 새로운 비동기 API에 맞춰 업데이트되었습니다.

Changes

Cohort / File(s) Summary
ElderIds 데이터 모델 및 직렬화
app/src/main/java/com/konkuk/medicarecall/data/model/ElderIds.kt, app/src/main/java/com/konkuk/medicarecall/data/util/ElderIdsSerializer.kt
ElderIds 데이터 클래스 추가 및 DataStore용 직렬화 구현 (Base64 인코딩, JSON 파싱, 에러 핸들링)
Repository 인터페이스 및 구현
app/src/main/java/com/konkuk/medicarecall/data/repository/ElderIdRepository.kt, app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderIdRepositoryImpl.kt
동기 메서드를 suspend 함수로 변경, getElderIds()를 Flow로 반환하도록 수정, DataStore 기반 저장소 통합
로그인 및 온보딩
app/src/main/java/com/konkuk/medicarecall/domain/usecase/CheckLoginStatusUseCase.kt, app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderViewModel.kt
ElderIdRepository 참조 코드 업데이트 및 불필요한 메서드 제거
전화 시간 설정 화면
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt, app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt
EldersInfoViewModel 의존성 제거, 로컬 Map 기반 상태 관리로 전환, Compose mutableState 사용
홈 화면 및 설정 화면
app/src/main/java/com/konkuk/medicarecall/ui/feature/home/viewmodel/HomeViewModel.kt, app/src/main/java/com/konkuk/medicarecall/ui/feature/settings/viewmodel/EldersInfoViewModel.kt, app/src/main/java/com/konkuk/medicarecall/ui/feature/settings/screen/ElderInfoScreen.kt
EldersInfoRepository → ElderIdRepository로 교체, Flow 기반 상태를 Compose mutableState로 변환

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

refactor

Suggested reviewers

  • ikseong00
  • librawish808
  • alswlekk

Poem

🔄 메모리에서 디스크로 떠나가며
데이터는 안전하게 머물고
비동기의 흐름을 따라
Flow와 Suspend는 춤을 춘다 ✨
리팩토링의 여정, 마무리 단계

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 반영하고 있습니다. 메모리 기반 elderIds 저장 방식을 DataStore를 사용한 내부 스토리지로 변경하는 리팩토링 작업을 잘 설명합니다.
Linked Issues check ✅ Passed PR이 연결된 세 이슈의 코딩 요구사항을 모두 충족합니다. #167 DataStore 기반 내부 저장소 구현, #177 elderId 전반 리팩토링, #206 시간 설정 부분 수정이 모두 반영되어 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 연결된 이슈의 범위 내에 있습니다. DataStore 도입, 저장소 구조 변경, 관련 뷰모델 리팩토링이 모두 이슈 #167, #177, #206의 목표와 일치합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/main/java/com/konkuk/medicarecall/domain/usecase/CheckLoginStatusUseCase.kt (1)

43-64: HttpException이 아닌 예외가 무시됩니다.

when (exception) 블록에서 HttpException만 처리하고, 네트워크 타임아웃이나 IOException 같은 다른 예외는 무시됩니다. 이 경우 해당 elder의 시간 설정 확인이 실패해도 다음 elder로 넘어가서 잘못된 상태로 홈 화면에 도달할 수 있습니다.

🛠️ 수정 제안
                     .onFailure { exception ->
-                        when (exception) {
-                            is HttpException -> {
-                                val code = exception.code()
-                                val errorBody = exception.response()?.errorBody()?.toString()
-
-                                Log.e("httplog", "HTTP 에러 발생 - 코드: $code, 메시지: $errorBody")
-
-                                when (code) {
-                                    404 -> {
-                                        // 404 Not Found 에러 처리
-                                        Log.d("httplog", "시간 설정 정보 없음, 시간 등록 화면으로")
-                                        return@runCatching NavigationDestination.GoToTimeSetting
-                                    }
-
-                                    else -> {
-                                        return@runCatching NavigationDestination.GoToLogin
-                                    }
-                                }
+                        if (exception is HttpException) {
+                            val code = exception.code()
+                            val errorBody = exception.response()?.errorBody()?.toString()
+
+                            Log.e("httplog", "HTTP 에러 발생 - 코드: $code, 메시지: $errorBody")
+
+                            when (code) {
+                                404 -> {
+                                    Log.d("httplog", "시간 설정 정보 없음, 시간 등록 화면으로")
+                                    return@runCatching NavigationDestination.GoToTimeSetting
+                                }
+                                else -> {
+                                    return@runCatching NavigationDestination.GoToLogin
+                                }
                             }
+                        } else {
+                            Log.e("httplog", "시간 설정 확인 중 예외 발생", exception)
+                            return@runCatching NavigationDestination.GoToLogin
                         }
                     }
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/senior/viewmodel/LoginElderViewModel.kt (1)

260-287: postElderBulk() 성공 후 elderId가 DataStore에 저장되지 않습니다.

서버에서 받은 elderId가 로컬 UI 상태(eldersList, elderHealthList)에만 저장되고, ElderIdRepository를 통한 DataStore 저장이 이루어지지 않습니다.

등록 완료 후 navigateToMainAfterLogin()을 통해 Home 화면으로 직접 이동하는데, 이 경로는 Splash 화면을 거치지 않으므로 CheckLoginStatusUseCase(DataStore 저장을 담당)가 실행되지 않습니다. 그 결과 HomeViewModel.fetchElderList()가 DataStore에서 빈 맵을 읽게 되어 첫 진입 시 어르신 목록이 표시되지 않을 수 있습니다.

postElderBulk().onSuccess 블록에서 elderIdRepository.updateElderIds()를 호출하거나, 다른 방식으로 DataStore 저장을 보장해야 합니다.

🤖 Fix all issues with AI agents
In `@app/src/main/java/com/konkuk/medicarecall/data/model/ElderIds.kt`:
- Around line 3-5: The data class ElderIds is missing the kotlinx.serialization
annotation required by ElderIdsSerializer (which calls
Json.decodeFromString/encodeToString); add the `@Serializable` annotation to the
ElderIds data class and import kotlinx.serialization.Serializable so
kotlinx.serialization can generate the serializer used by ElderIdsSerializer.

In
`@app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderIdRepositoryImpl.kt`:
- Around line 27-31: The current updateElderId implementation reads state with
getElderIds().first() then calls context.elderIdsDataStore.updateData, risking
lost updates; change updateElderId to perform the read-and-modify inside the
DataStore's atomic updateData block (use context.elderIdsDataStore.updateData {
current -> val m = current.elderIds.toMutableMap(); m[elderId] = name;
current.copy(elderIds = m) }) so the mutation is done atomically; reference
updateElderId, getElderIds, and context.elderIdsDataStore.updateData to locate
where to apply this change.

In `@app/src/main/java/com/konkuk/medicarecall/data/util/ElderIdsSerializer.kt`:
- Around line 20-38: The readFrom implementation closes the provided InputStream
via input.use { ... }, which conflicts with DataStore's stream management;
remove the input.use wrapper and instead read bytes without closing the stream
(e.g., call input.readBytes() inside withContext(Dispatchers.IO) directly),
leaving stream lifecycle to DataStore; keep existing Base64 decoding and the
exception fallback to defaultValue and adjust the read logic inside the readFrom
function accordingly.
- Around line 40-49: ElderIdsSerializer.writeTo currently closes the provided
OutputStream via output.use { ... }, which can conflict with DataStore's
lifecycle management; remove the use/closing and instead write to the stream
without closing it (e.g., perform output.write(encodedBytesBase64) inside the
withContext(Dispatchers.IO) block) so the OutputStream remains open for
DataStore to manage. Ensure you do not call close() on the OutputStream and keep
the Base64 encoding and IO dispatcher usage intact.

In
`@app/src/main/java/com/konkuk/medicarecall/ui/feature/home/viewmodel/HomeViewModel.kt`:
- Around line 122-137: fetchElderList currently calls
elderIdRepository.getElderIds().first() without handling exceptions, which can
leave _homeUiState.isLoading true and collapse the UI; wrap the DataStore read
in a try-catch around the call to elderIdRepository.getElderIds().first() inside
fetchElderList, on success continue populating _elderInfoList and selecting
restoredId via savedStateHandle/KEY_SELECTED_ELDER_ID as before, and on failure
set _homeUiState.update { it.copy(isLoading = false, error = /* appropriate
error or flag */) }, clear or leave _elderInfoList empty as appropriate, and log
the exception so the coroutine doesn't silently fail and the loading state is
always cleared.

In
`@app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt`:
- Around line 69-92: The code crashes when elderMap.keys.first() is called on an
empty map; update the initialization of selectedId and related state to guard
against an empty elderMap (e.g., use firstOrNull or check
elderMap.isNotEmpty()), set a safe default (such as -1 or null) for selectedId,
and adjust usage of callTimeViewModel.timeMap[selectedId] (and saved) and UI
rendering to handle the empty/invalid selectedId by showing an empty-state or
early return; locate and change the variables referenced (elderMap, selectedId,
saved, selectedTabIndex) in CallTimeScreen to implement this guard and
downstream null/empty handling.
- Around line 201-219: The code calls listState.animateScrollToItem(id) passing
an elder ID (from elderMap keys) instead of the list index; change the LazyRow
items(...) usage to itemsIndexed(...) so you receive the positional index and
use that index in animateScrollToItem, update the click handler to set
selectedId using the elder ID (value from the item) while calling
listState.animateScrollToItem(index), and ensure references to selectedId,
elderMap and listState remain consistent (functions/methods:
animateScrollToItem, items -> itemsIndexed, selectedId, elderMap, listState).

In
`@app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt`:
- Around line 29-58: The code maintains two separate elder ID states
(_elderIdMap / elderIds) causing staleness; unify them by using a single source
of truth or synchronizing updates: remove the one-off update that uses
_elderIdMap.update { elderIdRepository.getElderIds().first() } and instead
update _elderIdMap inside the same collect flow used by observeElderIds (or
collapse to only _elderIdMap and expose it as a State/StateFlow), ensuring all
updates come from elderIdRepository.getElderIds() in viewModelScope.launch
within observeElderIds so _elderIdMap, _elderIds (or the single retained state)
remain consistent.
🧹 Nitpick comments (2)
app/src/main/java/com/konkuk/medicarecall/ui/feature/settings/screen/ElderInfoScreen.kt (1)

53-58: Composable 내 로깅은 recomposition마다 실행됩니다.

디버깅 목적의 로그 호출이 Composable 함수 본문에 있어서 매 recomposition마다 실행됩니다. 프로덕션 빌드에서는 로그 스팸이 발생할 수 있으니 디버깅 후 제거하거나 LaunchedEffect로 이동하는 것을 고려해 주세요.

app/src/main/java/com/konkuk/medicarecall/data/util/ElderIdsSerializer.kt (1)

31-33: Base64 인코딩이 불필요해 보입니다.

DataStore 파일은 이미 앱 내부 저장소에 저장되므로 JSON만으로 충분합니다. Base64 인코딩은 추가적인 CPU 오버헤드와 파일 크기 증가(약 33%)를 발생시킵니다. 특별한 이유가 없다면 JSON 직접 저장을 고려해 주세요.

Comment on lines +3 to +5
data class ElderIds(
val elderIds: Map<Int, String>,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

@Serializable 어노테이션이 누락되어 런타임 크래시가 발생합니다.

ElderIdsSerializer에서 Json.decodeFromString()Json.encodeToString()을 사용하는데, 이 클래스에 @Serializable 어노테이션이 없으면 kotlinx.serialization이 동작하지 않아 런타임에 SerializationException이 발생합니다.

🐛 수정 제안
 package com.konkuk.medicarecall.data.model
 
+import kotlinx.serialization.Serializable
+
+@Serializable
 data class ElderIds(
     val elderIds: Map<Int, String>,
 )
🤖 Prompt for AI Agents
In `@app/src/main/java/com/konkuk/medicarecall/data/model/ElderIds.kt` around
lines 3 - 5, The data class ElderIds is missing the kotlinx.serialization
annotation required by ElderIdsSerializer (which calls
Json.decodeFromString/encodeToString); add the `@Serializable` annotation to the
ElderIds data class and import kotlinx.serialization.Serializable so
kotlinx.serialization can generate the serializer used by ElderIdsSerializer.

Comment on lines +27 to 31
override suspend fun updateElderId(elderId: Int, name: String) {
val elderIds = getElderIds().first().toMutableMap()
elderIds[elderId] = name
context.elderIdsDataStore.updateData { it.copy(elderIds = elderIds) }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "ElderIdRepositoryImpl.kt" -type f | head -20

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 167


🏁 Script executed:

git ls-files | grep -i repository | head -20

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 1689


🏁 Script executed:

cat -n ./app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderIdRepositoryImpl.kt

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 1816


🏁 Script executed:

cat -n ./app/src/main/java/com/konkuk/medicarecall/data/repository/ElderIdRepository.kt

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 463


🏁 Script executed:

rg "updateData" app/src/main/java/com/konkuk/medicarecall/data/ -A 2 -B 1

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 3980


🏁 Script executed:

cat -n ./app/src/main/java/com/konkuk/medicarecall/data/model/ElderIds.kt

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 216


updateElderId 메서드의 동시성 문제로 인한 데이터 유실 위험

getElderIds().first()로 읽은 후 별도로 updateData를 호출하면, 그 사이에 다른 업데이트가 발생했을 때 변경사항이 덮어써질 수 있어요. DataStore의 updateData 블록 내에서 읽고 수정하면 이런 경쟁 상태를 원자적으로 처리할 수 있습니다.

✅ 권장 수정
-    override suspend fun updateElderId(elderId: Int, name: String) {
-        val elderIds = getElderIds().first().toMutableMap()
-        elderIds[elderId] = name
-        context.elderIdsDataStore.updateData { it.copy(elderIds = elderIds) }
-    }
+    override suspend fun updateElderId(elderId: Int, name: String) {
+        context.elderIdsDataStore.updateData { current ->
+            current.copy(elderIds = current.elderIds + (elderId to name))
+        }
+    }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderIdRepositoryImpl.kt`
around lines 27 - 31, The current updateElderId implementation reads state with
getElderIds().first() then calls context.elderIdsDataStore.updateData, risking
lost updates; change updateElderId to perform the read-and-modify inside the
DataStore's atomic updateData block (use context.elderIdsDataStore.updateData {
current -> val m = current.elderIds.toMutableMap(); m[elderId] = name;
current.copy(elderIds = m) }) so the mutation is done atomically; reference
updateElderId, getElderIds, and context.elderIdsDataStore.updateData to locate
where to apply this change.

Comment on lines +20 to +38
override suspend fun readFrom(input: InputStream): ElderIds {
return try {
val encodedBytes = withContext(Dispatchers.IO) {
input.use { it.readBytes() }
}

// 빈 파일인 경우 기본값 반환
if (encodedBytes.isEmpty()) {
return defaultValue
}

val decodedBytesBase64 = Base64.getDecoder().decode(encodedBytes)
val decodedJsonString = decodedBytesBase64.decodeToString()
Json.decodeFromString(decodedJsonString)
} catch (e: Exception) {
Log.d("ElderIdsSerializer", "Failed to read ElderIds: ${e.message}")
defaultValue
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

input.use {} 도 동일하게 스트림 관리 충돌 가능성이 있습니다.

readFrom에서도 input.use {}로 스트림을 닫고 있습니다. DataStore가 스트림을 관리하므로 직접 닫지 않는 것이 좋습니다.

🛠️ 수정 제안
     override suspend fun readFrom(input: InputStream): ElderIds {
         return try {
             val encodedBytes = withContext(Dispatchers.IO) {
-                input.use { it.readBytes() }
+                input.readBytes()
             }
 
             // 빈 파일인 경우 기본값 반환
             if (encodedBytes.isEmpty()) {
                 return defaultValue
             }
 
             val decodedBytesBase64 = Base64.getDecoder().decode(encodedBytes)
             val decodedJsonString = decodedBytesBase64.decodeToString()
             Json.decodeFromString(decodedJsonString)
         } catch (e: Exception) {
             Log.d("ElderIdsSerializer", "Failed to read ElderIds: ${e.message}")
             defaultValue
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override suspend fun readFrom(input: InputStream): ElderIds {
return try {
val encodedBytes = withContext(Dispatchers.IO) {
input.use { it.readBytes() }
}
// 빈 파일인 경우 기본값 반환
if (encodedBytes.isEmpty()) {
return defaultValue
}
val decodedBytesBase64 = Base64.getDecoder().decode(encodedBytes)
val decodedJsonString = decodedBytesBase64.decodeToString()
Json.decodeFromString(decodedJsonString)
} catch (e: Exception) {
Log.d("ElderIdsSerializer", "Failed to read ElderIds: ${e.message}")
defaultValue
}
}
override suspend fun readFrom(input: InputStream): ElderIds {
return try {
val encodedBytes = withContext(Dispatchers.IO) {
input.readBytes()
}
// 빈 파일인 경우 기본값 반환
if (encodedBytes.isEmpty()) {
return defaultValue
}
val decodedBytesBase64 = Base64.getDecoder().decode(encodedBytes)
val decodedJsonString = decodedBytesBase64.decodeToString()
Json.decodeFromString(decodedJsonString)
} catch (e: Exception) {
Log.d("ElderIdsSerializer", "Failed to read ElderIds: ${e.message}")
defaultValue
}
}
🤖 Prompt for AI Agents
In `@app/src/main/java/com/konkuk/medicarecall/data/util/ElderIdsSerializer.kt`
around lines 20 - 38, The readFrom implementation closes the provided
InputStream via input.use { ... }, which conflicts with DataStore's stream
management; remove the input.use wrapper and instead read bytes without closing
the stream (e.g., call input.readBytes() inside withContext(Dispatchers.IO)
directly), leaving stream lifecycle to DataStore; keep existing Base64 decoding
and the exception fallback to defaultValue and adjust the read logic inside the
readFrom function accordingly.

Comment on lines +40 to +49
override suspend fun writeTo(t: ElderIds, output: OutputStream) {
val json = Json.Default.encodeToString(t)
val bytes = json.toByteArray()
val encodedBytesBase64 = Base64.getEncoder().encode(bytes)
withContext(Dispatchers.IO) {
output.use {
it.write(encodedBytesBase64)
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

output.use {} 사용 시 DataStore와 충돌 가능성이 있습니다.

DataStore는 writeTo에 전달된 OutputStream의 생명주기를 내부적으로 관리합니다. use {} 블록으로 스트림을 닫으면 DataStore가 후속 작업(예: flush, 트랜잭션 커밋)을 수행할 때 문제가 발생할 수 있습니다.

🛠️ 수정 제안
     override suspend fun writeTo(t: ElderIds, output: OutputStream) {
         val json = Json.Default.encodeToString(t)
         val bytes = json.toByteArray()
         val encodedBytesBase64 = Base64.getEncoder().encode(bytes)
         withContext(Dispatchers.IO) {
-            output.use {
-                it.write(encodedBytesBase64)
-            }
+            output.write(encodedBytesBase64)
         }
     }
🤖 Prompt for AI Agents
In `@app/src/main/java/com/konkuk/medicarecall/data/util/ElderIdsSerializer.kt`
around lines 40 - 49, ElderIdsSerializer.writeTo currently closes the provided
OutputStream via output.use { ... }, which can conflict with DataStore's
lifecycle management; remove the use/closing and instead write to the stream
without closing it (e.g., perform output.write(encodedBytesBase64) inside the
withContext(Dispatchers.IO) block) so the OutputStream remains open for
DataStore to manage. Ensure you do not call close() on the OutputStream and keep
the Base64 encoding and IO dispatcher usage intact.

Comment on lines +122 to +137
// 로컬에서 어르신 전체 목록을 불러옴
fun fetchElderList() {
viewModelScope.launch {
if (_elderInfoList.value.isEmpty()) {
_homeUiState.update { it.copy(isLoading = true) }
}
eldersInfoRepository.getElders()
.onSuccess { elders ->
_elderInfoList.value = elders.map {
ElderInfo(id = it.elderId, name = it.name, phone = it.phone)
}
val restoredId = savedStateHandle.get<Int?>(KEY_SELECTED_ELDER_ID)
if (restoredId != null && _elderInfoList.value.any { it.id == restoredId }) {
_selectedElderId.value = restoredId
} else if (_selectedElderId.value == null && _elderInfoList.value.isNotEmpty()) {
_selectedElderId.value = _elderInfoList.value.first().id
}
}
.onFailure { error ->
Log.e(TAG, "어르신 목록 로딩 실패", error)
_homeUiState.update { it.copy(isLoading = false) }
}
val elderIdMap = elderIdRepository.getElderIds().first()
_elderInfoList.value = elderIdMap.map {
ElderInfo(id = it.key, name = it.value, phone = null)
}
val restoredId = savedStateHandle.get<Int?>(KEY_SELECTED_ELDER_ID)
if (restoredId != null && _elderInfoList.value.any { it.id == restoredId }) {
_selectedElderId.value = restoredId
} else if (_selectedElderId.value == null && _elderInfoList.value.isNotEmpty()) {
_selectedElderId.value = _elderInfoList.value.first().id
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd app/src/main/java/com/konkuk/medicarecall/ui/feature/home/viewmodel && \
wc -l HomeViewModel.kt && \
cat -n HomeViewModel.kt | sed -n '110,150p'

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 2005


🏁 Script executed:

# Find elderIdRepository definition to understand getElderIds() return type
fd -t f "*.kt" | xargs rg "elderIdRepository\s*[:=]" -A 2 -B 2 | head -50

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 3004


🏁 Script executed:

# Search for ElderIdRepository definition
fd -t f "ElderIdRepository.kt" -o "*Repository*.kt" | xargs cat -n | head -100

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 228


🏁 Script executed:

# Search for ElderIdRepository interface/class definition
fd -type f -name "*.kt" | xargs rg "class ElderIdRepository|interface ElderIdRepository" -A 10

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 2330


🏁 Script executed:

# Search for getElderIds() method definition
fd -type f -name "*.kt" | xargs rg "fun getElderIds" -A 5

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 1276


🏁 Script executed:

# Check if there's DataStore usage or Flow involved
fd -type f -name "*.kt" | xargs rg "DataStore|Flow" | grep -i "elderid" | head -20

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 2622


🏁 Script executed:

# Check the complete ElderIdRepositoryImpl to see DataStore setup and error handling
cat -n app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/ElderIdRepositoryImpl.kt

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 1816


🏁 Script executed:

# Look for error handling patterns in HomeViewModel
cat -n app/src/main/java/com/konkuk/medicarecall/ui/feature/home/viewmodel/HomeViewModel.kt | sed -n '140,200p'

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 3106


🏁 Script executed:

# Check if there's a global error handler or CoroutineExceptionHandler in HomeViewModel
rg "CoroutineExceptionHandler|try\s*\{|catch|CoroutineContext" app/src/main/java/com/konkuk/medicarecall/ui/feature/home/viewmodel/HomeViewModel.kt -A 3

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 636


DataStore 읽기 실패 시 예외 처리 필요

getElderIds().first()에서 예외 발생 시 코루틴이 조용히 실패하고, 로딩 상태가 true로 남아 UI가 멈춥니다. 같은 ViewModel의 다른 메서드들(fetchHomeSummaryForToday, forceRefreshHomeData)은 이미 try-catch를 적용하고 있으므로, 이 메서드도 일관성 있게 예외 처리해야 합니다.

💡 제안 수정
-            val elderIdMap = elderIdRepository.getElderIds().first()
+            val elderIdMap = try {
+                elderIdRepository.getElderIds().first()
+            } catch (e: Exception) {
+                Log.e(TAG, "elderIds load failed", e)
+                _homeUiState.update { it.copy(isLoading = false) }
+                return@launch
+            }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/konkuk/medicarecall/ui/feature/home/viewmodel/HomeViewModel.kt`
around lines 122 - 137, fetchElderList currently calls
elderIdRepository.getElderIds().first() without handling exceptions, which can
leave _homeUiState.isLoading true and collapse the UI; wrap the DataStore read
in a try-catch around the call to elderIdRepository.getElderIds().first() inside
fetchElderList, on success continue populating _elderInfoList and selecting
restoredId via savedStateHandle/KEY_SELECTED_ELDER_ID as before, and on failure
set _homeUiState.update { it.copy(isLoading = false, error = /* appropriate
error or flag */) }, clear or leave _elderInfoList empty as appropriate, and log
the exception so the coroutine doesn't silently fail and the loading state is
always cleared.

Comment on lines +69 to +92
val elderMap = callTimeViewModel.elderIds
val isLoading = callTimeViewModel.isLoading.value

val isLoading by eldersInfoViewModel.isLoading.collectAsStateWithLifecycle()
val error by eldersInfoViewModel.error.collectAsStateWithLifecycle()
val nameIdList by eldersInfoViewModel.elderNameIdMapList.collectAsStateWithLifecycle()

when {
isLoading -> {
Box(
Modifier
.fillMaxSize()
.background(MediCareCallTheme.colors.bg)
.systemBarsPadding(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = MediCareCallTheme.colors.main,
modifier = Modifier.align(Alignment.Center),
)
}
return
}

error != null -> {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("어르신 정보를 불러오지 못했어요.\n잠시 후 다시 시도해 주세요.")
Spacer(Modifier.height(12.dp))
CTAButton(
type = CTAButtonType.GREEN,
text = "다시 시도",
onClick = { eldersInfoViewModel.refresh() },
)
}
return
}

nameIdList.isEmpty() -> {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("등록된 어르신이 없습니다.")
}
return
if (isLoading) {
Box(
Modifier
.fillMaxSize()
.background(MediCareCallTheme.colors.bg),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = MediCareCallTheme.colors.main,
)
}
return
}

val scrollState = rememberScrollState() // 스크롤 상태
val elderNames = nameIdList.map { it.keys.first() } // 어르신 이름 리스트
val elderIds = nameIdList.map { it.values.first() } // 어르신 아이디 리스트

// ViewModel 상태 구독
val showBottomSheet by callTimeViewModel.showBottomSheet.collectAsStateWithLifecycle()
val selectedIndex by callTimeViewModel.selectedIndex.collectAsStateWithLifecycle()
val selectedTabIndex by callTimeViewModel.selectedTabIndex.collectAsStateWithLifecycle()
val timeMap by callTimeViewModel.timeMap.collectAsStateWithLifecycle()
var showBottomSheet by remember { mutableStateOf(false) } // 하단 시트 제어

val selectedId = elderIds.getOrNull(selectedIndex) ?: 0 // 선택된 어르신 아이디
val saved = timeMap[selectedId] ?: CallTimes()
// var selectedIndex by remember { mutableIntStateOf(0) } // 선택된 어르신 인덱스
var selectedId by remember { mutableIntStateOf(elderMap.keys.first()) } // 선택된 어르신 아이디
val saved = callTimeViewModel.timeMap[selectedId] ?: CallTimes()
var selectedTabIndex by remember { mutableIntStateOf(0) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n "app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt"

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 18126


🏁 Script executed:

find . -name "CallTimeViewModel.kt" -type f

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 179


🏁 Script executed:

cat -n "./app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt"

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 4997


🏁 Script executed:

# Check if there are any other safeguards in the UI layer
rg "isLoading|isCompleteFor|isAllComplete" app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt -A 2 -B 2

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 386


🏁 Script executed:

# Check if isAllComplete is called on an empty set anywhere
rg "isAllComplete" app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/ -B 2 -A 2

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 1300


빈 elderMap에서 first() 호출로 크래시 가능

로딩이 끝났지만 elderMap이 비어있는 경우 first()가 터집니다. isLoading 플래그는 초기 데이터 로드 중에는 false이고, Flow 구독이 비동기로 진행되기 때문에 화면이 렌더링될 때 map이 아직 비어있을 수 있습니다.

🛠️ 제안 수정 (빈 목록 가드)
-    var selectedId by remember { mutableIntStateOf(elderMap.keys.first()) } // 선택된 어르신 아이디
+    val firstId = elderMap.keys.firstOrNull()
+    if (firstId == null) {
+        // TODO: empty state UI
+        return
+    }
+    var selectedId by remember(firstId) { mutableIntStateOf(firstId) } // 선택된 어르신 아이디
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt`
around lines 69 - 92, The code crashes when elderMap.keys.first() is called on
an empty map; update the initialization of selectedId and related state to guard
against an empty elderMap (e.g., use firstOrNull or check
elderMap.isNotEmpty()), set a safe default (such as -1 or null) for selectedId,
and adjust usage of callTimeViewModel.timeMap[selectedId] (and saved) and UI
rendering to handle the empty/invalid selectedId by showing an empty-state or
early return; locate and change the variables referenced (elderMap, selectedId,
saved, selectedTabIndex) in CallTimeScreen to implement this guard and
downstream null/empty handling.

Comment on lines +201 to 219
items(elderMap.keys.toList()) { id ->
Text(
text = name,
text = elderMap[id] ?: "",
modifier = Modifier
.clip(CircleShape)
.border(
width = if (idx == selectedIndex) 0.dp else (1.2).dp,
color = if (idx == selectedIndex) MediCareCallTheme.colors.main else MediCareCallTheme.colors.gray2,
width = if (id == selectedId) 0.dp else (1.2).dp,
color = if (id == selectedId) MediCareCallTheme.colors.main else MediCareCallTheme.colors.gray2,
shape = RoundedCornerShape(100.dp),
)
.background(
color = if (idx == selectedIndex) MediCareCallTheme.colors.main else Color.Transparent,
color = if (id == selectedId) MediCareCallTheme.colors.main else Color.Transparent,
shape = RoundedCornerShape(100.dp),
)
.clickable {
callTimeViewModel.setSelectedIndex(idx)
selectedId = id
scope.launch {
listState.animateScrollToItem(idx)
listState.animateScrollToItem(id)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd "CallTimeScreen.kt" --type f

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 171


🏁 Script executed:

wc -l "app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt"

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 175


🏁 Script executed:

cat -n "app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt" | sed -n '180,230p'

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 2751


🏁 Script executed:

cat -n "app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt" | sed -n '1,100p'

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 5050


🏁 Script executed:

cat -n "app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt" | sed -n '55,110p'

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 2349


🏁 Script executed:

rg "class CallTimeViewModel" -A 30 --type kt

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 105


🏁 Script executed:

rg "class CallTimeViewModel" -A 30

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 4175


🏁 Script executed:

rg "animateScrollToItem" --context 3

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 1074


🏁 Script executed:

cat -n "app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt" | head -50

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 2216


🏁 Script executed:

cat -n "app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt" | sed -n '50,100p'

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 2105


🌐 Web query:

Jetpack Compose animateScrollToItem LazyListState index parameter

💡 Result:

In LazyListState.animateScrollToItem(index, scrollOffset):

  • index is the 0-based position in the lazy list (the same index you get in items(...) { index -> ... }). It must be non‑negative. [1]
  • It’s the global list index across everything you emit into the LazyColumn/LazyRow—including headers like stickyHeader (those also have a “header index” which is the item’s position in the total list). [2]
  • scrollOffset is an extra pixel offset applied after positioning the item; in a normal top-to-bottom list, positive values push the item further up (partly offscreen). [1]

If you’re using stable keys, note that animateScrollToItem still scrolls by index, not key—you must find the item’s current index yourself before calling it. [2]

Sources: Android Developers API for LazyListState.animateScrollToItem [1], LazyListScope (global index/key behavior) [2].


animateScrollToItem()에 인덱스가 아닌 엘더 ID를 넘기는 문제

animateScrollToItem()은 LazyRow의 **위치 인덱스(0, 1, 2...)**를 기대하는데, 현재 코드는 elderMap의 키(엘더 ID)를 직접 넘기고 있습니다. 엘더 ID가 임의의 정수값(예: 101, 202, 303)이므로 스크롤 실패나 크래시가 발생할 수 있습니다.

itemsIndexed를 사용해서 인덱스를 얻어 전달하세요:

수정 방법
-import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.itemsIndexed
...
-                items(elderMap.keys.toList()) { id ->
+                itemsIndexed(elderMap.keys.toList()) { index, id ->
...
                             .clickable {
                                 selectedId = id
                                 scope.launch {
-                                    listState.animateScrollToItem(id)
+                                    listState.animateScrollToItem(index)
                                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
items(elderMap.keys.toList()) { id ->
Text(
text = name,
text = elderMap[id] ?: "",
modifier = Modifier
.clip(CircleShape)
.border(
width = if (idx == selectedIndex) 0.dp else (1.2).dp,
color = if (idx == selectedIndex) MediCareCallTheme.colors.main else MediCareCallTheme.colors.gray2,
width = if (id == selectedId) 0.dp else (1.2).dp,
color = if (id == selectedId) MediCareCallTheme.colors.main else MediCareCallTheme.colors.gray2,
shape = RoundedCornerShape(100.dp),
)
.background(
color = if (idx == selectedIndex) MediCareCallTheme.colors.main else Color.Transparent,
color = if (id == selectedId) MediCareCallTheme.colors.main else Color.Transparent,
shape = RoundedCornerShape(100.dp),
)
.clickable {
callTimeViewModel.setSelectedIndex(idx)
selectedId = id
scope.launch {
listState.animateScrollToItem(idx)
listState.animateScrollToItem(id)
}
itemsIndexed(elderMap.keys.toList()) { index, id ->
Text(
text = elderMap[id] ?: "",
modifier = Modifier
.clip(CircleShape)
.border(
width = if (id == selectedId) 0.dp else (1.2).dp,
color = if (id == selectedId) MediCareCallTheme.colors.main else MediCareCallTheme.colors.gray2,
shape = RoundedCornerShape(100.dp),
)
.background(
color = if (id == selectedId) MediCareCallTheme.colors.main else Color.Transparent,
shape = RoundedCornerShape(100.dp),
)
.clickable {
selectedId = id
scope.launch {
listState.animateScrollToItem(index)
}
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt`
around lines 201 - 219, The code calls listState.animateScrollToItem(id) passing
an elder ID (from elderMap keys) instead of the list index; change the LazyRow
items(...) usage to itemsIndexed(...) so you receive the positional index and
use that index in animateScrollToItem, update the click handler to set
selectedId using the elder ID (value from the item) while calling
listState.animateScrollToItem(index), and ensure references to selectedId,
elderMap and listState remain consistent (functions/methods:
animateScrollToItem, items -> itemsIndexed, selectedId, elderMap, listState).

Comment on lines +29 to +58
private val _elderIdMap = MutableStateFlow(emptyMap<Int, String>())
val elderIdMap = _elderIdMap.asStateFlow()

// Time data
private val _timeMap = MutableStateFlow<Map<Int, CallTimes>>(emptyMap())
val timeMap: StateFlow<Map<Int, CallTimes>> = _timeMap.asStateFlow()
private val _showBottomSheet = mutableStateOf(false)
private val _selectedIndex = mutableIntStateOf(0)
private val _selectedTabIndex = mutableIntStateOf(0)

// UI state
private val _showBottomSheet = MutableStateFlow(false)
val showBottomSheet: StateFlow<Boolean> = _showBottomSheet.asStateFlow()

private val _selectedIndex = MutableStateFlow(0)
val selectedIndex: StateFlow<Int> = _selectedIndex.asStateFlow()
init {
viewModelScope.launch {
_elderIdMap.update { elderIdRepository.getElderIds().first(); }
}
}

private val _selectedTabIndex = MutableStateFlow(0)
val selectedTabIndex: StateFlow<Int> = _selectedTabIndex.asStateFlow()
// Flow -> State 로 뱐환해서 보관
private val _elderIds = mutableStateOf<Map<Int, String>>(emptyMap())
val elderIds get() = _elderIds.value // UI에서 접근할 값

// Async state
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
init {
observeElderIds()
}

private val _lastError = MutableStateFlow<Throwable?>(null)
val lastError: StateFlow<Throwable?> = _lastError.asStateFlow()
// suspend + Flow 안전하게 처리하는 함수
private fun observeElderIds() {
viewModelScope.launch {
try {
elderIdRepository.getElderIds()
.collect { result ->
_elderIds.value = result
Log.d("CallTimeViewModel", "elderIds 업데이트: $result")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "CallTimeViewModel.kt" | head -5

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 179


🏁 Script executed:

cat -n app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 4997


🏁 Script executed:

rg "_elderIdMap|_elderIds" --type kotlin -B 2 -A 2

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 3074


🏁 Script executed:

rg "elderIdMap|elderIds" --type kotlin -B 1 -A 1 | grep -v "CallTimeViewModel.kt"

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 6249


🏁 Script executed:

find . -type kotlin -name "*.kt" -path "*/ui/*" | xargs grep -l "elderIdMap\|elderIds" 2>/dev/null

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 115


🏁 Script executed:

rg "\.elderIdMap" --type kotlin -B 2 -A 2

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 61


🏁 Script executed:

grep -n "elderIdMap\|elderIds" app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/screen/CallTimeScreen.kt

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 190


🏁 Script executed:

find . -type f -name "CallTimeScreen.kt" | head -1 | xargs cat -n

Repository: Medicare-Call/Medicare-Call-Android

Length of output: 18126


중복된 elder ID 상태로 인한 staleness 위험

_elderIdMap.first()로 한 번만 업데이트되어 이후 변경사항을 놓치지만, _elderIds는 계속 collect되므로 둘이 불일치할 수 있습니다. 특히 elderIdMap이 공개 API인데 stale 상태가 되는 것은 위험해요.

한 가지 source of truth로 통일하거나, 둘 다 유지한다면 같은 업데이트 흐름에서 동기화해주세요.

🧩 제안 수정
-    init {
-        viewModelScope.launch {
-            _elderIdMap.update { elderIdRepository.getElderIds().first(); }
-        }
-    }
...
                 elderIdRepository.getElderIds()
                     .collect { result ->
+                        _elderIdMap.update { result }
                         _elderIds.value = result
                         Log.d("CallTimeViewModel", "elderIds 업데이트: $result")
                     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private val _elderIdMap = MutableStateFlow(emptyMap<Int, String>())
val elderIdMap = _elderIdMap.asStateFlow()
// Time data
private val _timeMap = MutableStateFlow<Map<Int, CallTimes>>(emptyMap())
val timeMap: StateFlow<Map<Int, CallTimes>> = _timeMap.asStateFlow()
private val _showBottomSheet = mutableStateOf(false)
private val _selectedIndex = mutableIntStateOf(0)
private val _selectedTabIndex = mutableIntStateOf(0)
// UI state
private val _showBottomSheet = MutableStateFlow(false)
val showBottomSheet: StateFlow<Boolean> = _showBottomSheet.asStateFlow()
private val _selectedIndex = MutableStateFlow(0)
val selectedIndex: StateFlow<Int> = _selectedIndex.asStateFlow()
init {
viewModelScope.launch {
_elderIdMap.update { elderIdRepository.getElderIds().first(); }
}
}
private val _selectedTabIndex = MutableStateFlow(0)
val selectedTabIndex: StateFlow<Int> = _selectedTabIndex.asStateFlow()
// Flow -> State 로 뱐환해서 보관
private val _elderIds = mutableStateOf<Map<Int, String>>(emptyMap())
val elderIds get() = _elderIds.value // UI에서 접근할 값
// Async state
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
init {
observeElderIds()
}
private val _lastError = MutableStateFlow<Throwable?>(null)
val lastError: StateFlow<Throwable?> = _lastError.asStateFlow()
// suspend + Flow 안전하게 처리하는 함수
private fun observeElderIds() {
viewModelScope.launch {
try {
elderIdRepository.getElderIds()
.collect { result ->
_elderIds.value = result
Log.d("CallTimeViewModel", "elderIds 업데이트: $result")
}
private val _elderIdMap = MutableStateFlow(emptyMap<Int, String>())
val elderIdMap = _elderIdMap.asStateFlow()
private val _showBottomSheet = mutableStateOf(false)
private val _selectedIndex = mutableIntStateOf(0)
private val _selectedTabIndex = mutableIntStateOf(0)
// Flow -> State 로 뱐환해서 보관
private val _elderIds = mutableStateOf<Map<Int, String>>(emptyMap())
val elderIds get() = _elderIds.value // UI에서 접근할 값
init {
observeElderIds()
}
// suspend + Flow 안전하게 처리하는 함수
private fun observeElderIds() {
viewModelScope.launch {
try {
elderIdRepository.getElderIds()
.collect { result ->
_elderIdMap.update { result }
_elderIds.value = result
Log.d("CallTimeViewModel", "elderIds 업데이트: $result")
}
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/konkuk/medicarecall/ui/feature/login/carecall/viewmodel/CallTimeViewModel.kt`
around lines 29 - 58, The code maintains two separate elder ID states
(_elderIdMap / elderIds) causing staleness; unify them by using a single source
of truth or synchronizing updates: remove the one-off update that uses
_elderIdMap.update { elderIdRepository.getElderIds().first() } and instead
update _elderIdMap inside the same collect flow used by observeElderIds (or
collapse to only _elderIdMap and expose it as a State/StateFlow), ensuring all
updates come from elderIdRepository.getElderIds() in viewModelScope.launch
within observeElderIds so _elderIdMap, _elderIds (or the single retained state)
remain consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants