146 lines
3.8 KiB
Markdown
146 lines
3.8 KiB
Markdown
# ✅ FIX: History Screen Close App - Root Cause & Solutions
|
|
|
|
## 🔍 Root Causes Identified
|
|
|
|
### Issue 1: **Race Condition dalam LaunchedEffect**
|
|
**Problem:**
|
|
```kotlin
|
|
LaunchedEffect(Unit) {
|
|
userPreferences.npmFlow.collect { npm ->
|
|
currentNpm = npm
|
|
}
|
|
}
|
|
|
|
LaunchedEffect(Unit) {
|
|
userPreferences.namaFlow.collect { nama ->
|
|
currentNama = nama
|
|
}
|
|
}
|
|
```
|
|
- Dua LaunchedEffect dengan key yang sama (Unit) bersaing satu sama lain
|
|
- Bisa menyebabkan currentNpm tidak ter-load dengan benar saat membuka history
|
|
- Akibat: `currentNpm` masih null saat navigasi ke history → crash
|
|
|
|
### Issue 2: **No Error Handling di Composable History**
|
|
**Problem:**
|
|
```kotlin
|
|
composable("history") {
|
|
val attendanceList by repository.getAttendanceByNpm(
|
|
currentNpm ?: "" // Bisa null jika loading belum selesai
|
|
).collectAsState(initial = emptyList())
|
|
|
|
HistoryScreen(attendanceList = attendanceList, ...)
|
|
}
|
|
```
|
|
- `currentNpm` bisa null jika Flow belum selesai di-load
|
|
- `collectAsState` bisa throw exception
|
|
- Tidak ada error handling → langsung crash
|
|
|
|
---
|
|
|
|
## ✅ Solutions Applied
|
|
|
|
### Solution 1: **Added Error Handling di LaunchedEffect**
|
|
```kotlin
|
|
LaunchedEffect(Unit) {
|
|
try {
|
|
userPreferences.npmFlow.collect { npm ->
|
|
currentNpm = npm
|
|
isUserLoaded = npm != null // Track loading state
|
|
}
|
|
} catch (e: Exception) {
|
|
currentNpm = null
|
|
isUserLoaded = false
|
|
}
|
|
}
|
|
```
|
|
|
|
### Solution 2: **Improved History Composable dengan Try-Catch & Null Safety**
|
|
```kotlin
|
|
composable("history") {
|
|
try {
|
|
val npmForHistory = currentNpm ?: ""
|
|
|
|
if (npmForHistory.isEmpty()) {
|
|
// Show user-friendly warning
|
|
Box(modifier = Modifier.fillMaxSize(),
|
|
contentAlignment = Alignment.Center) {
|
|
Text("NPM tidak ditemukan. Silakan login ulang.")
|
|
}
|
|
} else {
|
|
val attendanceList by repository.getAttendanceByNpm(npmForHistory)
|
|
.collectAsState(initial = emptyList())
|
|
|
|
HistoryScreen(
|
|
attendanceList = attendanceList,
|
|
onBackClick = { navController.navigateUp() }
|
|
)
|
|
}
|
|
} catch (e: Exception) {
|
|
// Fallback error UI
|
|
Box(modifier = Modifier.fillMaxSize(),
|
|
contentAlignment = Alignment.Center) {
|
|
Text("Error: ${e.message ?: "Tidak dapat membuka riwayat"}")
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Solution 3: **Added Required Imports**
|
|
```kotlin
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.ui.Alignment
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Changes Summary
|
|
|
|
| Komponen | Sebelum | Sesudah |
|
|
|----------|---------|--------|
|
|
| **LaunchedEffect** | ❌ No error handling | ✅ Try-catch + state tracking |
|
|
| **History Composable** | ❌ No null check | ✅ Defensive null check |
|
|
| **Error Handling** | ❌ None | ✅ Try-catch with fallback UI |
|
|
| **Loading State** | ❌ None | ✅ `isUserLoaded` flag |
|
|
| **User Feedback** | ❌ Crash | ✅ Clear error messages |
|
|
|
|
---
|
|
|
|
## 🧪 Test Scenarios
|
|
|
|
### ✅ Test 1: Normal Flow
|
|
```
|
|
1. Login dengan npm valid
|
|
2. Tunggu loading selesai
|
|
3. Buka History
|
|
✅ Expected: Riwayat ditampilkan dengan aman
|
|
```
|
|
|
|
### ✅ Test 2: Rapid Navigation
|
|
```
|
|
1. Login
|
|
2. Langsung ke history (sebelum loading selesai)
|
|
✅ Expected: Tampil "NPM tidak ditemukan..." atau loading
|
|
```
|
|
|
|
### ✅ Test 3: Data Error
|
|
```
|
|
1. Network issue saat load preferences
|
|
2. Buka History
|
|
✅ Expected: Tampil user-friendly error message
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 Files Modified
|
|
- `MainActivity.kt` - Added error handling & improved history composable
|
|
|
|
## 🚀 Expected Results
|
|
✅ App tidak lagi crash saat membuka History
|
|
✅ User mendapat informasi jelas jika ada error
|
|
✅ Graceful handling untuk berbagai edge cases
|
|
✅ Proper state management untuk user preferences
|
|
|
|
|