본문 바로가기
🖥 Programming/📱 Android (Kotlin)

[Android][kotlin] LiveData 알아보기

by MinChan-Youn 2021. 10. 26.

Android Developer의 한글 공식문서입니다.

https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#kotlin 

 

LiveData 개요  |  Android 개발자  |  Android Developers

LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.

developer.android.com

내용은 다음과 같습니다.

 

 

LiveData를 사용하면 다음의 이점이 있습니다.

 

UI와 데이터 상태의 일치 보장

 LiveData는 관찰자 패턴을 따릅니다. LiveData는 기본 데이터가 변경될 때 Observer객체에 알립니다. 코드를 통합하여 이러한 observer객체에 UI를 업데이트 할 수 있습니다. 이렇게 하면 앱 데이터가 변경될 때마다 관찰자가 대신 UI를 업데이트 하므로 개발자가 업데이트할 필요가 없습니다.

 

메모리 누수 없음

 관찰자는 Lifecycle객체에 결합되어 있으며 연결된 수명 주기가 끝나면 자동으로 삭제됩니다.

 

중지된 활동으로 인한 비정상 종료 없음

 활동이 백 스택에 있을 때를 비롯하여 관찰자의 수명 주기가 비활성 상태에 있으면 관찰자는 어떤 LiveData 이벤트도 받지 않습니다.

 

수명 주기를 더 이상 수동으로 처리하지 않음

 UI구성요소는 관련 데이터를 관찰하기만 할 뿐 관찰을 중지하거나 다시 시작하지 않습니다. LiveData는 관찰하는 동안 관련 수명 주기 상태의 변경을 인식하므로 이 모든 것을 자동으로 관리합니다.

 

적절한 구성 변경

 기기 회전과 같은 구성 변경으로 인해 활동 또는 프래그먼트가 다시 생성되면 사용 가능한 최신 데이터를 즉시 받게 됩니다.

 

리소스 공유

 앱에서 시스템 서비스를 공유할 수 있도록 싱글톤 패턴을 사용하는 LiveDta객체를 확장하여 시스템 서비스를 래핑할 수 있습니다. LiveData객체가 시스템 서비스에 한 번 연결되면 리소스가 필요한 관찰자가 LiveData객체를 볼 수 있습니다.

 

 

 

아래는 LiveData를 사용한 예제코드 입니다.

자세하게 말하면 이번에는 ViewBinding을 사용하고 LiveData관련 2가지 케이스를 가지고 비교해 보겠습니다.

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.livedata_kotlin"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    //ViewBinding사용!
    buildFeatures{
        viewBinding = true;
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'


	//ViewModel 및 LiveData사용!
    def lifecycle_version = "2.0.0"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
}
//MainActivity.kt
class MainActivity : AppCompatActivity() {
    //binding
    private lateinit var binding: ActivityMainBinding
    private lateinit var mModel: RandomNumberViewModel
    private lateinit var mModel2: TimerViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)



        //TODO: 예제1
        //Get the view model
//        (참고)Lifecycle -extension 라이브러리가 2.2.0으로 버전업하면서 ViewModelProviders는 Deprecate되었습니다
//        mModel = ViewModelProviders.of(this).get(RandomNumberViewModel::class.java) //구버전

        mModel = ViewModelProvider(this).get(RandomNumberViewModel::class.java) //신버전

        //Create the observer which updates the ui
        val randomNumberObserver = Observer<Int> { newNumber ->
            //Update the ui wuth current data
            binding.mTextview.text = "Current Number : $newNumber"
        }

        //Observe the live data, passing in this activity as the life cycle owner and the observer
        mModel.currentRandomNUmber.observe(this, randomNumberObserver)

        //Button click listener
        binding.mButton.setOnClickListener {
            //Change the data
            mModel.currentRandomNUmber.value = Random().nextInt(50)
        }



        //TODO: 예제2
        //Get the view model
        mModel2 = ViewModelProvider(this).get(TimerViewModel::class.java)

        //Observe case1
        mModel2.seconds().observe(this, Observer {
            binding.mTextview2.text = it.toString()
        })

        //Observe case2
        mModel2.finished.observe(this, Observer {
            if(it){
                Toast.makeText(this, "Finished!", Toast.LENGTH_SHORT).show()
            }
        })

        binding.mTimeStart.setOnClickListener {
            if(binding.mInputText2.text.isEmpty() || binding.mInputText2.text.length>10){
                Toast.makeText(this, "Invaild Number!", Toast.LENGTH_SHORT).show()
            }else{
                mModel2.timerValue.value = binding.mInputText2.text.toString().toLong()
                mModel2.startTimer()
            }
        }
        binding.mTimeFinish.setOnClickListener {
            binding.mTextview2.text = "0"
            mModel2.stopTimer()
        }
    }
}
//RandomNumberViewModel.kt
class RandomNumberViewModel : ViewModel() {

    //Create a LiveData with a random number
    val currentRandomNUmber: MutableLiveData<Int> by lazy {
        MutableLiveData<Int>()
    }
}
//TimerViewModel.kt
class TimerViewModel : ViewModel() {
    private lateinit var timer: CountDownTimer
    var timerValue = MutableLiveData<Long>()

    private val _seconds = MutableLiveData<Int>() //Observe
    var finished = MutableLiveData<Boolean>() //Observe

    fun seconds(): LiveData<Int>{
        return _seconds
    }

    //이렇게 구문을 사용하는 이유는
    //실제 _seconds를 직접접근하는것이 아니라 읽기만 가능한 val의 seconds를 접근하여 원본이 수정되지 않도록 하기 위해서!
    val seconds: LiveData<Int>
        get() = _seconds


    fun startTimer(){
        timer = object: CountDownTimer(timerValue.value!!.toLong(), 1000){
            override fun onTick(millisUntilFinished: Long) {
                val timeLeft = millisUntilFinished/1000
                _seconds.value = timeLeft.toInt()
            }

            override fun onFinish() {
                finished.value = true
            }
        }.start()
    }

    fun stopTimer(){
        timer.cancel()
    }
}
<?xml version="1.0" encoding="utf-8"?>
<!-- activity_main.xml -->
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/m_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#F0F0F0"
        android:text="랜덤 숫자 생성" />

    <TextView
        android:id="@+id/m_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="#F0F0F0"
        android:padding="10dp"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <ImageView
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:background="#FF0000" />


    <EditText
        android:id="@+id/m_inputText2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#F0F0F0"
        android:padding="10dp"
        android:text="10000" />

    <TextView
        android:id="@+id/m_textview2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#00F0F0"
        android:padding="10dp"
        android:text="시간" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/m_time_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            android:background="#F0F0F0"
            android:padding="10dp"
            android:text="시작" />

        <TextView
            android:id="@+id/m_time_finish"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#F0F0F0"
            android:padding="10dp"
            android:text="종료" />

    </LinearLayout>


</LinearLayout>