본문 바로가기
안드로이드/코틀린

(android-jetpack)LiveData

by 나이아카 2021. 4. 13.
728x90

 LiveData라는 친구는 jetpack 관련 라이브러리를 뒤적거리다보면 항상 등장하고 꽤 중요하게 다뤄지는 기본 옵션같은 존재입니다. 이것을 대표할 수 있는 가장 큰 특징은 수명주기를 인식한다는 것입니다. 물론 일반적인 변수가 수명주기를 인식하는 것은 큰 메리트가 아닐지도 모릅니다. 그러나 이 LiveData는 observable하다는 특징을 지니고 있습니다. 이는 LiveData가 들고 있는 변수가 변경될 경우, 그와 연결된 데이터를 자동으로 변경시켜 준다는 것을 의미합니다.

 

 

 

 

 기존에 여러가지 옵저버들을 사용해 '변수 변경 -> UI 처리 코드 추가 -> UI 변경' 이 과정을 LiveData를 통해서 가능하다는 것을 의미합니다. 허나 다른 옵저버들은 현재 연결된 UI의 상태가 어떤 상태인지(생명주기를 인식하지 못해) 모르기 때문에 죽어 있더라도 코드를 실행해보는 등 비효율적인 문제가 발생할 수 있습니다. 이는 LiveData가 생명주기를 파악하는 것으로 해결할 수 있습니다.

 

 왼쪽 사진을 통한 프래그먼트를 예로 들었을 때, LiveData가 연결된 프래그먼트의 현재 상태가 onStart 이상이고 아직 onPause를 거치지 않았다면 데이터는 자연스럽게 프래그먼트의 UI를 조작하게 됩니다. 허나 onPuase로 넘어가게 되면 LiveData에 달려 있던 옵저버는 자연스럽게 소멸하게 됩니다. 이로 인해 사라진 프래그먼트의 옵저버가 유지되는 문제를 막아 옵저버 관련 메모리 누수를 방지하는데 도움을 줄 수 있습니다.

 

 그러나 이런 LiveData를 프래그먼트 및 액티비티에 직접 선언해 사용한다면 기존 변수와 큰 차이를 느끼기 어려울 수 있습니다. 기존 UI 클래스 역시 수명주기에 따라 제거될 때 연결된 다른 주소값이 없다면 같이 데이터가 날아가곤 하니까요. 이런 LiveData는 같은 jetpack 라이브러리의 ViewModel과 사용할 때 큰 효율을 나타냅니다.(사실 대부분의 jetpack 라이브러리 관련 코드는 전부 내부 코드들과 연동해서 사용해야 효율을 극대화시킬 수 있습니다.)

 

 

 

 

 

 

 

 

developer.android.com/topic/libraries/architecture/livedata?hl=ko

 

LiveData 개요  |  Android 개발자  |  Android Developers

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

developer.android.com

 공식 문서에서는 LiveData를 사용함으로서 총 7가지 정도의 이익을 볼 수 있다고 설명하고 있습니다.

 

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

 이는 observer 패턴의 특징으로 개발자가 세세하게 신경쓰지 않아도 데이터의 변경에 따라 UI가 변경되는 것을 의미합니다. 물론 여기서 하드웨어 기기 오류 등 코드 외적인 특정 오류에 따른 문제는 포함하지 않습니다.

 

메모리 누수 없음, 중지된 활동으로 인한 비정상 종료 없음, 수명 주기를 더 이상 수동으로 처리하지 않음

 수명 주기를 가지고 있는 클래스이기 때문에 수명 주기에 맞춰 observe 클래스를 제거하고 생성하기 때문에 사용하지 않는 observer가 메모리 상에 남아있는 문제를 해결해줍니다. 또한 observer가 메모리 상에 남아 이미 죽어있는 UI를 건드리는 경우 발생하는 여러가지 exception들을 방지해 줍니다.

 

최신 데이터 유지

 옵저버 패턴을 유지하는 동안 데이터는 패턴이 망가지는 경우(UI와 옵저버 클래스의 강제적인 연동 해제와 같은)를 제외하고는 변수에 들어있는 데이터와 UI상에 표시되는 데이터가 일치함을 의미합니다.

 

