Skip to content

Commit 662ceb2

Browse files
committed
refactor(quiz): replace imperative prefetch with reactive Flow
1 parent d8fe2a3 commit 662ceb2

1 file changed

Lines changed: 27 additions & 23 deletions

File tree

  • dynaquiz/composeApp/src/commonMain/kotlin/com/leanite/dynaquiz/feature/quiz

dynaquiz/composeApp/src/commonMain/kotlin/com/leanite/dynaquiz/feature/quiz/QuizViewModel.kt

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@ import com.leanite.dynaquiz.core.domain.usecase.GetRandomQuestionUseCase
1616
import com.leanite.dynaquiz.core.domain.usecase.SaveQuizSessionUseCase
1717
import com.leanite.dynaquiz.core.domain.usecase.SubmitAnswerUseCase
1818
import kotlinx.collections.immutable.toImmutableList
19-
import kotlinx.coroutines.Deferred
19+
import kotlinx.coroutines.ExperimentalCoroutinesApi
2020
import kotlinx.coroutines.async
2121
import kotlinx.coroutines.channels.Channel
22+
import kotlinx.coroutines.flow.Flow
2223
import kotlinx.coroutines.flow.MutableStateFlow
24+
import kotlinx.coroutines.flow.SharedFlow
25+
import kotlinx.coroutines.flow.SharingStarted
2326
import kotlinx.coroutines.flow.StateFlow
2427
import 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
2533
import kotlinx.coroutines.flow.receiveAsFlow
34+
import kotlinx.coroutines.flow.shareIn
2635
import kotlinx.coroutines.flow.update
2736
import 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

Comments
 (0)