refactor(stats): refactor flow collection to improve performance

This commit is contained in:
Nishant Mishra
2025-10-15 23:25:29 +05:30
parent 5717a0f274
commit a26b0eac74
2 changed files with 128 additions and 93 deletions

View File

@@ -35,6 +35,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -56,10 +57,8 @@ import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import com.patrykandpatrick.vico.core.common.data.ExtraStore
import kotlinx.coroutines.runBlocking
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.data.Stat
import org.nsh07.pomodoro.data.StatFocusTime
import org.nsh07.pomodoro.ui.statsScreen.viewModel.StatsViewModel
import org.nsh07.pomodoro.ui.theme.AppFonts.openRundeClock
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
@@ -72,20 +71,41 @@ fun StatsScreenRoot(
viewModel: StatsViewModel = viewModel(factory = StatsViewModel.Factory)
) {
val todayStat by viewModel.todayStat.collectAsStateWithLifecycle(null)
val lastWeekAverageFocusTimes by viewModel
.lastWeekAverageFocusTimes.collectAsStateWithLifecycle(null)
val lastMonthAverageFocusTimes by viewModel
.lastMonthAverageFocusTimes.collectAsStateWithLifecycle(null)
val lastWeekSummaryChartData by viewModel.lastWeekSummaryChartData.collectAsStateWithLifecycle()
val lastWeekAnalysisValues by viewModel.lastWeekAverageFocusTimes.collectAsStateWithLifecycle()
val lastMonthSummaryChartData by viewModel.lastMonthSummaryChartData.collectAsStateWithLifecycle()
val lastMonthAnalysisValues by viewModel.lastMonthAverageFocusTimes.collectAsStateWithLifecycle()
val lastWeekSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() }
val lastMonthSummaryAnalysisModelProducer = remember { CartesianChartModelProducer() }
LaunchedEffect(lastWeekAnalysisValues) {
lastWeekSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastWeekAnalysisValues)
}
}
}
LaunchedEffect(lastMonthAnalysisValues) {
lastMonthSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(lastMonthAnalysisValues)
}
}
}
StatsScreen(
contentPadding = contentPadding,
lastWeekSummaryChartData = remember { viewModel.lastWeekSummaryChartData },
lastWeekSummaryAnalysisModelProducer = remember { viewModel.lastWeekSummaryAnalysisModelProducer },
lastMonthSummaryChartData = remember { viewModel.lastMonthSummaryChartData },
lastMonthSummaryAnalysisModelProducer = remember { viewModel.lastMonthSummaryAnalysisModelProducer },
lastWeekSummaryChartData = lastWeekSummaryChartData,
lastWeekSummaryAnalysisModelProducer = lastWeekSummaryAnalysisModelProducer,
lastMonthSummaryChartData = lastMonthSummaryChartData,
lastMonthSummaryAnalysisModelProducer = lastMonthSummaryAnalysisModelProducer,
todayStat = todayStat,
lastWeekAverageFocusTimes = lastWeekAverageFocusTimes,
lastMonthAverageFocusTimes = lastMonthAverageFocusTimes,
lastWeekAverageFocusTimes = lastWeekAnalysisValues,
lastMonthAverageFocusTimes = lastMonthAnalysisValues,
modifier = modifier
)
}
@@ -99,8 +119,8 @@ fun StatsScreen(
lastMonthSummaryChartData: Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>,
lastMonthSummaryAnalysisModelProducer: CartesianChartModelProducer,
todayStat: Stat?,
lastWeekAverageFocusTimes: StatFocusTime?,
lastMonthAverageFocusTimes: StatFocusTime?,
lastWeekAverageFocusTimes: List<Int>,
lastMonthAverageFocusTimes: List<Int>,
modifier: Modifier = Modifier
) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
@@ -218,7 +238,11 @@ fun StatsScreen(
.padding(horizontal = 16.dp)
) {
Text(
millisecondsToHoursMinutes(lastWeekAverageFocusTimes?.total() ?: 0),
millisecondsToHoursMinutes(
remember(lastWeekAverageFocusTimes) {
lastWeekAverageFocusTimes.sum().toLong()
}
),
style = typography.displaySmall,
fontFamily = openRundeClock
)
@@ -290,7 +314,11 @@ fun StatsScreen(
.padding(horizontal = 16.dp)
) {
Text(
millisecondsToHoursMinutes(lastMonthAverageFocusTimes?.total() ?: 0),
millisecondsToHoursMinutes(
remember(lastMonthAverageFocusTimes) {
lastMonthAverageFocusTimes.sum().toLong()
}
),
style = typography.displaySmall,
fontFamily = openRundeClock
)
@@ -356,23 +384,25 @@ fun StatsScreen(
@Composable
fun StatsScreenPreview() {
val modelProducer = remember { CartesianChartModelProducer() }
val keys = remember { ExtraStore.Key<List<String>>() }
runBlocking {
LaunchedEffect(Unit) {
modelProducer.runTransaction {
columnSeries {
series(5, 6, 5, 2, 11, 8, 5, 2, 15, 11, 8, 13, 12, 10, 2, 7)
}
extras { it[keys] = listOf("M", "T", "W", "T", "F", "S", "S") }
}
}
StatsScreen(
PaddingValues(),
Pair(modelProducer, ExtraStore.Key()),
Pair(modelProducer, keys),
modelProducer,
Pair(modelProducer, ExtraStore.Key()),
Pair(modelProducer, keys),
modelProducer,
null,
null,
null
listOf(0, 0, 0, 0),
listOf(0, 0, 0, 0)
)
}
}

View File

@@ -16,9 +16,11 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import com.patrykandpatrick.vico.core.common.data.ExtraStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.nsh07.pomodoro.TomatoApplication
import org.nsh07.pomodoro.data.StatRepository
import java.time.format.TextStyle
@@ -29,83 +31,86 @@ class StatsViewModel(
) : ViewModel() {
val todayStat = statRepository.getTodayStat().distinctUntilChanged()
private val lastWeekStatsSummary = statRepository.getLastNDaysStatsSummary(7)
val lastWeekAverageFocusTimes =
statRepository.getLastNDaysAverageFocusTimes(7).distinctUntilChanged()
private val lastMonthStatsSummary = statRepository.getLastNDaysStatsSummary(30)
val lastMonthAverageFocusTimes =
statRepository.getLastNDaysAverageFocusTimes(30).distinctUntilChanged()
val lastWeekSummaryChartData =
private val lastWeekSummary =
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
val lastWeekSummaryAnalysisModelProducer = CartesianChartModelProducer()
val lastMonthSummaryChartData =
private val lastMonthSummary =
Pair(CartesianChartModelProducer(), ExtraStore.Key<List<String>>())
val lastMonthSummaryAnalysisModelProducer = CartesianChartModelProducer()
init {
viewModelScope.launch(Dispatchers.IO) {
lastWeekStatsSummary
.collect { list ->
// reversing is required because we need ascending order while the DB returns descending order
val reversed = list.reversed()
val keys = reversed.map {
it.date.dayOfWeek.getDisplayName(
TextStyle.NARROW,
Locale.getDefault()
)
}
val values = reversed.map { it.focusTime }
lastWeekSummaryChartData.first.runTransaction {
columnSeries { series(values) }
extras { it[lastWeekSummaryChartData.second] = keys }
}
val lastWeekSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(7)
.map { list ->
// reversing is required because we need ascending order while the DB returns descending order
val reversed = list.reversed()
val keys = reversed.map {
it.date.dayOfWeek.getDisplayName(
TextStyle.NARROW,
Locale.getDefault()
)
}
}
viewModelScope.launch(Dispatchers.IO) {
lastWeekAverageFocusTimes
.collect {
lastWeekSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(
it?.focusTimeQ1 ?: 0,
it?.focusTimeQ2 ?: 0,
it?.focusTimeQ3 ?: 0,
it?.focusTimeQ4 ?: 0
)
}
}
val values = reversed.map { it.focusTime }
lastWeekSummary.first.runTransaction {
columnSeries { series(values) }
extras { it[lastWeekSummary.second] = keys }
}
}
viewModelScope.launch(Dispatchers.IO) {
lastMonthStatsSummary
.collect { list ->
val reversed = list.reversed()
val keys = reversed.map { it.date.dayOfMonth.toString() }
val values = reversed.map { it.focusTime }
lastMonthSummaryChartData.first.runTransaction {
columnSeries { series(values) }
extras { it[lastMonthSummaryChartData.second] = keys }
}
lastWeekSummary
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = lastWeekSummary
)
val lastWeekAverageFocusTimes: StateFlow<List<Int>> =
statRepository.getLastNDaysAverageFocusTimes(7)
.map {
listOf(
it?.focusTimeQ1?.toInt() ?: 0,
it?.focusTimeQ2?.toInt() ?: 0,
it?.focusTimeQ3?.toInt() ?: 0,
it?.focusTimeQ4?.toInt() ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = listOf(0, 0, 0, 0)
)
val lastMonthSummaryChartData: StateFlow<Pair<CartesianChartModelProducer, ExtraStore.Key<List<String>>>> =
statRepository.getLastNDaysStatsSummary(30)
.map { list ->
val reversed = list.reversed()
val keys = reversed.map { it.date.dayOfMonth.toString() }
val values = reversed.map { it.focusTime }
lastMonthSummary.first.runTransaction {
columnSeries { series(values) }
extras { it[lastMonthSummary.second] = keys }
}
}
viewModelScope.launch(Dispatchers.IO) {
lastMonthAverageFocusTimes
.collect {
lastMonthSummaryAnalysisModelProducer.runTransaction {
columnSeries {
series(
it?.focusTimeQ1 ?: 0,
it?.focusTimeQ2 ?: 0,
it?.focusTimeQ3 ?: 0,
it?.focusTimeQ4 ?: 0
)
}
}
}
}
}
lastMonthSummary
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = lastMonthSummary
)
val lastMonthAverageFocusTimes: StateFlow<List<Int>> =
statRepository.getLastNDaysAverageFocusTimes(30)
.map {
listOf(
it?.focusTimeQ1?.toInt() ?: 0,
it?.focusTimeQ2?.toInt() ?: 0,
it?.focusTimeQ3?.toInt() ?: 0,
it?.focusTimeQ4?.toInt() ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = listOf(0, 0, 0, 0)
)
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {