일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- not working
- Android
- 리액트 네이티브
- Invalid credentials
- Git
- EC2
- AWS
- 인증문제해결
- VisualStudio
- 뒤로가기 안됨
- 예제
- react-native
- flutter
- git인증
- 개발환경설정
- react
- 데이터바인딩
- bitbucket
- gradle
- WPF
- 안드로이드 스튜디오
- 설정
- 안됨
- aPK
- 리액트
- 디버깅
- 안드로이드
- 빌드 오류
- sourcetree
- Kotlin
- Today
- Total
물에 살고싶은 개발자
[SingleLiveEvent] LiveData 사용 시 observe가 계속 호출되는 문제가 발생할 경우 본문
전제
안드로이드 MVVM 구조와 JetPack의 네비게이션,데이터바인딩을 사용하고 있으며, koin의 by sheardViewModel() 을 이용해 여러 Fragment에서 ViewModel을 공유하고 있다.
원인
여러 Fragment에서 ViewModel을 공유하다보니, 데이터의 상태가 유지되어 이전 화면으로 돌아갈 경우 세팅되어있던 데이터가 유지되는것은 좋았으나, 옵저빙에서 네비를 태운 경우 돌아가자마자 다시 observe가 호출되어 다시 앞 화면으로 돌아가는 경우가 생겼다. 즉, Fragment가 세팅됨과 동시에 데이터 변경이 없었음에도 변경된 이력이 있는 LiveData를 다시 옵저빙해 다시 네비를 태우는것.
해결책
네비게이션을 쓰는 경우 무지성으로 LiveData를 상속받아 만든 SingleLiveEvent를 사용하면 되겠다.
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val TAG = "SingleLiveEvent"
private val mPending: AtomicBoolean = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner) { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
}
위 싱글데이터 클래스를 라이브데이터 대용으로 사용하면 데이터가 변경되었던건에 대해선 단 한번만 이벤트를 보내므로, 이전과 같은 문제가 발생하지는 않는다.
원인에 대해 조금만 더 자세히 설명해보자면,
A Fragment에서 [다음]버튼을 클릭 시 B Fragment로 넘어가는 화면이 있다고 치자.
데이터바인딩과 뷰모델을 이용해서 A에서 각종 데이터를 세팅하고 버튼동작 역시 뷰모델의 함수를 만들어 라이브데이터값을 변경함으로써 A Fragment에서 옵저빙해 B로 화면전환해주는 코드가 있다고했을때, B에서 뒤로가기 또는 navController.popBack()을 하더라도 A의 옵저빙에서 다시 B로 화면전환을 시켜버리는것이다.
이를 막기 위해 라이브데이터를 Boolean 타입으로 만들고 옵저빙에서 값이 true일때 일단 다시 false로 바꾼 뒤 화면전환을 시켜주는식으로도 해보았으나..이 역시도 다른 문제를 발생시키기에 더 나은 방법을 찾다가 스택오버플로우에서 찾게 된 솔루션이다.
끝.