안드로이드 공식문서
https://developer.android.com/topic/libraries/architecture/livedata?hl=ko
LiveData 개요 | Android 개발자 | Android Developers
LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.
developer.android.com
LiveData
LiveData는 관찰 가능한 데이터 홀더 클래스이다.
수명 주기를 인식하며, 활동 수명 주기 상태에 있는 앱 구성요소 관찰자만 업데이트 한다.
관찰자의 수명 주기는 Observer 클래스로 표현되며, 생명 주기가 Started 또는 Resume 상태일 때 활성 상태로 간주한다.
활성 상태일 경우에만 업데이트 정보를 알린다.
// ViewModel
class MyViewModel: ViewModel(){
private var mutableLiveData = MutableLiveData<String>("")
val liveData: LiveData<String> = mutableLiveData
}
// Activity
class MainActivity: AppCompatActivity(){
private val viewModel: MyViewModel by viewModels()
override fun onCreate(){
...
viewModel.liveData.observe(this){ string ->
...
}
}
}
컴포넌트의 LifeCycle 상태를 확인해야 하기에 observe에 LifeCycleOwner를 전달해준다.
공식 문서에서의 LiveData의 이점은 다음과 같다.
1. UI와 데이터 상태의 일치 보장
- Observer 객체에 UI를 업데이트
2. 메모리 누수 없음
- LifeCycle에 결합되어 있으므로 연결 된 LifeCycle 종료 시 자동 삭제
3. 중지된 Activity로 인한 비정상 종료 없음
- LifeCycle이 비활성 상태면 어떠한 이벤트도 받지 않음
4. Lifecycle을 더 이상 수동으로 처리하지 않음
- 관찰을 중지하거나 다시 시작하지 않고, 관찰하는 동안 Lifecycle 상태 변경을 인식하기에 자동 관리
5. 최신 데이터 유지
- 활성화(재활성화) 될 때마다 최신 데이터 갱신
6. 적절한 구성 변경
- Activity/Fragment가 재생성되어도 사용 가능한 최신 데이터 즉시 받음
7. 리소스 공유
- 싱글톤 패턴을 사용하는 LiveData 객체를 확장하여 시스템 서비스를 래핑할 수 있다.
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager: StockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
companion object {
private lateinit var sInstance: StockLiveData
@MainThread
fun get(symbol: String): StockLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
return sInstance
}
}
}
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
// Update the UI.
})
}
}
LifeCycle에 따른 Observer
onResume, onPause, onStop 에서 1초마다 LiveData를 변경하여 Observer Callback 테스트
viewModel.test.observe(this){
Log.e("MainActivity", "$it Observer")
}
CoroutineScope(Dispatchers.IO).launch {
while (true){
delay(1000)
canvasViewModel.test("LifeCycle")
}
}
결과
MainActivity com.test.livedata E onCreate
MainActivity com.test.livedata E onStart
MainActivity com.test.livedata E onResume
MainActivity com.test.livedata E onResume Observer
MainActivity com.test.livedata E onResume Observer
MainActivity com.test.livedata E onResume Observer
MainActivity com.test.livedata E onResume Observer
MainActivity com.test.livedata E onResume Observer
MainActivity com.test.livedata E onPause
MainActivity com.test.livedata E onResume Observer
MainActivity com.test.livedata E onPause Observer
MainActivity com.test.livedata E onStop
이유
Observe는 활성(active)상태에서만 Observer Callback이 발생하기 때문에
비활성(Inactive)상태인 onStop에서는 Callback이 발생하지 않는다.
활성/비활성 상태
// LiveData.java
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
...
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
...
}
// Lifecycle.kt
public enum class State {
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
public fun isAtLeast(state: State): Boolean {
return compareTo(state) >= 0
}
}
위의 코드를 보면 isAtLeast(STARTED) 가 True 일 때를 Active 상태로 보고 있다.
isAtLeast(STARTED)의 True 조건은 STARTED와 RESUMED 상태
INITALIZED, DESTROYED, CREATED상태는 Inactive 상태로 볼 수 있다.
setValue와 postValue
LiveData에 데이터를 변경하는 방법은 두 가지가 있다.
1. setValue
메인 스레드에서 동작
// LiveData.java
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
2. postValue
백그라운드 스레드에서 동작
// LiveData.java
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
만일 Activity에서 비동기로 IO 스레드에서 setValue를 호출하면 다음과 같은 에러가 발생한다.
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
단, 메인 스레드에서 호출하면 에러가 나지 않는다.
따라서 어떤 스레드에서 동작하느냐에 따라 적절히 사용해주면 된다.
LiveData 변환
Transformations.map()
LiveData 객체에 저장된 값에 함수를 적용하고 결과를 다운스트림으로 전파
val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = userLiveData.map {
user -> "${user.name} ${user.lastName}"
}
Transformations.switchMap()
map()과 마찬가지로 LiveData 객체에 저장된 값에 함수를 적용하고 결과를 래핑 해제하여 다운스트림으로 전달 switchMap()에 전달된 함수는 다음 예와 같이 LiveData 객체를 반환해야 한다.
private fun getUser(id: String): LiveData<User> {
...
}
val userId: LiveData<String> = ...
val user = userId.switchMap { id -> getUser(id) }
'안드로이드 > Jetpack' 카테고리의 다른 글
[안드로이드] Navigation Component (0) | 2023.11.29 |
---|---|
[안드로이드] Jetpack (0) | 2023.11.24 |
[안드로이드] ViewModel (1) | 2023.11.24 |
[안드로이드] Databinding (1) | 2023.11.23 |
[안드로이드] Room DB (0) | 2023.11.23 |