저번에 LiveData를 데이터 바인딩과 합쳐서 설명을 해 줬었는데,
그때 LiveData에 대한 설명이 좀 부족했었다.
내가 잘 모를 때이기도 하면서, 급하게 썼었기 때문인 것 같다. 그래서 이번에 제대로 한번 공부해 와 보았다.
먼저 Gradle세팅부터 해야 한다.
저번에 말했던 gradle(Module:...) 파일에
dependencies {
...
implementation 'androidx.appcompat:appcompat:1.4.1'
...
}
dependencies에 이렇게 implementation을 해준다.
그러면 세팅은 끝이 났다.
일단 실습으로 들어가기 전에 이론적인 것을 먼저 알아보자.
LiveData란?
Livedata는 관찰 가능한 데이터 홀더 클래스이다.
일반적인 Observable과는 다르게 안드로이드의 수명주기(LifeCycle)를 알고 있다.
즉, 활동, 프래그먼트, 서비스 등 다른 앱 구성요소의 수명 주기를 고려합니다. 수명 주기 인식을 통해 LiveData는 활동 수명 주기 상태에 있는 앱 구성요소 관찰자만 업데이트한다.
여기에서 활동 수명 주기 상태란 STARTED(시작) 또는 RESUME(재게)를 의미한다.
또한 LiveData 객체는 Observer 객체와 함께 사용된다. LiveData가 가지고 있는 데이터에 어떠한 변화가 일어날 경우, LiveData는 등록된 Observer 객체에 변화를 알려주고, Observer의 onChanged() 메서드가 실행되게 된다.
LiveData가 생명주기를 아는 법
lifecycleOwner라는 클래스가 안드로이드의 생명주기를 알고 있다.
이 친구는 getLifeCycle() 메서드만 가지고 있는 단일 인터페이스 클래스이며 Activity나 Fragment에서 상속하고 있다.
즉, LiveData의 Observer 메서드의 LifeCycleOwner를 Activity나 Fragment의 변수로 사용한다면 각 화면의 생명주기에 따라 데이터를 처리한다.
사용 시 이점
- UI와 데이터 간 동기화 : LiveData는 관찰자 패턴을 따른다. LiveData는 기본 데이터가 변경될 때 Observer 객체에 알린다. 코드를 통합하여 이러한 Observer 객체에 UI를 업데이트할 수 있다. 이렇게 하면 앱 데이터가 변경될 때마다 관찰자가 대신 UI를 업데이트하므로 개발자가 업데이트할 필요가 없다.
- 메모리 누수 없음 : 관찰자는 Lifecycle 객체에 결합되어 있으며 연결된 수명 주기가 끝나면 자동으로 삭제된다.
- 중지된 활동(STOP)으로 인한 비정상 종료(CRASH) 없음 : 활동이 백 스택에 있을 때를 비롯하여 관찰자의 수명 주기가 비활성 상태에 있으면 관찰자는 어떤 LiveData 이벤트도 받지 않는다.
- 수명 주기를 더 이상 수동으로 처리하지 않음 : UI 구성요소는 관련 데이터를 관찰하기만 할 뿐 관찰을 중지하거나 다시 시작하지 않습니다. LiveData는 관찰하는 동안 관련 수명 주기 상태의 변경을 인식하므로 이 모든 것을 자동으로 관리합니다.
- 최신 데이터 유지 : 수명 주기가 비활성화되면 다시 활성화될 때 최신 데이터를 수신합니다. 예를 들어 백그라운드에 있었던 활동은 포그라운드로 돌아온 직후 최신 데이터를 받습니다.
- 리소스(자원) 공유 : 앱에서 시스템서 eData 객체가 시스템 서비스에 한 번 연결되면 리소스가 필요한 모든 관찰자가 LiveData 객체를 볼 수 있다.
대충 이론을 알았다면 이제 예제로 넘어가 보자.
먼저 xml파일을 하나 만들어주자.
이번에는 예제로 간단한 숫자 업 다운을 시키는 앱을 만들어 볼 것이다.
나는 이런 식으로 구성된 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/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LiveData : "
android:textSize="64sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginBottom="50dp"
/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UP"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.727"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintVertical_bias="0.487"
android:layout_marginRight="30dp"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DOWN"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.297"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintVertical_bias="0.487"
android:layout_marginLeft="30dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
간단하게 UP 버튼을 누르면 값이 올라가고, DOWN버튼을 누르면 값이 내려가게 될 것이다.
MainActivity코드이다.
package com.example.livedata
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
class MainActivity : AppCompatActivity() {
var LiveData:MutableLiveData<String> = MutableLiveData<String>()
// 그냥 LiveData는 추상클래스 이기 때문에 이 LiveData를 상속받은 MutableLiveData를 사용
// 또한 Text값을 바꿀 것이기 때문에 <String>으로 선언
private var num = 0 // 변경 될 수
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text = findViewById<TextView>(R.id.textView2)
val up = findViewById<Button>(R.id.up)
val down = findViewById<Button>(R.id.down)
// LiveData의 값(Value) 변경을 감지하고 호출됨
LiveData.observe(this, Observer {
//it으로 넘어오는 param은 LiveData의 바뀐 값. 즉, num.toString
text.text = it
})
up.setOnClickListener {
// LiveData의 value를 변경
// LiveData 자체를 변경시키면 안됨
LiveData.value = "LiveData : ${++num}"
}
down.setOnClickListener {
LiveData.value = "LiveData : ${--num}"
}
}
}
참고로
var LiveData:MutableLiveData<String> = MutableLiveData<String>()
이 친구를
var LiveData:LiveData<String> = MutableLiveData<String>()
이렇게 짜게 되면 LiveData.value = "값"을 하게 될 때 에러가 뜨게 된다.
Cannot assign to 'value': the setter is public in '메서드명' 대충 이런 에러가 뜰 텐데 이유가
LiveData에서는 setValue라는 메서드가 protected이기 때문이다. 즉 상속을 하지 않으면 값 변경을 못 시킨다.
그렇기 때문에 우리는 LiveData를 상속한 MutableLiveData로 선언하도록 하자.
난 이 에러 때문에 30분을 날렸다
어쨌든 실행시켜보자.
잘 돌아가는 것을 볼 수 있다.
아 참고로 이렇게 무지성으로 Activity안에 LiveData를 만들지 말고 ViewModel내에서 정의한 뒤 선언하여 사용하는 것이 더 좋다.
이유는 당연히 의존성을 낮추고, View는 오직 Data를 보여주는 역할을 유지하기 위함이다.(이건 안드로이드 아키텍처이다.)
이렇게 LiveData에 대해 자세히 알아보았다.
하지만 이렇게 LiveData를 혼자 쓰는 건 크게 좋아진 것을 느끼기 어렵다.
그렇기에 Databinding, ViewModel, RoomDatabase, Reactive Programing과 함께 쓰며 또 함께 쓰면 UI 출력 데이터에 관한 실수도 줄여준다.
참고
'안드로이드' 카테고리의 다른 글
[안드로이드] Activity와 Fragment (0) | 2022.06.07 |
---|---|
[안드로이드] 클린 아키텍처 정리 (0) | 2022.05.24 |
[안드로이드] 인텐트필터 알아보기 (0) | 2022.04.29 |
[안드로이드] 인텐트 알아보기 (0) | 2022.04.28 |
[안드로이드/Kotlin] MVVM응용 - 번호 뽑기 만들기 (0) | 2022.03.18 |