From d961b03ec5a77a632828c549f7085d51901331e5 Mon Sep 17 00:00:00 2001 From: jonathanjohnson Date: Tue, 6 Jan 2026 15:26:32 -0500 Subject: [PATCH 1/3] Refactor ScenesViewModel to remove unnecessary suspend modifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove suspend from getScenes, getScene, getLightsForRoom, removeScene, and saveOrUpdateScene since they use viewModelScope.launch internally - Add missing early return in getLightsForRoom for invalid room ID - Remove unnecessary coroutine scopes from ScenesScreen composables - Remove unused imports (rememberCoroutineScope, kotlinx.coroutines.launch) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../carbon/ui/lumen/scenes/ScenesScreen.kt | 10 +++------- .../carbon/ui/lumen/scenes/ScenesViewModel.kt | 11 ++++++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesScreen.kt b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesScreen.kt index bbec013..34f1631 100644 --- a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesScreen.kt +++ b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,7 +32,6 @@ import com.atomicrobot.carbon.data.lumen.SceneModel import com.atomicrobot.carbon.data.lumen.dto.LumenScene import com.atomicrobot.carbon.data.lumen.dto.SceneAndRoomName import com.atomicrobot.carbon.ui.lumen.LumenIndeterminateIndicator -import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel @Composable @@ -119,14 +117,13 @@ fun AddSceneTask( viewModel: ScenesViewModel = koinViewModel(), onDismissed: () -> Unit, ) { - val coroutine = rememberCoroutineScope() SceneDetailsList( sceneId = 0L, viewModel = viewModel, newScene = true, onDismissed = onDismissed, ) { - coroutine.launch { viewModel.saveOrUpdateScene(it) } + viewModel.saveOrUpdateScene(it) onDismissed() } } @@ -137,18 +134,17 @@ fun EditSceneTask( viewModel: ScenesViewModel = koinViewModel(), onDismissed: () -> Unit, ) { - val coroutine = rememberCoroutineScope() SceneDetailsList( sceneId = sceneId, viewModel = viewModel, newScene = false, onDismissed = onDismissed, onDeleteAction = { - coroutine.launch { viewModel.removeScene(sceneId) } + viewModel.removeScene(sceneId) onDismissed() }, ) { - coroutine.launch { viewModel.saveOrUpdateScene(it) } + viewModel.saveOrUpdateScene(it) onDismissed() } } diff --git a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt index 378ce33..ff8db8e 100644 --- a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt +++ b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt @@ -77,7 +77,7 @@ class ScenesViewModel( val sceneDetailsLightUIState: StateFlow get() = _sceneDetailsLightUIState - suspend fun getScenes() { + fun getScenes() { // Update the UI state to indicate that we are loading. _mainUiState.value = _mainUiState.value.copy(mainScreenState = Scenes.Loading) viewModelScope.launch { @@ -87,7 +87,7 @@ class ScenesViewModel( } } - suspend fun getScene(sceneId: Long) { + fun getScene(sceneId: Long) { if (sceneId == 0L) { viewModelScope.launch { _sceneDetailsUIState.value = @@ -116,13 +116,14 @@ class ScenesViewModel( } } - suspend fun getLightsForRoom(roomId: Long) { + fun getLightsForRoom(roomId: Long) { if (roomId == 0L) { // Invalid room ID, use an empty light list for the state _sceneDetailsLightUIState.value = _sceneDetailsLightUIState.value.copy( sceneDetailsLightState = SceneDetailsLights.Result(emptyList()), ) + return } _sceneDetailsLightUIState.value = @@ -149,13 +150,13 @@ class ScenesViewModel( .copy(sceneDetailsLightState = SceneDetailsLights.LoadingLights) } - suspend fun removeScene(sceneId: Long) { + fun removeScene(sceneId: Long) { viewModelScope.launch(Dispatchers.IO) { sceneDao.delete(sceneId) } } - suspend fun saveOrUpdateScene(scene: SceneModel) { + fun saveOrUpdateScene(scene: SceneModel) { viewModelScope.launch(Dispatchers.IO) { val sceneId: Long = if (scene.sceneId < 1) { From ecae5a3c24e0f7de58bfaa33bff4c139a0ef482f Mon Sep 17 00:00:00 2001 From: jonathanjohnson Date: Tue, 6 Jan 2026 15:36:30 -0500 Subject: [PATCH 2/3] Use MutableStateFlow.update for thread-safe state updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace direct value assignments with update function calls for atomic state updates in ScenesViewModel. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../carbon/ui/lumen/scenes/ScenesViewModel.kt | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt index ff8db8e..5aa2da4 100644 --- a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt +++ b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt @@ -17,6 +17,7 @@ import com.atomicrobot.carbon.data.lumen.toLumenScene import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class ScenesViewModel( @@ -79,10 +80,10 @@ class ScenesViewModel( fun getScenes() { // Update the UI state to indicate that we are loading. - _mainUiState.value = _mainUiState.value.copy(mainScreenState = Scenes.Loading) + _mainUiState.update { it.copy(mainScreenState = Scenes.Loading) } viewModelScope.launch { - sceneDao.getScenesWithRoom().collect { - _mainUiState.value = _mainUiState.value.copy(mainScreenState = Scenes.Result(it)) + sceneDao.getScenesWithRoom().collect { scenes -> + _mainUiState.update { it.copy(mainScreenState = Scenes.Result(scenes)) } } } } @@ -90,8 +91,8 @@ class ScenesViewModel( fun getScene(sceneId: Long) { if (sceneId == 0L) { viewModelScope.launch { - _sceneDetailsUIState.value = - _sceneDetailsUIState.value.copy( + _sceneDetailsUIState.update { + it.copy( SceneDetails.Result( scene = SceneAndLightsWithRoom( @@ -102,52 +103,44 @@ class ScenesViewModel( rooms = roomDao.getRoomNamesAndIds(), ), ) + } } return } - _sceneDetailsUIState.value = - _sceneDetailsUIState.value.copy(sceneDetailsState = SceneDetails.LoadingDetails) + _sceneDetailsUIState.update { it.copy(sceneDetailsState = SceneDetails.LoadingDetails) } viewModelScope.launch { val scene = sceneDao.getSceneAndLightsWithRoom(sceneId) val rooms = roomDao.getRoomNamesAndIds() - _sceneDetailsUIState.value = - _sceneDetailsUIState.value.copy(sceneDetailsState = SceneDetails.Result(scene, rooms)) + _sceneDetailsUIState.update { it.copy(sceneDetailsState = SceneDetails.Result(scene, rooms)) } } } fun getLightsForRoom(roomId: Long) { if (roomId == 0L) { // Invalid room ID, use an empty light list for the state - _sceneDetailsLightUIState.value = - _sceneDetailsLightUIState.value.copy( - sceneDetailsLightState = SceneDetailsLights.Result(emptyList()), - ) + _sceneDetailsLightUIState.update { + it.copy(sceneDetailsLightState = SceneDetailsLights.Result(emptyList())) + } return } - _sceneDetailsLightUIState.value = - _sceneDetailsLightUIState.value.copy( - sceneDetailsLightState = SceneDetailsLights.LoadingLights, - ) + _sceneDetailsLightUIState.update { + it.copy(sceneDetailsLightState = SceneDetailsLights.LoadingLights) + } viewModelScope.launch { - lightDao.getAllLightsForRoom(roomId).collect { - _sceneDetailsLightUIState.value = - _sceneDetailsLightUIState.value.copy( - sceneDetailsLightState = SceneDetailsLights.Result(it), - ) + lightDao.getAllLightsForRoom(roomId).collect { lights -> + _sceneDetailsLightUIState.update { + it.copy(sceneDetailsLightState = SceneDetailsLights.Result(lights)) + } } } } fun resetSceneDetailsState() { // Initialize the details state to loading... - _sceneDetailsUIState.value = - _sceneDetailsUIState.value - .copy(sceneDetailsState = SceneDetails.LoadingDetails) - _sceneDetailsLightUIState.value = - _sceneDetailsLightUIState.value - .copy(sceneDetailsLightState = SceneDetailsLights.LoadingLights) + _sceneDetailsUIState.update { it.copy(sceneDetailsState = SceneDetails.LoadingDetails) } + _sceneDetailsLightUIState.update { it.copy(sceneDetailsLightState = SceneDetailsLights.LoadingLights) } } fun removeScene(sceneId: Long) { From 6b91d6ac48aba2f55919f7e3b612b1db3fcf1728 Mon Sep 17 00:00:00 2001 From: jonathanjohnson Date: Tue, 6 Jan 2026 15:37:37 -0500 Subject: [PATCH 3/3] One more manual update --- .../com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt index 5aa2da4..5600ecc 100644 --- a/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt +++ b/app/src/main/java/com/atomicrobot/carbon/ui/lumen/scenes/ScenesViewModel.kt @@ -93,7 +93,7 @@ class ScenesViewModel( viewModelScope.launch { _sceneDetailsUIState.update { it.copy( - SceneDetails.Result( + sceneDetailsState = SceneDetails.Result( scene = SceneAndLightsWithRoom( scene = LumenScene(),