Using LiveData & Flow in MVVM — Part I - ProAndroidDev
Kotlin Flow の登場で盛り上がってきました。
どれにします? どの流れにします?
Repository
Result を返す。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WeatherForecastRepository @Inject constructor() { | |
suspend fun fetchWeatherForecast(): Result<Int> { | |
// Since you can only return one value from suspend function | |
// you have to set data loading before calling fetchWeatherForecast | |
// Fake api call | |
delay(1000) | |
// Return fake success data | |
return Result.Success((0..20).random()) | |
} | |
} |
Flow<Result>を返す。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WeatherForecastRepository @Inject constructor() { | |
/** | |
* This methods is used to make one shot request to get | |
* fake weather forecast data | |
*/ | |
fun fetchWeatherForecast() = flow { | |
emit(Result.Loading) | |
// Fake api call | |
delay(1000) | |
// Send a random fake weather forecast data | |
emit(Result.Success((0..20).random())) | |
} | |
/** | |
* This method is used to get data stream of fake weather | |
* forecast data in real time | |
*/ | |
fun fetchWeatherForecastRealTime() = flow { | |
emit(Result.Loading) | |
// Fake data stream | |
while (true) { | |
delay(1000) | |
// Send a random fake weather forecast data | |
emit(Result.Success((0..20).random())) | |
} | |
} | |
} |
ViewModel
Result を受けて、LiveData<Result> を渡す。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WeatherForecastOneShotViewModel @Inject constructor( | |
val weatherForecastRepository: WeatherForecastRepository | |
) : ViewModel() { | |
private var _weatherForecast = MutableLiveData<Result<Int>>() | |
val weatherForecast: LiveData<Result<Int>> | |
get() = _weatherForecast | |
fun fetchWeatherForecast() { | |
// Set value as loading | |
_weatherForecast.value = Result.Loading | |
viewModelScope.launch { | |
// Fetch and update weather forecast LiveData | |
_weatherForecast.value = weatherForecastRepository.fetchWeatherForecast() | |
} | |
} | |
} |
Flow<Result> を受けて、LiveData<Result> を渡す。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WeatherForecastOneShotViewModel @Inject constructor( | |
weatherForecastRepository: WeatherForecastRepository | |
) : ViewModel() { | |
private val _weatherForecast = weatherForecastRepository | |
.fetchWeatherForecast() | |
.asLiveData(viewModelScope.coroutineContext) // Use viewModel scope for auto cancellation | |
val weatherForecast: LiveData<Result<Int>> | |
get() = _weatherForecast | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WeatherForecastDataStreamViewModel @Inject constructor( | |
weatherForecastRepository: WeatherForecastRepository | |
) : ViewModel() { | |
private val _weatherForecast = weatherForecastRepository | |
.fetchWeatherForecastRealTime() | |
.map { | |
// Do some heavy operation. This operation will be done in the | |
// scope of this flow collected. In our case it is the scope | |
// passed to asLiveData extension function | |
// This operation will not block the UI | |
delay(1000) | |
it | |
} | |
.asLiveData( | |
// Use Default dispatcher for CPU intensive work and | |
// viewModel scope for auto cancellation when viewModel | |
// is destroyed | |
Dispatchers.Default + viewModelScope.coroutineContext | |
) | |
val weatherForecast: LiveData<Result<Int>> | |
get() = _weatherForecast | |
} |
Fragment
LiveData<Result> を受け取る。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WeatherForecastDataStreamFragment : DaggerFragment() { | |
... | |
override fun onActivityCreated(savedInstanceState: Bundle?) { | |
super.onActivityCreated(savedInstanceState) | |
// Obtain viewModel | |
viewModel = ViewModelProviders.of( | |
this, | |
viewModelFactory | |
).get(WeatherForecastDataStreamViewModel::class.java) | |
// Observe weather forecast data stream | |
viewModel.weatherForecast.observe(viewLifecycleOwner, Observer { | |
when (it) { | |
Result.Loading -> { | |
Toast.makeText(context, "Loading", Toast.LENGTH_SHORT).show() | |
} | |
is Result.Success -> { | |
// Update weather data | |
tvDegree.text = it.data.toString() | |
} | |
Result.Error -> { | |
Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show() | |
} | |
} | |
}) | |
lifecycleScope.launch { | |
while (true) { | |
delay(1000) | |
// Update text | |
tvDegree.text = "Not blocking" | |
} | |
} | |
} | |
} |
Flow<Result> を受け取る。
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(
this,
viewModelFactory
).get(WeatherForecastDataStreamFlowViewModel::class.java)
// Consume data when fragment is started
lifecycleScope.launchWhenStarted {
// Since collect is a suspend function it needs to be called
// from a coroutine scope
viewModel.weatherForecast.collect {
when (it) {
Result.Loading -> {
Toast.makeText(context, "Loading", Toast.LENGTH_SHORT).show()
}
is Result.Success -> {
tvDegree.text = it.data.toString()
}
Result.Error -> {
Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show()
}
}
}
}
}
WeatherForecastDataStreamFlowFragment #L47-L75
まとめ
とはいえ、今はまだ、完全に LiveData は捨てれんよの。
Kotlin で Result
Kotlin Flow vs Android LiveData - Stack Overflow
From RxJava 2 to Kotlin Flow: Threading - ProAndroidDev