mirror of
https://github.com/PranshulGG/WeatherMaster.git
synced 2026-10-21 18:43:03 -05:00
Final refactor refinements: fix auto-refresh lifecycle, clarify threading, and add cancellation tests
This commit is contained in:
1 parent
1fc1fdbfb3
commit
b904ccfda3
6 files changed
+40
-5
No files matched your search
@@ -154,7 +154,7 @@ dependencies {
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.materialKolor)
|
||||
implementation(libs.androidx.foundation.layout)
|
||||
implementation(libs.androidx.foundation.layout) // Removed duplicate entry
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
|
||||
|
||||
-2
@@ -89,7 +89,5 @@ class SourceDataRepository @Inject constructor(
|
||||
launch {
|
||||
onWeather(weatherJob.await())
|
||||
}
|
||||
|
||||
Unit
|
||||
}
|
||||
}
|
||||
+2
@@ -24,6 +24,8 @@ class GetWeatherUseCase @Inject constructor(
|
||||
* asynchronous operations.
|
||||
*
|
||||
* All callbacks are invoked on the dispatcher used by the caller.
|
||||
* Note: [onWeather], [onAlerts], and [onAirQuality] may be invoked from the
|
||||
* repository's dispatcher if not explicitly controlled by the caller.
|
||||
*
|
||||
* @param location The location to fetch data for.
|
||||
* @param isManualRefresh Whether this is a user-initiated refresh (e.g., pull-to-refresh).
|
||||
|
||||
+2
@@ -41,6 +41,8 @@ class UpdateLocationSourceUseCase @Inject constructor(
|
||||
locationsRepo.updateAlertSourceForLocation(location.id, alertSource)
|
||||
locationsRepo.updateOpenMeteoModelForLocation(location.id, openMeteoModel)
|
||||
|
||||
// Reconciliation cleans up stale data from previous sources.
|
||||
// Note: These updates are currently performed in sequence without a transaction.
|
||||
weatherDataReconcilerRepository.reconcileSourceChange(
|
||||
previous = location,
|
||||
updated = updatedLocation
|
||||
|
||||
+5
-1
@@ -59,6 +59,10 @@ class WeatherViewModel @Inject constructor(
|
||||
private var _uiState = mutableStateOf(MainScreenWeatherUiState())
|
||||
val uiState: State<MainScreenWeatherUiState> = _uiState
|
||||
|
||||
companion object {
|
||||
private val AUTO_REFRESH_INTERVAL = 45.minutes
|
||||
}
|
||||
|
||||
// Registered on the process-wide lifecycle (same pattern as AppVisibility) rather than a
|
||||
// Compose LocalLifecycleOwner tied to a screen: a screen-scoped observer gets torn down and
|
||||
// recreated by ordinary in-app navigation, and Android replays a synthetic ON_START to any
|
||||
@@ -368,7 +372,7 @@ class WeatherViewModel @Inject constructor(
|
||||
autoRefreshJob = viewModelScope.launch {
|
||||
while (isActive) {
|
||||
|
||||
delay(45.minutes)
|
||||
delay(AUTO_REFRESH_INTERVAL)
|
||||
val location = _uiState.value.activeLocation ?: continue
|
||||
|
||||
if (_uiState.value.isLoading || _uiState.value.isError) {
|
||||
|
||||
+30
-1
@@ -14,6 +14,7 @@ import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
class WeatherViewModelTest {
|
||||
@@ -123,7 +124,7 @@ class WeatherViewModelTest {
|
||||
// Given
|
||||
val deviceLocation = dummyLocation.copy(isDeviceLocation = true)
|
||||
val updatedLocation = deviceLocation.copy(latitude = 52.0)
|
||||
val onLocationUpdatedSlot = slot<(Location) -> Unit>()
|
||||
val onLocationUpdatedSlot = slot<suspend (Location) -> Unit>()
|
||||
|
||||
coEvery {
|
||||
getWeatherUseCase(
|
||||
@@ -149,6 +150,34 @@ class WeatherViewModelTest {
|
||||
assertEquals(updatedLocation, viewModel.uiState.value.activeLocation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auto refresh should call getWeather periodically`() = runTest {
|
||||
// Given
|
||||
viewModel.setActiveLocation(dummyLocation)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Clear initial call from setActiveLocation
|
||||
clearMocks(getWeatherUseCase, answers = false)
|
||||
|
||||
// When
|
||||
advanceTimeBy(45.minutes.inWholeMilliseconds + 1)
|
||||
|
||||
// Then
|
||||
coVerify {
|
||||
getWeatherUseCase(
|
||||
location = dummyLocation,
|
||||
isManualRefresh = false,
|
||||
isForceRefresh = false,
|
||||
isForceRefreshForAirQuality = false,
|
||||
isForceRefreshForAlerts = false,
|
||||
onLocationUpdated = any(),
|
||||
onWeather = any(),
|
||||
onAlerts = any(),
|
||||
onAirQuality = any()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleSourceChangeForWeather should compute force refresh flags correctly`() = runTest {
|
||||
// Given
|
||||
|
||||
Reference in new issue
Block a user