From 225bdbc2bc3f8fdb777304c94c884ea7d624e2d5 Mon Sep 17 00:00:00 2001 From: Trey t Date: Fri, 14 Nov 2025 14:33:42 -0600 Subject: [PATCH] Implement error handling with ApiResultHandler on key screens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applied the new error handling utilities to ResidencesScreen and ResidenceDetailScreen, replacing inline error displays with popup dialogs. Changes: - ResidencesScreen: Replaced inline error UI with ApiResultHandler - Shows error dialog popup instead of error message in center of screen - Maintains all functionality while improving UX - ResidenceDetailScreen: Comprehensive error handling implementation - Main residence loading uses ApiResultHandler with custom loading content - Generate report operation uses HandleErrors() extension - Delete residence operation uses HandleErrors() extension - All operations now show retry/cancel dialogs on error Benefits: - Consistent error handling UX across screens - Users can retry failed operations without navigating away - Cleaner code with less boilerplate error handling - Error dialogs are non-blocking and dismissible These screens now demonstrate the pattern for implementing error handling across the rest of the app using the error handling utilities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../ui/screens/ResidenceDetailScreen.kt | 100 ++++++------------ .../mycrib/ui/screens/ResidencesScreen.kt | 52 ++------- 2 files changed, 40 insertions(+), 112 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt index b94a507..2b91259 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidenceDetailScreen.kt @@ -15,7 +15,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.mycrib.android.ui.components.AddNewTaskDialog +import com.mycrib.android.ui.components.ApiResultHandler import com.mycrib.android.ui.components.CompleteTaskDialog +import com.mycrib.android.ui.components.HandleErrors import com.mycrib.android.ui.components.ManageUsersDialog import com.mycrib.android.ui.components.common.InfoCard import com.mycrib.android.ui.components.residence.PropertyDetailItem @@ -116,6 +118,12 @@ fun ResidenceDetailScreen( } // Handle generate report state + // Handle errors for generate report + generateReportState.HandleErrors( + onRetry = { residenceViewModel.generateMaintenanceReport(residenceId) }, + errorTitle = "Failed to Generate Report" + ) + LaunchedEffect(generateReportState) { when (generateReportState) { is ApiResult.Success -> { @@ -124,15 +132,16 @@ fun ResidenceDetailScreen( showReportSnackbar = true residenceViewModel.resetGenerateReportState() } - is ApiResult.Error -> { - reportMessage = (generateReportState as ApiResult.Error).message - showReportSnackbar = true - residenceViewModel.resetGenerateReportState() - } else -> {} } } + // Handle errors for delete residence + deleteState.HandleErrors( + onRetry = { residenceViewModel.deleteResidence(residenceId) }, + errorTitle = "Failed to Delete Property" + ) + // Handle delete residence state LaunchedEffect(deleteState) { when (deleteState) { @@ -140,11 +149,6 @@ fun ResidenceDetailScreen( residenceViewModel.resetDeleteResidenceState() onNavigateBack() } - is ApiResult.Error -> { - reportMessage = (deleteState as ApiResult.Error).message - showReportSnackbar = true - residenceViewModel.resetDeleteResidenceState() - } else -> {} } } @@ -403,63 +407,29 @@ fun ResidenceDetailScreen( } } ) { paddingValues -> - when (residenceState) { - is ApiResult.Idle, is ApiResult.Loading -> { - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - contentAlignment = Alignment.Center + ApiResultHandler( + state = residenceState, + onRetry = { + residenceViewModel.getResidence(residenceId) { result -> + residenceState = result + } + }, + modifier = Modifier.padding(paddingValues), + errorTitle = "Failed to Load Property", + loadingContent = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - CircularProgressIndicator() - Text( - text = "Loading residence...", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } + CircularProgressIndicator() + Text( + text = "Loading residence...", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) } } - is ApiResult.Error -> { - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Icon( - Icons.Default.Error, - contentDescription = null, - modifier = Modifier.size(64.dp), - tint = MaterialTheme.colorScheme.error - ) - Text( - text = "Error: ${(residenceState as ApiResult.Error).message}", - color = MaterialTheme.colorScheme.error - ) - Button( - onClick = { - residenceViewModel.getResidence(residenceId) { result -> - residenceState = result - } - }, - shape = RoundedCornerShape(12.dp) - ) { - Text("Retry") - } - } - } - } - is ApiResult.Success -> { - val residence = (residenceState as ApiResult.Success).data + ) { residence -> LazyColumn( modifier = Modifier .fillMaxSize() @@ -720,8 +690,6 @@ fun ResidenceDetailScreen( } } } - - else -> {} } } } diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidencesScreen.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidencesScreen.kt index 1337430..dbafe10 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidencesScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/ResidencesScreen.kt @@ -114,50 +114,12 @@ fun ResidencesScreen( }, floatingActionButtonPosition = FabPosition.End ) { paddingValues -> - when (myResidencesState) { - is ApiResult.Idle, is ApiResult.Loading -> { - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - } - is ApiResult.Error -> { - Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Icon( - Icons.Default.Error, - contentDescription = null, - modifier = Modifier.size(64.dp), - tint = MaterialTheme.colorScheme.error - ) - Text( - text = "Error: ${(myResidencesState as ApiResult.Error).message}", - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodyLarge - ) - Button( - onClick = { viewModel.loadMyResidences() }, - shape = RoundedCornerShape(12.dp) - ) { - Text("Retry") - } - } - } - } - is ApiResult.Success -> { - val response = (myResidencesState as ApiResult.Success).data + ApiResultHandler( + state = myResidencesState, + onRetry = { viewModel.loadMyResidences() }, + modifier = Modifier.padding(paddingValues), + errorTitle = "Failed to Load Properties" + ) { response -> if (response.residences.isEmpty()) { Box( modifier = Modifier @@ -436,8 +398,6 @@ fun ResidencesScreen( } } } - - else -> {} } } }