0

I have a interface called TimeZoneMonitor to monitorize Time Zone or Date changes.

I also have a UseCase that queries the database, looking for information based on the current system date.

The code works as expected in terms of querying based on the given date, but I am unable to update the information when the date or time zone changes.

Code:

TimeZoneMonitor

interface TimeZoneMonitor {
    val currentTimeZone: Flow<TimeZone>
    val currentDate:Flow<LocalDateTime>
}

TimeZoneBroadcastMonitor

@Singleton
class TimeZoneBroadcastMonitor @Inject constructor(
    @ApplicationContext private val context: Context,
    @ApplicationScope appScope: CoroutineScope,
    @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
) : TimeZoneMonitor {

    override val currentTimeZone: SharedFlow<TimeZone> =
        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)

}

GetHomeUseCase

class GetHomeUseCase @Inject constructor(
    private val universalisRepository: UniversalisRepository,
    private val userDataRepository: UserDataRepository,
) {
    
    operator fun invoke(
        date: Int,
    ): Flow<HomeResource> {
        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
            )
        }
    }
}

HomeViewModel

@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<HomeUiState> = combine(
        selectedTopicId,
        selectedDate,
        getHomeUseCase.invoke(
            date = selectedDate.value,
        ),
        HomeUiState::HomeData,
    ).catch<HomeUiState> {
        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

}

HomeScreen (Jetpack Compose)

@ExperimentalLayoutApi
@Composable
fun HomeScreen(
    uiState: HomeUiState,
    modifier: Modifier,
    onTopicClick: (String) -> Unit,
    currentTimeZone: State<TimeZone>,
    currentDate: State<LocalDateTime>,
) {

    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)
        }
    }
}

@ExperimentalLayoutApi
@Composable
fun HomeItems(
    uiState: HomeUiState,
    onTopicClick: (String) -> Unit,
    modifier: Modifier,
    haveDate: Boolean = false,
    currentTimeZone: State<TimeZone>,
    currentDate: State<LocalDateTime>

) {

// ...
        val data = uiState as HomeUiState.HomeData

        Text(
              text = data.topics.data.fecha,
              modifier = Modifier.padding(2.dp),
              textAlign = TextAlign.Center,
             )
}

My problem with this code is that if I change the date in the system, the HomeData information is not updated with the new date.

How can I call the UseCase again with the new date and update the UI?

8
  • You want when the date is changed or the timezone is changed? You are currently only watching for when the timezone is changed Commented Mar 3 at 19:36
  • @tyczj I would like to observe both changes. But I have seen that I can get the date based on the TimeZone. I don't know if this is conclusive and it would be enough to observe the TimeZone, or should I also observe the date change? My idea is to update the information when the date changes, either due to Time Zone reasons or due to the clock (for example, when we go from 23:59:59 to 00:00:00). Commented Mar 3 at 19:40
  • You should also be listening to ACTION_TIME_CHANGED and ACTION_DATE_CHANGED Commented Mar 3 at 19:43
  • @tyczj I'm not sure how to do that. I added this: if (intent.action != Intent.ACTION_TIMEZONE_CHANGED || intent.action != Intent.ACTION_DATE_CHANGED || intent.action != Intent.ACTION_TIME_CHANGED) return but it doesn't work. Commented Mar 3 at 19:59
  • you need to first add the actions to your IntentFilter here context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)) Commented Mar 3 at 20:01

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.