안녕하세요. Marty입니다
Android 개발을 하면서 가장 많이 고민하는 부분 중 하나가 바로 상태 관리입니다. 특히 Jetpack Compose를 사용하면서 LiveData와 Flow 중 어떤 것을 사용해야 할지 고민하신 분들이 많을 것입니다.
오늘은 제가 WaveOn 앱을 개발하면서 겪은 경험을 바탕으로 두 라이브러리를 비교해보겠습니다.
7년간 주로 LiveData를 사용해왔는데, WaveOn 앱에서 Compose를 도입하면서 Flow로 마이그레이션하게 되었습니다. 그 과정에서 두 라이브러리의 차이점을 정말 많이 느꼈습니다.
class WeatherViewModel : ViewModel() {
private val _weatherData = MutableLiveData<WeatherData>()
val weatherData: LiveData<WeatherData> = _weatherData
fun fetchWeather() {
viewModelScope.launch {
val data = repository.getWeatherData()
_weatherData.value = data
}
}
}
class WeatherViewModel : ViewModel() {
private val _weatherData = MutableStateFlow<WeatherData?>(null)
val weatherData: StateFlow<WeatherData?> = _weatherData.asStateFlow()
fun fetchWeather() {
viewModelScope.launch {
repository.getWeatherData().collect { data ->
_weatherData.value = data
}
}
}
}
@Composable
fun WeatherScreen(viewModel: WeatherViewModel) {
val weatherData by viewModel.weatherData.observeAsState()
weatherData?.let { data ->
Text(text = "온도: ${data.temperature}°C")
Text(text = "날씨: ${data.weatherStatus}")
}
}
@Composable
fun WeatherScreen(viewModel: WeatherViewModel) {
val weatherData by viewModel.weatherData.collectAsState()
weatherData?.let { data ->
Text(text = "온도: ${data.temperature}°C")
Text(text = "날씨: ${data.weatherStatus}")
}
}
7년간 LiveData를 사용해왔는데, Compose와 함께 사용하면서 몇 가지 한계점을 발견했습니다:
// 문제가 있던 코드
class ReservationRepository @Inject constructor() {
private val _reservations = MutableLiveData<List<Reservation>>()
val reservations: LiveData<List<Reservation>> = _reservations
fun addReservation(reservation: Reservation) {
val currentList = _reservations.value ?: emptyList()
_reservations.value = currentList + reservation
}
}
// Compose에서 사용할 때
@Composable
fun ReservationList(viewModel: ReservationViewModel) {
val reservations by viewModel.reservations.observeAsState()
// observeAsState()를 사용해야 하는 번거로움
}
문제점:
Flow로 바꾸면서 정말 편해졌습니다:
// 개선된 코드
class ReservationRepository @Inject constructor(
private val reservationDao: ReservationDao
) {
fun getUpcomingReservations(): Flow<List<Reservation>> {
return reservationDao.getUpcomingReservations(Date()).map { entities ->
entities.map { it.toDomainModel() }
}
}
}
// Compose에서 사용할 때
@Composable
fun ReservationList(viewModel: ReservationViewModel) {
val reservations by viewModel.reservations.collectAsState()
// collectAsState()로 직접 사용 가능!
}
장점:
1. 날씨 데이터 실시간 업데이트
// Flow 사용
fun getWeatherData(): Flow<WeatherData> {
return weatherApiService.getWeather()
.map { response -> response.toWeatherData() }
.catch { error ->
emit(WeatherData.error(error.message))
}
}
2. 예약 내역 필터링
// Flow의 강력한 연산자 활용
fun getUpcomingReservations(): Flow<List<Reservation>> {
return reservationDao.getAllReservations()
.map { reservations ->
reservations.filter { it.date >= Date() }
.sortedBy { it.date }
}
}
3. 여러 데이터 소스 조합
// 날씨 + 수온 데이터 조합
fun getCombinedData(): Flow<CombinedData> {
return combine(
weatherRepository.getWeatherData(),
temperatureRepository.getTemperatureData()
) { weather, temperature ->
CombinedData(weather, temperature)
}
}
// 기존 LiveData
private val _data = MutableLiveData<String>()
// Flow로 변경
private val _data = MutableStateFlow<String?>(null)
val data: StateFlow<String?> = _data.asStateFlow()
// LiveData
val data by viewModel.data.observeAsState()
// Flow
val data by viewModel.data.collectAsState()
개인적인 추천: Flow를 사용하세요.
특히 Compose를 사용한다면 Flow가 훨씬 더 자연스럽고 강력합니다. 처음에는 조금 어려울 수 있지만, 한번 익숙해지면 정말 편리합니다.
저는 WaveOn 앱에서 Flow를 선택했습니다. 그 이유는: