Skip to content

Instantly share code, notes, and snippets.

@AndroidKiran
Last active November 11, 2024 19:33
Show Gist options
  • Select an option

  • Save AndroidKiran/8248dabc0367a4b309ebd2f3e10faebd to your computer and use it in GitHub Desktop.

Select an option

Save AndroidKiran/8248dabc0367a4b309ebd2f3e10faebd to your computer and use it in GitHub Desktop.
Kotlin implementation of a RequestFlowManager using Flows and Coroutines. Includes functions for refresh, retry, and reset, along with extension functions to convert request events into Flows and StateFlows.
import com.applogics.scrabble.core.common.utils.RequestFlowManager.RequestState
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
/**
* Manages the flow of data requests and their corresponding results using Kotlin Flows and Coroutines.
*
* This class provides a structured way to initiate, retry, and reset data requests,
* and exposes the results as observable flows using `asFlow()` and `asStateFlow()`.
*
* @param D The type of data used to initiate a request.
* @param T The type of data emitted by the resulting flow.
*/
class RequestFlowManager<D, T> {
/**
* The current request state.
*/
private var requestState: RequestState<D> = RequestState.Initial(Unit)
/**
* A [MutableStateFlow] that emits the current [RequestState].
*/
val requestEvent = MutableStateFlow<RequestState<D>>(requestState)
/**
* Initiates a new request with the given data.
* @param data The data for the request.
*/
fun refresh(data: D) {
requestState = RequestState.Refresh(data)
requestEvent.update { requestState }
}
/**
* Re-triggers the previous request.
*/
fun retry() {
requestEvent.update { requestState }
}
/**
* Resets the request state to [RequestState.Initial].
* Note: Not updating the [requestState] here, so that it can be used while retrying the request
*/
fun resetRequest() {
requestEvent.update { RequestState.Initial(Unit) }
}
/**
* Represents the state of a data request.
*/
sealed interface RequestState<D> {
/**
* Represents a refresh request with the given data.
* @param param The data for the request.
*/
data class Refresh<D>(val param: D) : RequestState<D>
/**
* Represents the initial state before any request.
*/
data class Initial<D>(val nothing: Unit) : RequestState<D>
}
}
@FlowPreview
inline fun <reified D, reified T> RequestFlowManager<D, T>.asFlow(
debounce: Long = 0L,
dispatcher: CoroutineDispatcher = Dispatchers.IO,
crossinline flowProvider: (D) -> Flow<T>
) = requestEvent
.filterIsInstance<RequestState.Refresh<D>>()
.distinctUntilChanged()
.debounce(debounce)
.flowOn(dispatcher)
.flatMapLatest { flowProvider(it.param) }
.onEach { resetRequest() }
@FlowPreview
inline fun <reified D, reified T> RequestFlowManager<D, T>.asStateFlow(
debounce: Long = 0L,
dispatcher: CoroutineDispatcher = Dispatchers.IO,
coroutineScope: CoroutineScope,
initialState: T,
crossinline flowProvider: (D) -> Flow<T>
) = asFlow(debounce, dispatcher, flowProvider)
.stateIn(
coroutineScope,
SharingStarted.Lazily,
initialState
)
/**
* RequestFlowManager Usage
*/
/**
* Represents the UI state of a screen.
*
* This sealed interface defines the possible states that a screen can be in,
* such as loading, success, or error. It is used to communicate the current
* state of the screen to the UI layer, which can then update its appearance
* accordingly.
*
* Possible implementations:
* - [LoadingState]: Represents the state when data is being loaded.
* - [SuccessState]: Represents the state when data has been successfully loaded.
* - [ErrorState]: Represents the state when an error has occurred.
*/
sealed interface ScreenUIState {
data object LoadingState: ScreenUIState
data class SuccessState(val data: Any): ScreenUIState
data class ErrorState(val message: String): ScreenUIState
}
/**
* The UI state of the screen, represented as a [StateFlow].
*
* This state flow emits values of type [ScreenUIState], which can be one of the following:
* - [ScreenUIState.LoadingState]: Indicates that the screen is currently loading data.
* - [ScreenUIState.SuccessState]: Indicates that the data has been successfully loaded.
* - [ScreenUIState.ErrorState]: Indicates that an error occurred while loading the data.
*
* The initial state is [ScreenUIState.LoadingState].
*
* This state flow is created using the `asStateFlow` extension function on a
* [RequestFlowManager] instance. The data fetching logic is defined within the
* lambda expression passed to `asStateFlow`.
*
* The data fetching logic uses `runCatching` to handle potential errors. If the
* data is successfully fetched, a [ScreenUIState.SuccessState] is emitted. If
* an error occurs, a [ScreenUIState.ErrorState] is emitted.
*
* The `onStart` operator is used to emit a [ScreenUIState.LoadingState] before
* the data fetching logic starts, ensuring that the UI displays a loading
* indicator while the data is being fetched.
*/
private val requestFlowManager1 = RequestFlowManager<Unit, ScreenUIState>()
val screenUiState: StateFlow<ScreenUIState> = requestFlowManager1.asStateFlow(
dispatcher = ioDispatchers,
coroutineScope = viewModelScope,
initialState = ScreenUIState.LoadingState
) {
flow {
runCatching {
advertiseRepository.getHowItWorksBanners()
}.onSuccess {
emit(ScreenUIState.SuccessState(it))
}.onFailure {
emit(ScreenUIState.ErrorState("Failed to fetch data"))
}
}.onStart {
emit(ScreenUIState.LoadingState)
}
}
fun fetchScreenData() {
requestFlowManager.refresh(Unit)
}
fun retryFetchScreenData() {
requestFlowManager.retry()
}
/**
* Consume inside the composable function
*/
val screeUIState by viewModel.screenUiState.collectAsStateWithLifecycle()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment