Observer Pattern(옵저버 패턴)
이란 서브젝트의 상태 변호를 관찰하는 옵저버들을 객체와 연결하고 서브젝트의 상태 변화를 초래하는 이벤트가 발생하면 객체가 그 이벤트를 직접 옵저버에게 통제하는 구조로 디자인 패턴의 한 종류이다.
Observable
과 LiveData
가 있다. Observable Boolean, Observable Byte, Observable Char 등 기본형은 이미 준비되어 있는데 특수한 타입을 사용하고 싶다면 ObservableField를 사용해 직접 구현할 수도 있다.
- Obervable :
CallBack 등록
- lifecycle을 알 수 없으므로 등록한 콜백이 상시 작동되어야 한다.
- 작동이 필요 없어지면
removeOnPropertyChangedCallback
을 호출하여 콜백을 수동으로 직접 제거 해야 한다.
private val observableString = ObservableField<String>("Default value")
observableString.addOnPropertyChangedCallback(object: Observable.OnPropertyChangedCallback() { // 콜백을 등록하는 과정이 있음
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
// Do Somthing
}
})
LiveData
는 값의 변경을 감지할 수 있는 Data Holder로 라이브 데이터를 사용할 경우 값의 변경을 감지하여 UI의 변화를 자동으로 반영되게 할 수 있다.
예를 들어, 정수 1을 담고 있는 라이브 데이터의 값이 정수 2로 변하는 순간을 시스템이 감지할 수 있다.
- LiveData :
lifecycleOwner 전달
- lifecycle이
STARTED
혹은RESUMED
로 활성화 상태일 때만 Observe를 수행한다.- 나머지 상태에서는 자동으로 비활성화 된다.
private val observableString = MutableLiveData<String>("Default value")
observableString.observe(lifecycleOwner, Observer { // LifecycleOwner를 전달하는 과정이 있음
// Do Somthing
})
LiveData를 사용하면 다음의 이점이 있다.
- UI와 데이터 상태의 일치 보장
- 메모리 누수 없음
- 중지된 활동으로 인한 비정상 종료 없음
- 수명 주기를 더 이상 수동으로 처리하지 않음
- 최신 데이터 유지
- 적절한 구성 변경
- 리소스 공유
dependencies {
...
// viewModel 사용을 위한 dependency
implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
// by viewModels()
implementation("androidx.activity:activity-ktx:1.8.2")
implementation("androidx.fragment:fragment-ktx:1.6.2")
// save state
implementation("androidx.compose.runtime:runtime-saved-instance-state:1.0.0-alpha11")
// LiveData
implementation("android.arch.lifecycle:livedata:1.1.1") // add
}
var liveCounter: MutableLiveData<Int> = MutableLiveData(_counter) // LiveData 선언
/*
counter를 저장하는 ViewModel
*/
class MyViewModel(
_counter: Int,
private val savedStateHandle: SavedStateHandle
) : ViewModel() { // 생성자로 savedStateHandle을 받도록 함
// LiveData는 변경 불가능한 타입이라서 MutableLiveData를 사용
var liveCounter: MutableLiveData<Int> = MutableLiveData(_counter)
var counter = savedStateHandle.get<Int>(SAVE_STATE_KEY)
?: _counter // savedStateHandle에서 값을 가져오게 하는데 만약 null이면 전달 받은 초기값을 사용하도록 함
fun saveState() {
savedStateHandle.set(SAVE_STATE_KEY, counter) // count 값을 저장하는 SaveState를 정의
}
companion object {
private const val SAVE_STATE_KEY =
"counter" // savedStateHandle은 Key Value 형태로 값을 저장하기 때문에 저장과 복원에 사용할 Key를 정해줌
}
}
SavedStateHandle을 종속 항목으로 전달
해야 하는 경우 ViewModel에 대해 AbstractSavedStateViewModelFactory
를 사용한다.class MyViewModelFactory(
private val counter: Int,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
return MyViewModel(counter, handle) as T // ViewModel을 반환할 때 핸들을 함께 반환하도록 함
}
throw IllegalArgumentException("Viewmodel class not found")
}
}
myViewModel.liveCounter.observe(this) { counter -> // counter LiveData에 대한 Observer 등록 // Do Somthing }
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// ViewModel의 카운터 값을 save state에 연결해서 시스템에 의한 종료가 발생해도 값이 유지 되도록
val factory = MyViewModelFactory(100, this)
val myViewModel by viewModels<MyViewModel> { factory }
binding.button.setOnClickListener {
myViewModel.liveCounter.value = myViewModel.liveCounter.value?.plus(1)
}
// liveCounter를 옵저빙 함으로써 UI에 표시하는 로직을 더이상 버튼에 클릭 리스너 안에 둘 필요가 없음
myViewModel.liveCounter.observe(this) { counter -> // counter LiveData에 대한 Observer 등록
binding.textView.text = counter.toString()
}
}
}
참조
냉동코더의 알기 쉬운 Modern Android Development 입문 강의를 듣고 정리 했습니다.