@@ -16,13 +16,22 @@ import com.leanite.dynaquiz.core.domain.usecase.GetRandomQuestionUseCase
1616import com.leanite.dynaquiz.core.domain.usecase.SaveQuizSessionUseCase
1717import com.leanite.dynaquiz.core.domain.usecase.SubmitAnswerUseCase
1818import kotlinx.collections.immutable.toImmutableList
19- import kotlinx.coroutines.Deferred
19+ import kotlinx.coroutines.ExperimentalCoroutinesApi
2020import kotlinx.coroutines.async
2121import kotlinx.coroutines.channels.Channel
22+ import kotlinx.coroutines.flow.Flow
2223import kotlinx.coroutines.flow.MutableStateFlow
24+ import kotlinx.coroutines.flow.SharedFlow
25+ import kotlinx.coroutines.flow.SharingStarted
2326import kotlinx.coroutines.flow.StateFlow
2427import kotlinx.coroutines.flow.asStateFlow
28+ import kotlinx.coroutines.flow.distinctUntilChanged
29+ import kotlinx.coroutines.flow.filter
30+ import kotlinx.coroutines.flow.first
31+ import kotlinx.coroutines.flow.map
32+ import kotlinx.coroutines.flow.mapLatest
2533import kotlinx.coroutines.flow.receiveAsFlow
34+ import kotlinx.coroutines.flow.shareIn
2635import kotlinx.coroutines.flow.update
2736import kotlinx.coroutines.launch
2837
@@ -39,7 +48,22 @@ class QuizViewModel(
3948 private val _events = Channel <QuizEvent >(Channel .BUFFERED )
4049 val events = _events .receiveAsFlow()
4150
42- private var prefetchedNextQuestion: Deferred <Question ?>? = null
51+ private val nextQuestionTrigger: Flow <Int > =
52+ _uiState
53+ .filter { it.phase is QuizPhase .Playing }
54+ .map { it.currentQuestionIndex + 1 }
55+ .distinctUntilChanged()
56+ .filter { it < QuizRules .TOTAL_QUESTIONS }
57+
58+ @OptIn(ExperimentalCoroutinesApi ::class )
59+ private val prefetchedQuestion: SharedFlow <Question ?> =
60+ nextQuestionTrigger
61+ .mapLatest { fetchQuestion() }
62+ .shareIn(
63+ scope = viewModelScope,
64+ started = SharingStarted .Eagerly ,
65+ replay = 1 ,
66+ )
4367
4468 init {
4569 // Reflete o timer no UiState sem o VM gerenciar a coroutine
@@ -88,7 +112,6 @@ class QuizViewModel(
88112 private fun startPlaying (question : Question ) {
89113 _uiState .update { it.copy(phase = QuizPhase .Playing (question = question)) }
90114 startQuestionTimer()
91- schedulePrefetchOfNextQuestion()
92115 }
93116
94117 private fun startQuestionTimer () {
@@ -110,19 +133,6 @@ class QuizViewModel(
110133 viewModelScope.launch { advanceToNextQuestion(log) }
111134 }
112135
113- // Inicia em background o fetch da próxima pergunta enquanto o user resolve a atual.
114- private fun schedulePrefetchOfNextQuestion () {
115- prefetchedNextQuestion?.cancel()
116- val nextIndex = _uiState .value.currentQuestionIndex + 1
117-
118- prefetchedNextQuestion =
119- if (nextIndex < QuizRules .TOTAL_QUESTIONS ) {
120- viewModelScope.async { fetchQuestion() }
121- } else {
122- null
123- }
124- }
125-
126136 private fun onAnswerSelected (answer : String ) {
127137 val state = _uiState .value
128138 val playing = state.phase as ? QuizPhase .Playing ? : return
@@ -222,11 +232,7 @@ class QuizViewModel(
222232 newLog : List <AnswerLog >,
223233 nextIndex : Int ,
224234 ) {
225- // Consome o prefetched provavelmente já pronto
226- // Fallback é um fetch reativo caso o prefetched falhe
227- val nextQuestion = prefetchedNextQuestion?.await() ? : fetchQuestion()
228- prefetchedNextQuestion = null
229-
235+ val nextQuestion = prefetchedQuestion.first() ? : fetchQuestion()
230236 if (nextQuestion == null ) {
231237 abortWithLoadError()
232238 return
@@ -240,7 +246,6 @@ class QuizViewModel(
240246 )
241247 }
242248 startQuestionTimer()
243- schedulePrefetchOfNextQuestion()
244249 }
245250
246251 private suspend fun fetchQuestion (): Question ? =
@@ -258,7 +263,6 @@ class QuizViewModel(
258263
259264 override fun onCleared () {
260265 timer.cancel()
261- prefetchedNextQuestion?.cancel()
262266 super .onCleared()
263267 }
264268}
0 commit comments