본문 바로가기

Android/Android의 모든 것

👋Android의 모든 것 : 1️⃣MVVM을 위한 Databinding, LiveData 개념과 예제

728x90

https://goni95.tistory.com/173

 

👋Android의 모든 것 | Droid Knights 2021

목  차 예제 Android 👋Android의 모든 것 : 1️⃣Android, 2️⃣Android SDK, 3️⃣Platform Architecture 👋Android의 모든 것 개념 : 1️⃣App Manifest, 2️⃣Android Context, 3️⃣App Components 1 O 👋An..

goni95.tistory.com

 

 

Android에서 프로젝트를 MVVM 아키텍처 패턴으로 설계하기 위해 알아보면 ViewModel이 View에 대한 의존성을 갖지 않고 느슨하게 연결되도록 Databinding 라이브러리가 필수적으로 사용된다는 말과 함께 ViewModel이 데이터의 변경을 알리면 View가 이를 관찰하고 있다 UI를 업데이트 하도록 LiveData, Rx, Coroutine Flow 등이 언급됩니다.

 

Databinding과 LiveData 모두 Jetpack의 아키텍처(Architecture) 카테고리에 포함됩니다.

 

1️⃣ Databinding(데이터 바인딩)

데이터 바인딩은 Android 4.0 (API Level : 14) 이상 안드로이드 기기부터 지원합니다.

데이터 바인딩을 사용하려면 안드로이드 스튜디오 1.5.0 이상의 버전을 사용해야 하며, 최신의 데이터 바인딩 라이브러리를 사용하려면 안드로이드 스튜디오와 SDK 버전을 최신 상태로 유지하는 것이 좋습니다.

 

데이터 바인딩은 선언적 형식으로 레이아웃의 UI 구성 요소를 어플리케이션의 데이터와 결합할 수 있는 라이브러리입니다.

 

데이터 바인딩을 통해 boilerplate code를 줄이고 성능 향상과 메모리 누수 및 Null Pointer Exception을 방지 그리고 MVVM 아키텍처 패턴 설계를 위해 사용해 테스트 용이성, 유지보수성을 높일 수 있지만 클래스 파일 수가 증가하고 빌드 속도가 느려질 수 있는 단점이 있습니다.

 

기존에는 TextView에 ViewModel로 부터 title이라는 문자열을 프로퍼티를 참조하여 변경해야 했습니다.

val tileTv : TextView = findViewById(R.id.titleTextView)
titleTv.text = viewModel.title

 

데이터 바인딩을 사용하면 레이아웃 파일에서 직접 데이터 바인딩 표현식을 사용해 선언형 프로그래밍 코드를 작성할 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<layout  
    ...>
    
    <data>
        <variable
            name="viewModel"
            type="sang.gondroid.databindingexample.MainViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        ...>

        <TextView
            android:id="@+id/TitleTextView"
            ...
            android:text="@{viewModel.title}"
            ... />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

 

1. 데이터 바인딩 설정

모듈 수준의 build.gradle에 추가합니다.

// 1. 안드로이드 스튜디오 4.0 미만인 경우 
android {
    ...
    buildFeatures {
        dataBinding = true
    }
}


// 2. 안드로이드 스튜디오 4.0 이상인 경우
plugins {
    ...
    id 'kotlin-kapt'    // databinding [you should apply the kotlin-kapt plugin. / bindingAdapter]
}

android {
    ...
    buildFeatures {
        dataBinding = true
    }
}

 

 

2. 바인딩 클래스 생성

데이터 바인딩은 레이아웃의 변수와 뷰를 참조할 수 있는 바인딩 클래스를 생성하며 바인딩 클래스는 ViewDataBinding을 상속합니다.

 

xml 레이아웃 파일의 상위 레이아웃을 태그로 감싸면 자동으로 바인딩 클래스가 생성되는데 바인딩 클래스 이름은 레이아웃 파일명을 파스칼 케이스로 변경한 뒤 Binding 접미어를 붙여 생성됩니다.

<?xml version="1.0" encoding="utf-8"?>
<layout>
    
    <androidx.constraintlayout.widget.ConstraintLayout
        ...>
        
        ...

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

tip : 레이아웃에 대한 표현은 ***Binding 클래스로 작성되지만 실제 비즈니스 로직 추적 또는 디버깅을 하려면 ***BindingImpl을 참조해야 합니다.

 

 

3. 바인딩 클래스로 바인딩 객체 생성하기

DataBindingUtil 클래스의 메서드를 활용해 바인딩 객체 생성

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.lifecycleOwner = this
    }
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
saveInstanceState : Bundle?) : View? {
   
    binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
    binding.lifecycleOwner = this
    return binding.root
}

 

inflate() 메서드를 사용하여 레이아웃 전개와 함께 바인딩 객체를 생성하는 방법

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.lifecycleOwner = this
    }
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
saveInstanceState : Bundle?) : View? {
   
    binding = MainFragmentBinding.inflate(inflater, container, false)
    // binding = MainFragmentBinding.inflate(inflater)
    // binding = MainFragmentBinding.inflate(layoutInflater)
    binding.lifecycleOwner = this
    return binding.root
}

*전개 : xml 레이아웃 파일과 자식 View의 속성을 읽어 실제 View 객체를 생성하는 동작을 의미합니다.

 

 

4. 바인딩 클래스 이름 정의

기본적으로 바인딩 클래스 이름은 레이아웃 파일명을 바탕으로 생성되지만 이를 변경하고 싶은 경우 <data> 태그 내에 class 속성을 사용하여 변경하면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<layout
    ... >

    <data class="MainActivityBindingClass"/>

    <androidx.constraintlayout.widget.ConstraintLayout
        ... >

        ...

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class MainActivity : AppCompatActivity() {
    private lateinit var binding: MainActivityBindingClass

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.lifecycleOwner = this
    }
}

 

 

4. ID로 View 참조

바인딩 클래스 내부에서 미리 findViewByID()를 호출한 결과를 캐싱해 두기 때문에 바인딩 클래스를 사용하면 findViewById()를 호출할 필요가 없습니다.

<?xml version="1.0" encoding="utf-8"?>
<layout
    ... >

    <data class="MainActivityBindingClass"/>

    <androidx.constraintlayout.widget.ConstraintLayout
        ... >

        <TextView
            android:id="@+id/titleTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.lifecycleOwner = this

        binding.titleTextView.text = "안녕"
    }
}

 

 

5. 레이아웃 변수 선언과 클래스 참조

레이아웃에 변수를 선언하고 변수에 값을 대입하는 것으로 View의 상태를 변경할 수 있습니다.

변수를 선언하기 위해선 <layout> 태그 내의 <data> 태그를 사용하며 <data> 태그 내에서 변수를 <variable> 태그를 사용해 선언하면 됩니다.

<variable> 태그는 변수의 이름을 정의하는 name과 변수의 자료형을 정의하는 type 두 속성을 갖습니다.

 

참조하고 싶은 클래스를 레이아웃 파일에 불러오려면 <import> 태그를 사용해 선언하며 자료형을 정의하는 type 속성을 갖습니다.

만약 이름이 동일한 서로 다른 클래스를 불러오는 경우 alias 속성을 통해 레이아웃 내에서 class 명 대신 사용할 이름을 설정하면 됩니다.

 

ViewModel을 사용해 View의 상태를 변경하는 예제입니다.

<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewModel"
            type="sang.gondroid.databindingexample.MainViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/titleTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.title}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: MainViewModel
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.lifecycleOwner = this

        viewModel = ViewModelProvider(this) [MainViewModel::class.java]
        binding.viewModel = viewModel
    }
}
class MainViewModel : ViewModel() {
    private val _title = MutableLiveData<String>()
    val title : LiveData<String>
        get() = _title

    init {
        viewModelScope.launch {
            for (i in 0.. 1000) {
                delay(1000)
                _title.value = "타이틀 $i"
            }
        }
    }
}

 

6. 바인딩 표현식

xml 레이아웃에서 사용할 수 있는 연산자, Collections 클래스를 사용한 표현식을 알아보겠습니다.

중요한 점은 바인딩 표현식 작성 시 <는 &lt; 로 입력해야 합니다.

  • 산술 연산자 : +  -  /  *  %
  • 문자열 연결 : +
  • 논리 연산자 : &&  ||
  • 비트 연산자 : &  |  ^
  • 단항 연산자 : +  -  !  ~
  • 비트 이동 연산자 : >>  >>>  <<
  • 비교 연산자 : == >  <  >=  <=
  • Null 병합 연산자 : ??
<data>
    <variable
        name="viewModel"
        type="sang.gondroid.databindingexample.MainViewModel" />

    <import type="android.view.View"/>
    <import type="sang.gondroid.databindingexample.View" alias="MyView"/>
 
    <variable
        name="index"
        type="Integer" /> 
    <import type="java.util.List"/>
    <variable
        name="list"
        type="List&lt;String>" />

    <import type="java.util.Map"/>
    <variable
        name="map"
        type="Map&lt;String, String>" />
</data>

android:text="@{String.valueOf(viewModel.index + 1}"

// import 태그를 통해 View 클래스를 참조
android:visibility="@{viewModel.num > 10 ? View.GONE : View.VISIBLE}"
android:text='@{"name : " + viewModel.name}'
android:text="@{viewModel.name ?? viewModel.nullValue}"

<!-- 
1. string 리소스 참조하기(strings.xml)
   <string name="nameFormat">%s %s</string>
   <string name="nameFormat">%2$s %1$s</string>    // 매개변수 순서 변경 가능
-->
android:text="@{@string/nameFormat(firstName, lastName)}"

android:text="@{list[index]}"
android:text="@{list[0]}"

android:text='@{map["ad"]}'
android:text="@{map[`ad`]}"

 

2️⃣ LiveData

 

 

 

3️⃣