회사를 가니 Live data를 쓰고 있었다. Flow를 올해 1월부터 시작해서, 약 10개월 동안 잘 쓰고 있던 터라, Live Data 쓰는 법을 완전히 까먹고 있었다.
세 개 다 안드로이드 프로그래밍에서 비동기 프로그래밍을 가능하게 하는 라이브러리이다. Observer 패턴을 구현할 수 있는 방법이기도 하다.
출시 시기가 궁금해서 찾아봤는데, 변화 주기가 생각보다 매우 빠르다. 이렇게 해서 개발자 계속 할 수 있을지나 모르겠다.
Flow는 코루틴 위에서 돌아가는 비동기 프레임워크인데, 주목할만한 점은 스레드 위가 아니라 코루틴 위에서 동작한다는 것이다.
코루틴에 대해 간략히 설명하자면, 동시성을 가능하게 하며, 스레드의 개념이 아니라 스레드 처럼 동작한다고 해서 경량 스레드라고도 불린다. 스레드가 아닌 덕분에, 컨텍스트 스위칭이 없어 동시성 작업을 하는데 있어 빠른 속도를 지원한다.
https://developer.android.com/kotlin/coroutines?hl=ko
다시 Flow로 돌아가자면, Coroutine 위에서 생성/소비 되는 비동기식 데이터 스트림이며, 여러값을 순차적으로 내보낼 수 있으며 (emit 등을 통해), 중간 연산자(combine, map, take…), 종단 연산자(collect …) 등을 활용하여 풍부하게 사용할 수 있다. 특히, 종단연산자를 호출하기 전까지는 데이터를 생성하지 않아, 생성하는 위치를 지정할 수 있어 불필요한 리소스 사용을 막을 수 있다.
suspend를 함께 사용하여 일시중단 할 수도 있다.
flow는 cold stream이고 channel은 hot stream에 해당하는 개념이라는데, 인상깊었던 것은 cold stream은 CD Player, Hot stream은 라디오에 비유한 글이다. 사용자가 원하는 때(호출한때)에 맞춰서 원하는 정보를 얻을 수 있다는 게 Flow와 적절한 비유인듯하다.
https://developer.android.com/topic/libraries/architecture/livedata?hl=ko
LifecycleOwner가 살아 있는 경우, 모든 변동사항이 관찰됨.
LiveData 자체는 읽기만 가능하고 쓸 수는 없다
Activity의 onCreate에서 관찰
알아서 변동사항을 추적하므로 개발자가 업데이트 할 필요가 없음
생명주기와 결합되어서 메모리 누수 걱정할 필요 없음 → 생명주기가 끝나면 자동 삭제됨
사용법
class NameActivity : AppCompatActivity() {
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
private val model: NameViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Other code to setup the activity...
// Create the observer which updates the UI.
// 옵저버 만들기
val nameObserver = Observer<String> { newName ->
// Update the UI, in this case, a TextView.
nameTextView.text = newName
}
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
// 라이브데이터와 위에서 만든 옵저버를 연결
model.currentName.observe(this, nameObserver)
}
}
합쳐서 사용
viewModel.startDate.observe(this) { startDate ->
binding.firstDatePickerTv.text = startDate
}
→ Live data와 Observer를 결합한거임
“LiveData와 함께 코루틴 사용”
놀랍게도 이런것도 있다.
val liveDataSource1 = MutableLiveData<String>()
val liveDataSource2 = MutableLiveData<String>()
val mediatorLiveData = MediatorLiveData<String>()
mediatorLiveData.addSource(liveDataSource1) { value ->
val source2Value = liveDataSource2.value ?: ""
mediatorLiveData.value = "Combined: $value and $source2Value"
}
mediatorLiveData.addSource(liveDataSource2) { value ->
val source1Value = liveDataSource1.value ?: ""
mediatorLiveData.value = "Combined: $source1Value and $value"
}
val flow1: Flow<String> = ...
val flow2: Flow<String> = ...
val combinedFlow = flow1.combine(flow2) { value1, value2 ->
"Combined: $value1 and $value2"
}
combinedFlow.collect { combinedValue ->
// UI 업데이트
}
내가 이해한대로 Flow와 Livedata에 대한 정리는 이렇다.
내가 느낀 분명한 장점은 두가지이다.
combine이 매우 좋다. 가독성도 좋고, 데이터 클래스를 따로 처리해줄 필요가 없다.
// MediatorLiveData를 사용하여 두 LiveData를 결합
val distanceData = MediatorLiveData<Pair<Int, Int>>().apply {
addSource(userDistance) { userDist ->
value = Pair(userDist ?: 0, distance.value ?: 0)
}
addSource(distance) { dist ->
value = Pair(userDistance.value ?: 0, dist ?: 0)
}
}
val distanceData = combine(userDistance, distance) { userDist, dist -> userDist to dist }
가독성이 매우 좋아졌지 않았나!!!
스레드를 걱정할 필요가 없다.
flow는 스레드위에서 동작하는게 아니라 코루틴 위에서 동작한다. 그러므로, 메인 스레드에서 동작하는 livedata에 비해 flow는 스레드를 괴롭히지 않기 때문에 비교적 가볍고 부화되면 어떡하지 이런 걱정이 안든다. 물론, 테스트를 해보지 않는한, 개발자가 정량적으로 알 수는 없다.
개발자가 계속 공부를 해야하고 끊임없이 새로운 라이브러리, 프레임워크들이 나오는 이유가 내가 생각하기에는 저 두가지 이유 때문인 것 같다. 가독성와 성능. 이 두가지 이유라면 충분히 시간을 들여서 공부를 하고, 마이그레이션 하기에 충분한 메리트가 있지 않는가.
나름 스타트업이지만, 8년이라는 세월은 생각보다 길다. 짧다고 생각했는데 길다. 8년짜리 묵은 코드를 보고 있으면, 그저 웃프다. 아무튼, 스타트업이라고 해서 무조건 신기술을 쓰는 것도 아니고, 앞으로 내가 가게 될 여느 회사던, 레거시는 있을 수 있기에, 이번 기회에 익혀두면 좋겠지 아니한다.
이와 비슷한 이유로, 안드로이드 개발자가 compose만 할 줄 알고 xml을 못하면, 그런 사람을 뽑아줄 회사가 몇군데나 되는가싶다. 새 프로젝트거나 아니면 이미 마이그레이션이 다 끝난 곳이 아니라면 xml을 다룰 줄 알아야한다. 이렇듯, 구 기술이라고 무시할게 못된다!