android-flow-mvvm-sample
android-flow-mvvm-sample copied to clipboard
Android MVVM sample app that uses kotlin coroutines flow (without LiveData)
Android MVVM sample app that uses kotlin coroutines flow (without LiveData)
This is a sample app that uses kotlin coroutines flow.
It is MVVM Architecture without LiveData.
There is a user search feature on Github.
Screenshot
top | detail |
---|---|
![]() |
![]() |
Architecture

ViewModel -> View
Use kotlin coroutines flow with StateFlow.
After transformed to hot stream with ViewModelScope, bind to view with LifecycleScope.
class TopViewModel(
private val repository: RepoRepository
): ViewModel() {
private val resource = repository
.getRepoList("Google")
.stateIn(viewModelScope, SharingStarted.Eagerly, Resource.Loading)
val data = resource.map { it.valueOrNull.orEmpty() }
}
inline fun <T> AppCompatActivity.bind(
source: Flow<T>,
crossinline action: (T) -> Unit
) {
source.onEach { action.invoke(it) }
.launchIn(lifecycleScope)
}
class TopActivity : AppCompatActivity() {
private val viewModel: TopViewModel by viewModel()
private lateinit var binding: ActivityTopBinding
private lateinit var adapter: RepoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_top)
adapter = RepoAdapter()
bind(viewModel.data) {
adapter.setList(it)
}
}
}
View -> ViewModel
Call a ViewModel function, and emit to MutableSharedFlow.
class TopViewModel(
private val repository: RepoRepository
) : ViewModel() {
private val submitEvent = MutableSharedFlow<Unit>()
private val resource = submitEvent
.flatMapLatest { repository.getRepoList("Google") }
.stateIn(viewModelScope, SharingStarted.Eagerly, Resource.Loading)
fun submit() {
viewModelScope.launch {
submitEvent.emit(Unit)
}
}
}
class TopActivity : AppCompatActivity() {
private val viewModel: TopViewModel by viewModel()
private lateinit var binding: ActivityTopBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_top)
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
viewModel.submit()
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
}
}
View <-> ViewModel (2-way data binding)
Combine the above two.
class TopViewModel(
private val repository: RepoRepository
) : ViewModel() {
private val _userName = MutableStateFlow("Google")
val userName: Flow<String> = _userName
fun setUserName(userName: String) {
_userName.value = userName
}
}
class TopActivity : AppCompatActivity() {
private val viewModel: TopViewModel by viewModel()
private lateinit var binding: ActivityTopBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_top)
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
viewModel.setUserName(newText.orEmpty())
return false
}
})
bind(viewModel.userName) {
val current = binding.searchView.query.toString()
// Need to compare with current value
if (current != it) {
setQuery(query, false)
}
}
}
}