If the action is produced further down the UI tree, like in a RecyclerView item or a custom View, the ViewModel should still be the one handling user events.
For example, suppose that all news items from NewsActivity contain a bookmark button. The ViewModel needs to know the ID of the bookmarked news item. When the user bookmarks a news item, the RecyclerView adapter does not call the exposed addBookmark(newsId) function from the ViewModel, which would require a dependency on the ViewModel. Instead, the ViewModel exposes a state object called NewsItemUiState which contains the implementation for handling the event:
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
val publicationDate: String,
val onBookmark: () -> Unit
)
class LatestNewsViewModel(
private val formatDateUseCase: FormatDateUseCase,
private val repository: NewsRepository
)
val newsListUiItems = repository.latestNews.map { news ->
NewsItemUiState(
title = news.title,
body = news.body,
bookmarked = news.bookmarked,
publicationDate = formatDateUseCase(news.publicationDate),
// Business logic is passed as a lambda function that the
// UI calls on click events.
onBookmark = {
repository.addBookmark(news.id)
}
)
}
}This way, the RecyclerView adapter only works with the data that it needs: the list of NewsItemUiState objects. The adapter doesn’t have access to the entire ViewModel, making it less likely to abuse the functionality exposed by the ViewModel. When you allow only the activity class to work with the ViewModel, you separate responsibilities. This ensures that UI-specific objects like views or RecyclerView adapters don't interact directly with the ViewModel.
Warning: It's bad practice to pass the ViewModel into the RecyclerView adapter because that tightly couples the adapter with the ViewModel class.
https://developer.android.com/topic/architecture/ui-layer/events#recyclerview-events