Although the question relates to Android, it is actually a fundamental question that is independent of the chosen platform. I have read a lot about MVVM, DDD and clean architecture concepts and studied many examples available on GitHub. But there is one thing I almost everytime stumbled upoun: the exposure of the domain model in the viewmodel to the view. And related to this, how and where data is prepared and processed for the view.
For example, we have following domain model:
data class News(
val title: String,
val content: String,
val isFeatured: Boolean,
val publishedAt: LocalDateTime,
val tags: List<Tag>
)
If we now have a ViewModel for a list of News, every example seems to do it this way:
@HiltViewModel
class NewsListViewModel @Inject constructor(
private val newsRepository: NewsRepository,
) : ViewModel() {
// this can also be wrapped in a UiState or a MutableStateFlow
private val _news: MutableLiveData<List<News>> = MutableLiveData()
val news: LiveData<List<News>>
fun loadData() {
// examle, can be in a Flow, RxJava, CompletionBlock, etc.
val loadedNews: List<News> = newsRepository.getNews()
news.postValue(loadedNews)
}
fun onNewsClicked(news: News) {
// do something ...
}
}
At the end, it doesn’t matter, how the ViewModel is consumed by the view (Compose, Fragment with RecyclerView, etc.). But the view has to process the data / the provided domain model to display it. So the question is, if it is not better or cleaner (following MVVM, clean architecture), to define all required data and information in the ViewModel.
Example:
// not sure about the naming.
// If no reuse needed, maybe a nested class in the ViewModel, e.g. NewsListViewModel.Item
data class NewsListItemViewModel(
val title: String,
val content: AnnotatedString,
val featuredIcon: Int,
val backgroundColorRes: Int
val publishedAt: String
val tags: List<String>
val onClick: () -> Unit
)
@HiltViewModel
class NewsListViewModel @Inject constructor(
private val newsRepository: NewsRepository,
) : ViewModel() {
// this can also be wrapped in a UiState or a MutableStateFlow
private val _news: MutableLiveData<List<NewsListItemViewModel>> = MutableLiveData()
val news: LiveData<List<NewsListItemViewModel>>
fun loadData() {
// examle
val loadedNews: List<News> = newsRepository.getNews()
val newsItems = loadedNews.map {
it.title,
Html.fromHtml(it.content),
if (it.isFeatured) R.drawable.ic_featured else 0,
if (it.isFeatured) R.color.bg_featured else R.color.bg_default,
dateFormatter.format(it.publishedAt),
it.tags.map { it.title },
{ onNewsClicked(it) }
}
news.postValue(newsItems)
}
fun onNewsClicked(news: News) {
// do something ...
}
}
1