적절한 구성 변경

 안드로이드의 경우 화면 변동이 일어날 경우 UI 클래스 내부의 변수들을 소실할 확률이 높은데 이를 적절하게 방지해(기기 변경시 데이터 소실 자체가 없는 건지, 아니면 소실 후 복구가 가능한 건지에 대해서는 내부적인 확인을 해봐야 할 듯! 그것도 아니라면 단순히 viewModel과 같이 사용했을 때 viewModel이 죽지 않으니 살아있어서 자동으로 UI와 연결되니 계속 유지가 된다는 건지는...) UI의 데이터 최신화를 이루어냅니다.

 


 라이브 데이터를 사용하기 위해 가장 먼저 해야할 일은

 

  • 라이브 데이터를 사용할 클래스 생성(ex. viewModel)
  • 라이브 데이터를 적용할 UI 생성(xml, fragment, activity)

입니다.

 

class SharedViewModel(application: Application): AndroidViewModel(application){
    private val _liveData = MutableLiveData<String>()
    val liveData: LiveData<String> get() = _liveData
    //MutableLiveData는 get/set이 가능
    //LiveData는 get만 사용 가능
}

 

 먼저 viewModel을 통해 라이브데이터를 생성합니다. 그냥

private val liveData = MutableLiveData<String>()

만 사용해도 문제가 없으나 저런식으로 구별을 지어놓은 것은 viewModel과 view에서의 사용을 명확하게 구분짓기 위해서라고 생각하면 편할 것입니다.

 여러가지 디자인 패턴의 공통점은 데이터는 항상 view 외부에서 처리가 된다는 것입니다. 이 코드에서는 viewModel에서 처리가 되는 것이고, 그렇다면 view에서는 굳이 set 메소드를 가질 이유가 없죠. 여럿이서 작업을 하는 경우에는 더더욱 set메소드가 없는 것이 패턴을 깨뜨릴 위험을 줄일 수 있습니다.

 

 결론적으로 위와 아래 방식 모두 변수 선언 방식에는 문제가 없으나 위의 경우처럼 선언하는 경우는 view에서는 liveData를, viewModel에서는 _liveData를 이용해 데이터를 이용한다고 생각하면 되겠습니다.

 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="sharedVM"
            type="packageName.viewmodel.SharedViewModel" />
    </data>
    
            <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="4dp"
            android:orientation="horizontal">

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingTop="4dp"
                android:paddingBottom="4dp"
                android:paddingLeft="8dp"
                android:paddingRight="8dp"
                android:text="@{sharedVM.liveData}"
                android:textSize="16dp"/>

        </androidx.appcompat.widget.LinearLayoutCompat>
    
</layout>

 

 xml을 통해 데이터를 바인딩해줍니다. 이 방식 말고 물론 fragment 및 activity에서

sharedViewModel.liveData.observe(this, Observer{ newName ->
        textView.text = newName
    }
})

이런 형태를 통해 직접 데이터를 조작할 수도 있습니다. 간단한 조작이 아니라 데이터의 변경에 따라 텍스트 색상, 텍스트 등등 변경할 것이 여러가지라면 observe를 이용하는 것이 더 효율적일 수 있습니다.

 

//fragment
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    binding = DataBindingUtil.inflate(
            layoutInflater,     
            layoutId,
            null,
            false
    )
    binding.lifecycleOwner = this
    binding.sharedVM = sharedViewModel
    return binding.root
}


//activity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, layoutId)
    binding.sharedVM = sharedViewModel
}

 

 각 프래그먼트에서는 onCreateView에서, 액티비티에서는 onCreate에서 뷰모델을 바인딩해 직접 xml로 넣어줄 수 있습니다. 뿐만 아니라 위에서 설명했던 observe도 UI 클래스 내부에서 다뤄줄 수 있습니다.

 

 이 라이브 데이터에는 가장 큰 주의점이 하나 있습니다. 라이브데이터는 생성 자체가 라이브데이터클래스 내부의 값이기 때문에 사용할 때 sharedViewModel.liveData.value를 통해 사용해야 한다는 점입니다.

 

 이를 그냥 String, int 등의 변수처럼 사용하게 되면 코틀린에서는 nullable한 문제가 있다고 syntax 에러를 주게 되어 사용할 수 없게 되는 경우가 있습니다. 그래서 그 부분에 유의하는 것이 중요합니다.

 


 기본적으로 LiveData는 jetpack에서 소개되는 여러가지 자료들과 같이 사용해야 제대로 사용할 수 있습니다. 이는 jetpack 라이브러리의 큰 특징인데요. 그래서 이전에 사용하던 프로젝트에 jetpack을 적용할 때는 하나씩 적용하는 것이 아니라 조금 복잡하더라도 한 번에 적용을 하는 것을 추천드립니다.

 

반응형

댓글