【MVVM】Flow vs LiveData

👉 Using LiveData & Flow in MVVM — Part I - ProAndroidDev 

Kotlin Flow の登場で盛り上がってきました。

どれにします? どの流れにします?

Repository

Result を返す。

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>を返す。

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> を渡す。

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> を渡す。

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
}

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> を受け取る。

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 

追記: ホットな Flow が登場したので以下。

👉 【MVVM】 Kotlin Flow で使える5つの利用パターン