하... 데이터 바인딩 좀 활용해보려니 live data도 짝꿍이래 view모델도 짝꿍이라 같이 써야한다고 한다.
예제는 mvvm까지 설명해 이래선 알아들을수가 없다 망할...
그니까 그냥 간단하게 databinding이랑만 같이 일단 써보려한다.
그래도 가장 기본적인 개념과 같이 쓰이지않고 분리시켜서 쓸때 어떻게 사용하는지 알아야하니까 그에 대해서 정리해보고 넘어가려한다. (이쪽은 내부적인거 어떻게 돌아가나 알아보려면 끝이없다 조금씩 정복해나가자)
1. LiveData란?
LiveData는 Data의 변경을 관찰 할 수 있는 Data Holder 클래스 입니다.
일반적인 Observable과는 다르게 LiveData는 안드로이드 생명주기(LifeCycle)를 알고 있습니다. (Lifecycle-Aware)
즉, 액티비티나, 프레그먼트, 서비스 등과 같은 안드로이드 컴포넌트의 생명주기(Lifecycle)를 인식하며 그에따라 LiveData는 활성상태(active)일때만 데이터를 업데이트(Update) 합니다.
활성상태란 STARTED 또는 RESUMED를 의미합니다.
또한 LiveData 객체는 Observer 객체와 함께 사용됩니다. LiveData가 가지고 있는 데이터에 어떠한 변화가 일어날 경우, LiveData는 등록된 Observer 객체에 변화를 알려주고, Observer의 onChanged() 메소드가 실행되게 됩니다.
한마디로 정리해보면
라이브데이터는 안드로이드 생명주기를 알고있어 화면이 활성화되어 있지 않을때는 자기도 멈춰있고 화면이 활성상태 일 때만 데이터를 업데이트하는 똑똑한 Observable 이다.
observer은 계속 데이터를 관찰하다 변화가 생기면 이벤트를 발생시켜서 뭔가 처리할수있는
한마디로 여기서 주로 사용 용도를 보면 데이터를 계속 보다가 뭔가 바뀐다싶으면 이벤트를 발생시켜 ui를 업데이트한다 고로 딱히 뭘안해도 데이터만 바뀌어도 ui까지 바뀌게 만들어주는 일을 하는것이다.
2. 그래서 장점이 무었인가?
- Data와 UI간 동기화
LiveData는 Observer 패턴을 따릅니다. 그에따라 LiveData는 안드로이드 생명주기에 데이터 변경이 일어날 때마다 Observer 객체에 알려줍니다.
그리고 이 Observer 객체를 사용하면 데이터의 변화가 일어나는 곳마다 매번 UI를 업데이트하는 코드를 작성할 필요 없이 통합적이고 확실하게 데이터의 상태와 UI를 일치시킬 수 있습니다. - 메모리 누수(Memory Leak)가 없습니다.
Observer 객체는 안드로이드 생명주기 객체와 결합되어 있기 때문에 컴포넌트가 Destroy 될 경우 메모리상에서 스스로 해제합니다. - Stop 상태의 액티비티와 Crash가 발생하지 않습니다.
액티비티가 Back Stack에 있는 것처럼 Observer의 생명주기가 inactive(비활성화) 일 경우, Observer는 LiveData의 어떤 이벤트도 수신하지 않습니다. - 생명주기에 대한 추가적인 handling을 하지 않아도 됩니다.
LiveData가 안드로이드 생명주기에 따른 Observing을 자동으로 관리를 해주기 때문에 UI 컴포넌트는 그저 관련 있는 데이터를 "관찰"하기만 하면 됩니다. - 항상 최신 데이터를 유지합니다.
화면 구성이 변경되어도 데이터를 유지합니다.
예를 들어, 디바이스를 회전하여 세로에서 가로로 화면이 변경될 경우에도 LiveData는 회전하기 전의 최신 상태를 즉시 받아옵니다.(이건 뷰모델 같이 썻을때 같은데???) - 자원(Resource)를 공유할 수 있습니다.
LiveData를 상속하여 자신만의 LiveData클래스를 구현할 수 있고 싱글톤 패턴을 이용하여 시스템 서비스를 둘러싸면(Wrap) 앱 어디에서나 자원을 공유 할 수 있습니다.
그래서 이제 사용법을 알아보자
1. App 수준의 build.gradle 종속성 확인
build.gradle(Module: app) 확인
dependencies {
...
implementation 'androidx.appcompat:appcompat:1.1.0'
...
}
- 위와 같은 종송석이 추가 되어있어야 합니다.
2. LiveData를 사용할 곳에서 LiveData 정의
사용할 액티비티 MainActivity.kt 같은곳 에서 라이브 데이터를 정의한다.
예제에서는 MainActivity.kt 에서 사용했다
private var liveText: MutableLiveData<String> = MutableLiveData()
- 위와 같은 변수를 정의하고 초기화 해줍니다.
- LiveData는 abstract class이기 때문에 LiveData Class를 상속받은 MutableLiveData를 사용합니다.
-> LiveData와 MutableLiveData 는 또 사연이 있기에 나중에 또 알아보겠다
3. LiveData에 Observer 달기
MainActivity.kt
// LiveData의 value의 변경을 감지하고 호출
liveText.observe(this, Observer {
// it로 넘어오는 param은 LiveData의 value
})
저기이제 it 으로 넘어오는게 있는데 그게 livedata의 변경된 값들이 계속 넘어온다 그걸로 원하는 작업 처리해주면 된다(ui바꾸는거 같은거)
매개변수 살펴보면
- 첫번째 매개변수 this는 LifeCycleOwner인 MainActivity 이고.
- 두 번째 매개변수인 Observer Callback은 LiveData(liveText)의 value의 변경을 감지하고 호출되는 부분입니다.
즉 옵져버에서 콜백에서 작업한다.
이제 예제를 통해서 알아보자
LiveData를 이용해 Button을 누르면 TextView의 숫자를
1씩 증가시키기
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/text_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/btn_change"
app:layout_constraintVertical_chainStyle="packed"/>
<Button
android:id="@+id/btn_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ADD 1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_test"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
텍스트 뷰랑 버튼하나있어서 버튼누르면 텍스트뷰 변하는것이다.
MainActivity.kt
package com.imaec.livedataex
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private var liveText: MutableLiveData<String> = MutableLiveData()
private var count = 0 // button을 누르면 증가 될 숫자
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// LiveData의 value의 변경을 감지하고 호출
liveText.observe(this, Observer {
// it로 넘어오는 param은 LiveData의 value
text_test.text = it
})
btn_change.setOnClickListener {
// liveText의 value를 변경
// liveText 자체를 변경시키면 안됨
liveText.value = "Hello World! ${++count}"
}
}
}
버튼을 누르면 livedata에 value값을 변경해주고 그러면 observer가 이벤트를 생성할테니 그 이벤트에
textview의 text를 바꿔주는것을 담은것이다.
또한 live data에 초기에 값을 지정해주는 방법을 알아보자
이거 이렇게 안해도됨 옛날에쓴거라 똥글 ㅎㅎ
LiveData의 값을 초기에 지정해주는 방법은 여러가지가 있는데 두 가지만 알아보자면 하나는 kotlin의 apply block을 사용하는 것이고 하나는 kotlin의 extension function을 사용하는 방법이 있습니다.
1. apply block 이용
private var liveText: MutableLiveData<String> = MutableLiveData<String>().apply {
value = "Hello World! ${++count}"
}
이렇게 초기화할때 .apply 블록에다가 value를 넣어주는 방법이다 이렇게 사용할때는
MutableLiveData의 생성자에 Type을 꼭 지정해주어야한다.
2.extension function 사용
set 함수 정의
/**
* MutableLiveData의 value를 정의 해주는 함수
* @param value MutableLiveData의 value
* @return MutableLiveData의 instance
*/
private fun MutableLiveData<String>.set(value: String) : MutableLiveData<String> {
this.value = value
return this
}
2. 함수사용
private var liveText: MutableLiveData<String> = MutableLiveData<String>().set("Hello World! ${++count}")
이렇게 set을 통해서도 초기 값을 지정해줄수있다.
liveData 연습하면서 getter setter 를 멍청하게 다 만들어가면서 했었었다
심지어 지금보니까 setter로 네이밍해야하는것을 get으로 해놓았다 ㄹㅇ 빡갈통인가
어쩃든 이런거 코틀린에서는 할필요 없는데 이걸 굳이굳이했다
그래서 이런거 getter setter 설정안하고 어떻게 쓰는가?
setter로는
setValue() 함수와 postValue() 함수가 있다.
둘다 mutatableLivedata의 값을 설정해주는 함수인데 살짝 차이가 있다 어느 쓰레드에서 처리하는가의 차이인데 이부분은 적절하게 설정해서 사용해야하는것같다
setValue()
setValue()는 메인 쓰레드에서 LiveData의 값을 변경해준다. 메인 쓰레드에서 바로 값을 변경해주기 때문에 setValue() 함수를 호출한 뒤 바로 밑에서 getValue() 함수로 값을 읽어오면 변경된 값을 가져올 수 있다. 중요한 점은, setValue()는 메인 쓰레드에서 값을 dispatch 하기 때문에 백그라운드에서 setValue()를 호출한다면 오류가 나게 된다. setValue()가 동작하지 않는다면, 해당 함수가 호출되는 쓰레드가 메인 쓰레드인지 체크해봐야 한다.
postValue()
postValue()는 setValue()와 다르게 백그라운드에서 값을 변경한다. 백그라운드 쓰레드에서 동작하다가 메인 쓰레드에 값을 post 하는 방식으로 사용된다. 함수 내부적으로는 아래와 같은 코드가 실행된다.
new Handler(Looper.mainLooper()).post(() -> setValue())
메인 쓰레드에 적용되기 전에 postValue()가 여러 번 호출된다면 모든 값이 적용되는 것이 아니라 가장 최신의 값이 적용된다. 따라서 postValue()를 호출한 뒤 바로 getValue()로 값을 읽으려고 한다면 변경된 값을 읽어오지 못할 가능성이 높다. Hander()를 통해 메인 쓰레드에 값이 전달되기 전에 getValue()를 호출하기 때문이다. LiveData의 값을 즉각적으로 변경해야 한다면 postValue()가 아닌 setValue()를 사용해야 한다.
즉 비동기적으로 처리되는가 + 어느 쓰레드에서 처리하는가의 차이가있다.
사용은 이런식으로
livedata.postValue(바꿔줄값) 이런식으로 넣으면된다.
getter로는 getValue()를 사용하면된다.
내가 아무생각 없이 사용하고있었는데 엄청나게 중요한 실수를 하고있었다
프래그먼트 lifecycleowner 같은경우에는 무조건 생명주기상
viewlifecycleowner을 사용해야하는데 그냥 무감각하게 this 쓰고있었다
-> 컴파일도 되고 실행은 되지만 메모리 릭이 생길것이다.
결론 -> 프래그먼트라면 라이프사이클 오너 사용할때 주의하자 무조건 viewLifecycleOwner로 사용해!!!
참고 블로그
https://leveloper.tistory.com/179
출처: https://dev-imaec.tistory.com/39
https://velog.io/@jojo_devstory/Android-LiveData...%EB%84%8C-%EB%88%84%EA%B5%AC%EB%83%90
'안드로이드' 카테고리의 다른 글
databinding의 Two-way Binding (0) | 2021.11.01 |
---|---|
리사이클러 뷰에 databinding적용해보기 (0) | 2021.11.01 |
디자인패턴의 전반 mvc,mvp,mvvm을 알아보자 (0) | 2021.10.19 |
data binding 과 live data 동시 적용해보자 (0) | 2021.10.14 |
Kotlin Android Extensions,Viewbinding,databinding (0) | 2021.10.10 |