Unidirectional Data Flow (UDF) is a kind of architecture that ensures single source of truth for data. In this architecture the state flows down and the events flow up.
State: UI state is the property that describes the app’s view. It holds immutable data.
Event: UI generates an event and passes it upward, such as a button click
UDF could be maintained by the following steps:
The ViewModel holds and exposes the state to be consumed by the UI
The UI notifies the ViewModel of user events
The ViewModel handles the user events and execute action for data layer and updates the state
The updated state gets back to the UI to render
Advantages
Single source of truth: It helps to make less error. Here only data layer is allowed to change data. And we have more control over app data.
UI consistency: All state updates are immediately reflected in the UI by the use of observable state holders, like StateFlow or LiveData
Easier to debug, as we know what is coming from where
Separation of data handling It separates the place where state changes originate, the place where they are transformed, and the place where they are finally consumed
Maintainability: It is more easier to find the place where needs to change for any incident/new changes
Testability: Decoupling state from the UI that displays it makes it easier to test both in isolation
Example
Like/Unlike product review
Data Class
data class Review(
val id: Int,
val isLiked: Boolean
)
data class ReviewUiState(
val id: Int,
val isLiked: Boolean
)
Repository
class ReviewRepository(private val networkService: ReviewNetworkService) {
private val reviewsFlow: MutableStateFlow<List<Review>> = MutableStateFlow(emptyList())
suspend fun getReviews(): Flow<List<Review>> {
reviewsFlow.value = networkService.getReviews()
return reviewsFlow
}
suspend fun toggleLike(reviewId: Int, isLiked: Boolean) {
val response = networkService.postLike(reviewId, isLiked)
if (response.isSuccess) {
reviewsFlow.update {
it.toApplyLike(
reviewId = reviewId,
isLiked = isLiked
)
}
}
}
}
UseCase
class GetReviewsUseCase(
private val repository: ReviewRepository
) {
suspend operator fun invoke(): Flow<List<Review>> {
return repository.getReviews()
}
}
class ToggleReviewLikeUseCase(
private val repository: ReviewRepository
) {
suspend operator fun invoke(query: Query) {
return repository.toggleReview(
reviewId = query.reviewId,
isLiked = query.isLiked
)
}
data class Query(
val reviewId: Int,
val isLiked: Boolean
)
}
ViewModel
class ReviewViewModel(
private val getReviewsUseCase: GetReviewsUseCase,
private val toggleReviewLikeUseCase: ToggleReviewLikeUseCase
) : ViewModel() {
private val _reviewUiStates = MutableStateFlow<List<ReviewUiState>>(emptyList())
val reviewUiStates: Flow<List<ReviewUiState>> = _reviewUiStates
init {
requestReviews()
}
private fun requestReviews() {
viewModelScope.launch {
val reviewUiStateFlow = getReviewsUseCase()
.map { reviews ->
reviews.map { review ->
ReviewUiState(id = review.id, isLiked = review.isLiked)
}
}
_reviewUiStates.emitAll(reviewUiStateFlow)
}
}
fun likeButtonClicked(reviewId: Int, isLiked: Boolean) {
viewModelScope.launch {
val query = ToggleReviewLikeUseCase.Query(
reviewId = reviewId,
isLiked = isLiked
)
toggleReviewLikeUseCase(query = query)
}
}
}
Comments