Skip to content

Commit

Permalink
refactor: Extract operation counter to a class (#977)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikclayton authored Oct 3, 2024
1 parent 0d5d118 commit cdbd0ef
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 40 deletions.
51 changes: 51 additions & 0 deletions core/ui/src/main/kotlin/app/pachli/core/ui/OperationCounter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2024 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/

package app.pachli.core.ui

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.getAndUpdate

class OperationCounter {
private val _count: MutableStateFlow<Int> = MutableStateFlow(0)

/** Count of outstanding operations. */
val count = _count.asStateFlow()

/**
* Runs [block] incrementing the operation count before [block]
* starts, decrementing it when [block] ends.
*
* ```kotlin
* private val operationCounter: OperationCounter()
* val operationCount = operationCounter.count
*
* suspend fun foo(): SomeType = operationCounter {
* some_network_operation()
* }
* ```
*
* @return Whatever [block] returned
*/
suspend operator fun <R> invoke(block: suspend () -> R): R {
_count.getAndUpdate { it + 1 }
val result = block.invoke()
_count.getAndUpdate { it - 1 }
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import app.pachli.core.data.repository.StatusDisplayOptionsRepository
import app.pachli.core.data.repository.SuggestionsError.DeleteSuggestionError
import app.pachli.core.data.repository.SuggestionsError.FollowAccountError
import app.pachli.core.data.repository.SuggestionsRepository
import app.pachli.core.ui.OperationCounter
import app.pachli.feature.suggestions.UiAction.GetSuggestions
import app.pachli.feature.suggestions.UiAction.SuggestionAction
import app.pachli.feature.suggestions.UiAction.SuggestionAction.AcceptSuggestion
Expand All @@ -42,10 +43,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
Expand Down Expand Up @@ -128,8 +127,8 @@ internal class SuggestionsViewModel @Inject constructor(
private val _uiResult = Channel<Result<UiSuccess, UiError>>()
override val uiResult = _uiResult.receiveAsFlow()

private val _operationCount = MutableStateFlow(0)
override val operationCount = _operationCount.asStateFlow()
private val operationCounter = OperationCounter()
override val operationCount = operationCounter.count

private val reload = MutableSharedFlow<Unit>(replay = 1)

Expand Down Expand Up @@ -212,44 +211,28 @@ internal class SuggestionsViewModel @Inject constructor(
}

/** Get fresh suggestions from the repository. */
private suspend fun getSuggestions(): Result<Suggestions.Loaded, GetSuggestionsError> = operation {
// Note: disabledSuggestions is *not* cleared here. Suppose the user has
// dismissed a suggestion and the network operation has not completed yet.
// They reload, and get a list of suggestions that includes the suggestion
// they have just dismissed. In that case the suggestion should still be
// disabled.
suggestionsRepository.getSuggestions().mapEither(
{ Suggestions.Loaded(it.map { SuggestionViewData(suggestion = it) }) },
{ GetSuggestionsError(it) },
)
}
private suspend fun getSuggestions(): Result<Suggestions.Loaded, GetSuggestionsError> =
operationCounter {
// Note: disabledSuggestions is *not* cleared here. Suppose the user has
// dismissed a suggestion and the network operation has not completed yet.
// They reload, and get a list of suggestions that includes the suggestion
// they have just dismissed. In that case the suggestion should still be
// disabled.
suggestionsRepository.getSuggestions().mapEither(
{ Suggestions.Loaded(it.map { SuggestionViewData(suggestion = it) }) },
{ GetSuggestionsError(it) },
)
}

/** Delete a suggestion from the repository. */
private suspend fun deleteSuggestion(suggestion: Suggestion): Result<Unit, DeleteSuggestionError> = operation {
suggestionsRepository.deleteSuggestion(suggestion.account.id)
}
private suspend fun deleteSuggestion(suggestion: Suggestion): Result<Unit, DeleteSuggestionError> =
operationCounter {
suggestionsRepository.deleteSuggestion(suggestion.account.id)
}

/** Accept the suggestion and follow the account. */
private suspend fun acceptSuggestion(suggestion: Suggestion): Result<Unit, FollowAccountError> = operation {
suggestionsRepository.followAccount(suggestion.account.id)
}

/**
* Runs [block] incrementing the network operation count before [block]
* starts, decrementing it when [block] ends.
*
* ```kotlin
* suspend fun foo(): SomeType = operation {
* some_network_operation()
* }
* ```
*
* @return Whatever [block] returned
*/
private suspend fun <R> operation(block: suspend () -> R): R {
_operationCount.getAndUpdate { it + 1 }
val result = block.invoke()
_operationCount.getAndUpdate { it - 1 }
return result
}
private suspend fun acceptSuggestion(suggestion: Suggestion): Result<Unit, FollowAccountError> =
operationCounter {
suggestionsRepository.followAccount(suggestion.account.id)
}
}

0 comments on commit cdbd0ef

Please sign in to comment.