Stop calendar queries emitting an empty list before they have run

`queryCalendarEvents` forwarded the `MutableStateFlow` it accumulates
the provider results in, so the initial empty value was the first thing
collectors saw - before any provider had reported. Callers in the
launcher take that first result (`CalendarPartProvider` uses `.first()`)
and cannot tell it apart from "no events", so the agenda stayed empty
and its part never showed up.

`CalendarRepository.findMany`'s 500 ms debounce used to hide this: the
placeholder was superseded by the real list within the debounce window,
which is exactly why the agenda only ever appeared half a second late.

Keep the accumulator null until the first provider reports, and let
`findMany` - a one-shot query for the clock and calendar widgets - wait
for all providers (`awaitAllProviders`) so it never hands out a partial
list. The search path keeps streaming results as providers report, now
without an empty flash.
This commit is contained in:
2026-09-11 21:17:57 +02:00
parent c8109d9197
commit 4836618609

View File

@@ -22,10 +22,12 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlin.time.Duration.Companion.days
@@ -126,6 +128,7 @@ internal class CalendarRepositoryImpl(
excludeAllDayEvents = excludeAllDayEvents,
excludeCalendars = excludeCalendars,
providers = providers,
awaitAllProviders = true,
allowNetwork = false,
)
emitAll(if (debounceMillis > 0L) events.debounce(debounceMillis) else events)
@@ -139,12 +142,17 @@ internal class CalendarRepositoryImpl(
excludeAllDayEvents: Boolean = false,
excludeCalendars: List<String> = emptyList(),
allowNetwork: Boolean = false,
awaitAllProviders: Boolean = false,
providers: List<CalendarProvider>,
): Flow<ImmutableList<CalendarEvent>> = flow {
supervisorScope {
val result = MutableStateFlow(persistentListOf<CalendarEvent>())
// Null until the first provider has reported. Emitting the (empty) accumulator right
// away would hand collectors an empty list before any query had run, which callers
// that take the first result - like the agenda in the clock widget - cannot tell
// apart from "no events".
val result = MutableStateFlow<ImmutableList<CalendarEvent>?>(null)
for (provider in providers) {
val jobs = providers.map { provider ->
launch {
val r = provider.search(
query,
@@ -158,11 +166,19 @@ internal class CalendarRepositoryImpl(
allowNetwork = allowNetwork,
)
result.update {
(it + r).toPersistentList()
((it ?: persistentListOf()) + r).toPersistentList()
}
}
}
emitAll(result)
if (awaitAllProviders) {
// One-shot callers must not receive a partial list, so wait until every
// provider has reported; if there is none, report an empty result.
jobs.joinAll()
emit(result.value ?: persistentListOf())
} else {
emitAll(result.filterNotNull())
}
}
}