top of page

Unidirectional Data Flow in Android

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


bottom of page