본문 바로가기

모바일(Mobile)/안드로이드(Android)

[Android] 데이터 바인딩과 뷰모델(DataBinding & ViewModel)

데이터 바인딩을 하는 이유


 

지난 포스트에서는 데이터 바인딩(DataBinding)의 가장 간단한 형식에 대해서 알아보았습니다.

 

https://1-hee.tistory.com/87

 

[Android] 데이터 바인딩(DataBinding)

데이터 바인딩(DataBinding)이란? 안드로이드 스튜디오 개발자 사이트에서 정의한 표현을 인용하자면 다음과 같이 정의할 수 있습니다. 프로그래매틱 방식이 아니라 선언적 형식으로 레이아웃의 UI

1-hee.tistory.com

 




데이터 바인딩은 더 이상 개발자가 xml로 설계한 화면에 대하여

일일히 findViewById() 메소드를 통해서 뷰 바인딩을 해주지 않아도 된다는 이점을 가집니다.

 

처음 안드로이드 개발을 배우는 상황에서는 명시적으로

뷰를 Id를 통해 찾아내고 뷰 바인딩을 직접하는 것이 이해하기 더 쉬울 수 있습니다.

하지만, 간단한 샘플 앱에서는 몇 개의 레이아웃만 처리해주면 되므로 금방할 수 있겠지만,
실제 시장에 출시한 제품을 만들고자 한다면 몇 개의 화면과 인터페이스로는(버튼, 텍스트 뷰 등..) 턱 없이 부족할 것이며,
소위 '엔터프라이즈'급의 서비스를 만들고자 한다면 프로그래밍은 더이상 설계가 아니라 고된 노동이 될 것입니다.

또한, 이러한 반복적인 작업은 사람보다 컴퓨터가 더 잘하는 일이고 
그렇기에 안드로이드에서는 Dagger라는 도구를 통해 컴파일 시에 구현체 클래스(xxxImpl)를 만들게 됩니다.
이러한 구현체 클래스들은 안드로이드 스튜디오에서 앱을 실행한 후 generated로 표시된 디렉토리에서 코드를 확인해볼 수도 있습니다.

Dagger에 대해서는 다음 포스팅에서 더 자세히 다루도록 하겠습니다.

데이터 바인딩(Databinding)은 이러한 반복적인 작업을 줄여주고 개발자가 비즈니스 로직과 서비스 설계 등 
시간을 더 많이 투자해야 할 부분에 집중할 수 있도록 해줍니다. 

개인적으로 개발자로서 일을 '잘한다'라는 것이 무엇일까에 대해 정의를 내려보자면,
고객의 요구사항을 만족하며 안정성을 확보하고 세심하게 디테일을 챙기는 것이라고 생각합니다.

그리고 이러한 부분들을 고려하며 개발을 하기 위해서는 시간의 '투자'가 많이 필요하고,

개발 과정에서 반복적이거나 불필요한 작업에 대해 시간을 줄이는 것이 꼭 필요합니다.

 

왜냐하면, 개발자에게 시간 또한 개발에 사용하는 '비용'이기 때문입니다.

따라서, 데이터 바인딩은 이러한 측면에서 개발자에게 시간을 절약하게 해주어
다른 중요한 일에 집중할 수 있도록 하여 결과적으로 생산성을 확보해줄 수 있는 좋은 도구입니다.

 

 

뷰 모델(ViewModel)이란?



뷰 모델(ViewModel)의 의의는 간단하게 요약하자면 화면에 표시할 데이터에 대한 처리를 전담하는 클래스 라고 정의할 수 있습니다.

뷰 모델(ViewModel) 은 아래와 같이 앱 화면에 표시할 데이터의 처리와 관리를 전담하여 
더 효율적으로 사용자에게 데이터를 표시하고 프로그램에서 처리할 수 있도록 도와줍니다.

 

fig 1.0. 안드로이드 뷰(View)와 뷰모델(ViewModel)의 관계

 




왜 뷰 모델(ViewModel)을 사용해야하나요?


안드로이드에서는 앱 화면을 구성하고 사용자에게 제공할 기능을 구현하기 위해서 주로 액티비티 클래스를 사용합니다.

