Wie kann ich auf Datums- oder Zeitzone -Änderungen beobachten, um die Benutzeroberfläche mit MVVM, Uscase und Hilt zu ak
Posted: 03 Mar 2025, 20:31
Ich habe eine Schnittstelle namens TimeZonemonitor zur Überwachung der Zeitzone oder des Datumsänderungen. Änderungen .
Code:
(Jetpack Compose)
Mein Problem mit diesem Code ist, dass, wenn ich das Datum im System ändere, die Homedata Informationen nicht mit dem neuen Datum aktualisiert werden.>
Code:
Code: Select all
TimeZoneMonitor
Code: Select all
interface TimeZoneMonitor {
val currentTimeZone: Flow
val currentDate:Flow
}
< /code>
TimeZoneBroadcastMonitor
Code: Select all
@Singleton
class TimeZoneBroadcastMonitor @Inject constructor(
@ApplicationContext private val context: Context,
@ApplicationScope appScope: CoroutineScope,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
) : TimeZoneMonitor {
override val currentTimeZone: SharedFlow =
callbackFlow {
// Send the default time zone first.
trySend(TimeZone.currentSystemDefault())
// Registers BroadcastReceiver for the TimeZone changes
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_TIMEZONE_CHANGED) return
val zoneIdFromIntent = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
null
} else {
// Starting Android R we also get the new TimeZone.
intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { timeZoneId ->
// We need to convert it from java.util.Timezone to java.time.ZoneId
val zoneId = ZoneId.of(timeZoneId, ZoneId.SHORT_IDS)
// Convert to kotlinx.datetime.TimeZone
zoneId.toKotlinTimeZone()
}
}
// If there isn't a zoneId in the intent, fallback to the systemDefault, which should also reflect the change
trySend(zoneIdFromIntent ?: TimeZone.currentSystemDefault())
}
}
trace("TimeZoneBroadcastReceiver.register") {
context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED))
}
// Send here again, because registering the Broadcast Receiver can take up to several milliseconds.
// This way, we can reduce the likelihood that a TZ change wouldn't be caught with the Broadcast Receiver.
trySend(TimeZone.currentSystemDefault())
awaitClose {
context.unregisterReceiver(receiver)
}
}
// We use to prevent multiple emissions of the same type, because we use trySend multiple times.
.distinctUntilChanged()
.conflate()
.flowOn(ioDispatcher)
// Sharing the callback to prevent multiple BroadcastReceivers being registered
.shareIn(appScope, SharingStarted.WhileSubscribed(5_000), 1)
}
< /code>
GetHomeUseCase
Code: Select all
class GetHomeUseCase @Inject constructor(
private val universalisRepository: UniversalisRepository,
private val userDataRepository: UserDataRepository,
) {
operator fun invoke(
date: Int,
): Flow {
return combine(
userDataRepository.userData,
universalisRepository.countUniversalis(UniversalisResourceQuery(date)),
) { userData, count ->
if (count == 0 && date.isDateValid()) {
universalisRepository.insertFromRemote(UniversalisResourceQuery(date))
}
val newData = universalisRepository.getUniversalisForTest(date).first()
HomeResource(
date = date,
data = newData,
count = count,
dynamic = userData
)
}
}
}
< /code>
HomeViewModel
Code: Select all
@HiltViewModel
class HomeViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val analyticsHelper: AnalyticsHelper,
timeZoneMonitor:TimeZoneMonitor,
val userDataRepository: UserDataRepository,
getHomeUseCase: GetHomeUseCase,
) : ViewModel() {
private val selectedTopicIdKey = "selectedTopicIdKey"
private val route: UniversalisRoute = savedStateHandle.toRoute()
private val selectedTopicId = savedStateHandle.getStateFlow(
key = selectedTopicIdKey,
initialValue = route.initialTopicId.toString(),
)
val currentTimeZone = timeZoneMonitor.currentTimeZone
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
TimeZone.currentSystemDefault(),
)
val zi = ZoneId.of(currentTimeZone.value.id)
val time = ZonedDateTime.now(zi)
private val newDate=time.format(DateTimeFormatter.ofPattern("yyyyMMdd")).toInt()
private var selectedDate = savedStateHandle.getStateFlow(
key = "date",
initialValue = newDate,
)
val uiState: StateFlow = combine(
selectedTopicId,
selectedDate,
getHomeUseCase.invoke(
date = selectedDate.value,
),
HomeUiState::HomeData,
).catch {
val error = HomeUiState.HomeError(
date = selectedDate.value,
message = it.message!!
)
analyticsHelper.logHomeErrorEvent(error)
emit(error)
}//.distinctUntilChanged()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = HomeUiState.Loading,
)
}
sealed interface HomeUiState {
data object Loading : HomeUiState
data class HomeData(
val selectedTopicId: String?,
val selectedDate:Int,
val topics: HomeResource,
) : HomeUiState
data class HomeError(
val date: Int,
val message: String
) : HomeUiState {
override fun toString(): String {
return "Date: $date Msg: $message"
}
}
data object Empty : HomeUiState
}
< /code>
HomeScreen
Code: Select all
@ExperimentalLayoutApi
@Composable
fun HomeScreen(
uiState: HomeUiState,
modifier: Modifier,
onTopicClick: (String) -> Unit,
currentTimeZone: State,
currentDate: State,
) {
when (uiState) {
HomeUiState.Empty -> EmptyState(modifier = modifier)
is HomeUiState.HomeData -> {
HomeItems(uiState = uiState, onTopicClick = onTopicClick, currentTimeZone=currentTimeZone,currentDate=currentDate,modifier = modifier, haveDate=true)
}
HomeUiState.Loading -> LoadingState(modifier = modifier)
is HomeUiState.HomeError -> {
HomeItems(uiState = uiState, onTopicClick = onTopicClick, currentTimeZone=currentTimeZone,currentDate=currentDate,modifier = modifier, haveDate=false)
}
}
}
< /code>
@ExperimentalLayoutApi
@Composable
fun HomeItems(
uiState: HomeUiState,
onTopicClick: (String) -> Unit,
modifier: Modifier,
haveDate: Boolean = false,
currentTimeZone: State,
currentDate: State
) {
// ...
val data = uiState as HomeUiState.HomeData
Text(
text = data.topics.data.fecha,
modifier = Modifier.padding(2.dp),
textAlign = TextAlign.Center,
)
}