액티비티 클래스 외에도 이외에도 다양한 기능을 제공하기 위해 다양한 클래스를 사용할 수 있는데,
그 중에서 안드로이드 앱을 완성하기 위해 사용할 수 있는 대표적인 4가지 클래스들을 컴포넌트 클래스라고 부릅니다.

컴포넌트 클래스들에 대해서는 아래의 포스트에서 설명해두었으니 참고해보시면 좋을 것 같습니다.

 

https://1-hee.tistory.com/49

 

[Android] 안드로이드와 컴포넌트 (안드로이드를 구성하는 원소들)

안드로이드와 컴포넌트 안드로이드 앱 개발의 핵심은 컴포넌트입니다. 안드로이드에서 동작하는 프로그램을 보통 APP 또는 어플리케이션이라고 칭합니다. 이러한 안드로이드 앱은 컴포넌트로

1-hee.tistory.com



이러한 컴포넌트 클래스들(eg. 액티비티)은 스마트폰과 같은 모바일 환경에서 자원의 낭비를 줄이고 
시스템을 효율적으로 사용하기 위해 라이프 사이클에 따라 자원을 할당하거나 해제합니다.

안드로이드 앱에서 사용자에게 보여지는 화면을 만드는 작업
액티비티 클래스의 onCreate()와 같은 라이프 사이클 메서드에서 수행할 수 있고,
이 과정에서 새롭게 처리된 데이터를 UI와 함께 표시하게 할 수도 있습니다.

하지만, 이렇게 하나의 클래스(=액티비티)에서 이렇게 많은 작업을 전담하게 하면
객체지향 프로그래밍 관점에서 보았을 때 잘 만들어진 프로그램이라 보기 어렵습니다.
왜냐하면, 프로그램이란 새롭게 개발할 수도 있지만, 다른 개발자에 의해 소스코드가 분석되고 유지 보수 되어야 할 경우도 많기 때문입니다.

따라서, 안드로이드에서는 UI에 표시할 데이터에 대한 일련의 '작업'을 처리하기 위한 매개체로서 '뷰모델(ViewModel)'라는 도구를 제공하는 것입니다.


그리고 이러한 도구를 사용하는 것은 개발자기 직접 모든 자원을 관리하는 것보다 더 효율적으로 자원을 관리할 수 있도록 도와줍니다.

뷰모델에 대한 더 자세한 설명은 아래의 안드로이드 스튜디오 공식 문서에서 더 확인하실 수 있습니다.

 

https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko

 

ViewModel 개요  |  Android 개발자  |  Android Developers

ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.

developer.android.com

 


 

 

데이터 바인딩(DataBinding)과 뷰모델(ViewModel) 사용하기


 

안드로이드 앱 개발에서 데이터 바인딩(DataBinding)뷰모델(ViewModel)은 
화면의 설계와 뷰 바인딩을 간편하게 처리하고 (with 데이터 바인딩),
화면에 표시할 데이터를 안전하게 관리하는 데 활용할 수 있습니다 (with 뷰모델)

그러므로 뷰모델에 대하여 쉽고 간편하게 이해하기 위해서 뷰모델(ViewModel)을 사용하면
화면을 재구성하더라도 데이터가 보전되는지 확인하는 간단한 앱을 만들어보도록 하겠습니다.

 

 

1. 안드로이드 스튜디오를 통해 프로젝트를 생성합니다. 

 

fig 1.1 안드로이드 스튜디오를 통해 프로젝트를 생성한 직후의 모습

 

 


2. 데이터 바인딩 옵션 활성화 및 androidx.lifecycle 도구 의존성 추가

 

먼저 앱 수준의 gradle 파일에 데이터 바인딩 옵션을 추가합니다.

android {

	( ... 생략 ... )

    buildFeatures {
        dataBinding true
    }
}

 

 


다음으로 뷰모델(ViewModel)을 사용하기 위해 androidx.lifecycle의 하위 구성요소인

뷰모델(ViewModel)과 라이브 데이터(LiveData) 의존성을 추가합니다.

 

dependencies {

    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.10.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    // for viewmodel
    def version = "2.5.1"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$version"

}

 

 

💡 여기서 잠깐!


안드로이드 스튜디오에서는 프로젝트 생성 시,

개발자가 많이 또는 반드시 사용하는 기본 라이브러리에 대한 의존성을 자동으로 추가해 줍니다.

 

이 과정에서 기본적으로 추가해주는 라이브러리 중에 대표적인 것이 

androidx.core 나 androidx.appcompat 이 있습니다.
이 두 가지 라이브러리의 경우 내부적으로 ViewModel에 대한 의존성이 포함되어 있습니다.

그래서 안드로이드 스튜디오를 통해 프로젝트를 처음 생성하고 의존성을 추가하지 않았더라도,
뷰모델(ViewModel)을 상속받는 클래스를 작성하면,

별도의 의존성 추가 없이도 참조가 가능하고 import 구문이 추가가 됩니다.

이는 안드로이드 프로젝트를 생성 하면서

기본적으로 추가된 라이브러리에서 내부적으로 의존성을 포함하기에 가능한 것입니다.

그러나, 본 포스팅에서는 안드로이드 lifecylce의 라이브러리의 구성요소인 
뷰모델(ViewModel)과 라이브 데이터(Livedata)를 사용할 것이므로 
위와 같이 직접 의존성을 추가하도록 하겠습니다.

 

 

3. 앱 화면에 간단하게 Button과 TextView의 레이아웃 쌍 두 개 추가합니다. (대조를 위함!)

 

fig 1.2. 메인 액티비티의 레이아웃 구성

 

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

    <data>
        <variable
            name="vm"
            type="com.example.databindingviewmodel.viewmodel.TextViewModel" />
    </data>

    <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/tv_control"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="@id/btn_state_data"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/btn_state_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="데이터 추가하기2"
            app:layout_constraintBottom_toBottomOf="@id/tv_display"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@id/tv_control"
            />


        <TextView
            android:id="@+id/tv_display"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{vm.inputData}"
            app:layout_constraintBottom_toBottomOf="@id/btn_save_data"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@id/btn_state_data" />
        <Button
            android:id="@+id/btn_save_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="데이터 추가하기1"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@id/tv_display"
            android:onClick="@{()->vm.addCharacter()}"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

 

이때, 데이터 바인딩을 활성화 했으므로

메인 액티비티의 레이아웃 xml에 <layout/>  태그를 통해 데이터 바인딩을 할 수 있도록 감싸 줍니다.

 

4. 뷰모델의 생성 

 

다음과 같이 ViewModel 클래스를 상속받는 뷰모델을 작성합니다.

 

package com.example.databindingviewmodel.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class TextViewModel: ViewModel() {
    
    private var _inputData = MutableLiveData<String>()
    
    val inputData:LiveData<String> 
        get() = _inputData
    
    init {
        _inputData.value = "RESULT=";
    }
    fun addCharacter(){
        _inputData.value+="A"; // A 계속 추가
    }


}

 


5. 메인 액티비티에서 화면 구성 및 기능 완성

 

그 다음 메인 액티비티에서 데이터 바인딩(DataBinding)을 통해 화면을 구성하고 
뷰모델(ViewModel)을 메인 액티비티가 소유하게 하여 액티비티의 라이프 사이클에 따라 관리될 수 있도록 합니다.

 

package com.example.databindingviewmodel

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.databindingviewmodel.databinding.ActivityMainBinding
import com.example.databindingviewmodel.viewmodel.TextViewModel

class MainActivity : AppCompatActivity() {

    private lateinit var bind:ActivityMainBinding;
    private lateinit var viewModel:TextViewModel;
    private lateinit var control:String;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        bind = DataBindingUtil.setContentView(this, R.layout.activity_main);
        viewModel = ViewModelProvider(this)[TextViewModel::class.java];
        bind.vm = viewModel; //

        viewModel.inputData.observe(this, Observer { it
            -> bind.tvDisplay.text = it;
        });

        // 대조를 위해 값 추가
        control = "";
        val prefix:String  = "RESULT=";
        bind.tvControl.text = prefix + control;

        bind.btnStateData.setOnClickListener {
            control += "A";
            bind.tvControl.text = prefix + control
        }

    }
}

 

 

이 때, 대조를 위해 데이터 바인딩과 뷰모델을 적용하지 않은 레이아웃의 이벤트 리스너도 설정해줍니다.

 

6. 앱에서 데이터가 잘 표시되는지 확인하면 완성!

fig 1.3. 뷰모델과 적용 결과

 


 

📑 참고한 사이트