Android Kotlin 기초 02.4: 데이터 결합 기본사항

이 Codelab은 Android Kotlin 기초 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다. 모든 과정 Codelab은 Android Kotlin 기본사항 Codelab 방문 페이지에 나열되어 있습니다.

소개

이 과정의 이전 Codelab에서는 findViewById() 함수를 사용하여 뷰 참조를 가져왔습니다. 앱에 복잡한 뷰 계층 구조가 있으면 Android가 원하는 뷰를 찾을 때까지 루트에서 시작하여 뷰 계층 구조를 탐색하므로 findViewById()가 비싸고 앱 속도가 느려집니다. 다행히 더 나은 방법이 있습니다.

뷰에 데이터를 설정하기 위해 문자열 리소스를 사용하고 활동에서 데이터를 설정했습니다. 뷰가 데이터를 알고 있으면 더 효율적입니다. 다행히도 이 작업은 가능합니다.

이 Codelab에서는 데이터 결합을 사용하여 findViewById()의 필요성을 없애는 방법을 알아봅니다. 데이터 결합을 사용하여 뷰에서 직접 데이터에 액세스하는 방법도 알아봅니다.

기본 요건

다음을 잘 알고 있어야 합니다.

  • 활동의 정의와 onCreate()에서 레이아웃으로 활동을 설정하는 방법을 알아야 합니다.
  • 텍스트 뷰를 만들고 텍스트 뷰에 표시되는 텍스트를 설정합니다.
  • findViewById()를 사용하여 뷰 참조 가져오기
  • 뷰의 기본 XML 레이아웃을 만들고 수정합니다.

학습할 내용

  • 데이터 결합 라이브러리를 사용하여 findViewById()에 대한 비효율적인 호출을 제거하는 방법
  • XML에서 앱 데이터에 직접 액세스하는 방법

실습할 내용

  • findViewById() 대신 데이터 결합을 사용하고 레이아웃 XML 파일에서 직접 데이터에 액세스하도록 앱을 수정합니다.

이 Codelab에서는 AboutMe 앱으로 시작하여 데이터 결합을 사용하도록 앱을 변경합니다. 완료되면 앱은 정확히 동일하게 표시됩니다.

AboutMe 앱의 기능은 다음과 같습니다.

  • 사용자가 앱을 열면 앱에 이름, 닉네임을 입력하는 필드, 완료 버튼, 별 이미지, 스크롤 가능한 텍스트가 표시됩니다.
  • 사용자는 닉네임을 입력하고 완료 버튼을 탭할 수 있습니다. 수정 가능한 필드와 버튼이 입력된 닉네임을 표시하는 텍스트 뷰로 대체됩니다.


이전 Codelab에서 빌드한 코드를 사용하거나 GitHub에서 AboutMeDataBinding-Starter 코드를 다운로드할 수 있습니다.

이전 Codelab에서 작성한 코드는 findViewById() 함수를 사용하여 뷰 참조를 가져옵니다.

뷰가 생성되거나 다시 생성된 후 findViewById()를 사용하여 뷰를 검색할 때마다 Android 시스템은 런타임에 뷰 계층 구조를 순회하여 뷰를 찾습니다. 앱에 뷰가 몇 개만 있는 경우에는 문제가 되지 않습니다. 하지만 프로덕션 앱에는 레이아웃에 수십 개의 뷰가 있을 수 있으며, 최적의 디자인을 사용하더라도 중첩된 뷰가 있을 수 있습니다.

텍스트 뷰가 포함된 스크롤 뷰가 포함된 선형 레이아웃을 생각해 보세요. 크거나 깊은 뷰 계층 구조의 경우 뷰를 찾는 데 시간이 오래 걸려 사용자의 앱 속도가 눈에 띄게 느려질 수 있습니다. 변수에 뷰를 캐싱하면 도움이 될 수 있지만 각 네임스페이스에서 각 뷰의 변수를 초기화해야 합니다. 뷰가 많고 활동이 여러 개이면 이 비용도 합산됩니다.

한 가지 해결책은 각 뷰에 대한 참조를 포함하는 객체를 만드는 것입니다. Binding 객체라고 하는 이 객체는 전체 앱에서 사용할 수 있습니다. 이 기법을 데이터 결합이라고 합니다. 앱의 바인딩 객체가 생성되면 뷰 계층 구조를 탐색하거나 데이터를 검색하지 않고도 바인딩 객체를 통해 뷰와 기타 데이터에 액세스할 수 있습니다.

데이터 바인딩에는 다음과 같은 이점이 있습니다.

  • 코드가 findByView()를 사용하는 코드보다 더 짧고 읽기 쉬우며 유지관리하기 쉽습니다.
  • 데이터와 뷰가 명확하게 분리되어 있습니다. 데이터 바인딩의 이점은 이 과정의 후반부에서 점점 더 중요해집니다.
  • Android 시스템은 각 뷰를 가져오기 위해 뷰 계층 구조를 한 번만 순회하며, 이는 사용자가 앱과 상호작용하는 런타임이 아닌 앱 시작 중에 발생합니다.
  • 뷰 액세스를 위한 타입 안전성을 얻을 수 있습니다. (유형 안전성은 컴파일러가 컴파일 중에 유형을 확인하고 개발자가 잘못된 유형을 변수에 할당하려고 하면 오류를 발생시킨다는 의미입니다.)

이 작업에서는 데이터 결합을 설정하고 데이터 결합을 사용하여 findViewById() 호출을 바인딩 객체 호출로 대체합니다.

1단계: 데이터 결합 사용 설정

데이터 바인딩은 기본적으로 사용 설정되어 있지 않으므로 데이터 바인딩을 사용하려면 Gradle 파일에서 데이터 바인딩을 사용 설정해야 합니다. 데이터 바인딩은 컴파일 시간을 늘리고 앱 시작 시간에 영향을 줄 수 있기 때문입니다.

  1. 이전 Codelab의 AboutMe 앱이 없는 경우 GitHub에서 AboutMeDataBinding-Starter 코드를 가져옵니다. Android 스튜디오에서 엽니다.
  2. build.gradle (Module: app) 파일을 엽니다.
  3. android 섹션 내에서 닫는 중괄호 앞에 dataBinding 섹션을 추가하고 enabledtrue로 설정합니다.
dataBinding {
    enabled = true
}
  1. 메시지가 표시되면 프로젝트를 동기화합니다. 메시지가 표시되지 않으면 File > Sync Project with Gradle Files를 선택합니다.
  2. 앱을 실행할 수는 있지만 변경사항은 표시되지 않습니다.

2단계: 데이터 결합과 함께 사용할 수 있도록 레이아웃 파일 변경

데이터 바인딩을 사용하려면 XML 레이아웃을 <layout> 태그로 래핑해야 합니다. 이렇게 하면 루트 클래스가 더 이상 뷰 그룹이 아니라 뷰 그룹과 뷰를 포함하는 레이아웃이 됩니다. 그러면 바인딩 객체가 레이아웃과 레이아웃의 뷰를 알 수 있습니다.

  1. activity_main.xml 파일을 엽니다.
  2. 텍스트 탭으로 전환합니다.
  3. <LinearLayout> 주위에 가장 바깥쪽 태그로 <layout></layout>를 추가합니다.
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. Code > Reformat code를 선택하여 코드 들여쓰기를 수정합니다.

    레이아웃의 네임스페이스 선언은 가장 바깥쪽 태그에 있어야 합니다.
  1. <LinearLayout>에서 네임스페이스 선언을 잘라내어 <layout> 태그에 붙여넣습니다. 여는 <layout> 태그는 아래와 같이 표시되어야 하며 <LinearLayout> 태그에는 뷰 속성만 포함되어야 합니다.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. 앱을 빌드하고 실행하여 올바르게 수행했는지 확인합니다.

3단계: 기본 활동에서 바인딩 객체 만들기

바인딩 객체에 대한 참조를 기본 활동에 추가하여 뷰에 액세스할 수 있도록 합니다.

  1. MainActivity.kt 파일을 엽니다.
  2. onCreate() 앞에 최상위 수준에서 바인딩 객체의 변수를 만듭니다. 이 변수는 일반적으로 binding라고 합니다.

    binding의 유형인 ActivityMainBinding 클래스는 이 기본 활동을 위해 컴파일러에 의해 특별히 생성됩니다. 이름은 레이아웃 파일 이름(activity_main + Binding)에서 파생됩니다.
private lateinit var binding: ActivityMainBinding
  1. Android 스튜디오에서 메시지가 표시되면 ActivityMainBinding를 가져옵니다. 메시지가 표시되지 않으면 ActivityMainBinding를 클릭하고 Alt+Enter (Mac의 경우 Option+Enter)를 눌러 누락된 클래스를 가져옵니다. (단축키에 관한 자세한 내용은 단축키를 참고하세요.)

    import 문은 아래와 비슷해야 합니다.
import com.example.android.aboutme.databinding.ActivityMainBinding

다음으로 현재 setContentView() 함수를 다음 작업을 실행하는 명령어로 바꿉니다.

  • 바인딩 객체를 만듭니다.
  • DataBindingUtil 클래스의 setContentView() 함수를 사용하여 activity_main 레이아웃을 MainActivity와 연결합니다. 이 setContentView() 함수는 뷰의 데이터 바인딩 설정도 처리합니다.
  1. onCreate()에서 setContentView() 호출을 다음 코드 줄로 바꿉니다.
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. DataBindingUtil를 가져옵니다.
import androidx.databinding.DataBindingUtil

4단계: 바인딩 객체를 사용하여 findViewById()에 대한 모든 호출 바꾸기

이제 findViewById()에 대한 모든 호출을 바인딩 객체에 있는 뷰에 대한 참조로 바꿀 수 있습니다. 바인딩 객체가 생성되면 컴파일러는 레이아웃의 뷰 ID에서 바인딩 객체의 뷰 이름을 생성하여 카멜 표기법으로 변환합니다. 따라서 예를 들어 done_button는 바인딩 객체에서 doneButton가 되고 nickname_editnicknameEdit가 되며 nickname_textnicknameText가 됩니다.

  1. onCreate()에서 findViewById()을 사용하여 done_button를 찾는 코드를 바인딩 객체의 버튼을 참조하는 코드로 바꿉니다.

    이 코드를 다음 코드로 대체합니다. findViewById<Button>(R.id.done_button)
    binding.doneButton

    onCreate()에서 클릭 리스너를 설정하는 완성된 코드는 다음과 같습니다.
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. addNickname() 함수의 모든 findViewById() 호출에 대해서도 동일한 작업을 실행합니다.
    findViewById<View>(R.id.id_view)의 모든 일치하는 항목을 binding.idView로 바꿉니다. 다음과 같은 방법으로 이 작업을 수행하세요.
  • editTextnicknameTextView 변수의 정의와 findViewById() 호출을 삭제합니다. 이렇게 하면 오류가 발생합니다.
  • (삭제된) 변수 대신 binding 객체에서 nicknameText, nicknameEdit, doneButton 뷰를 가져와 오류를 수정합니다.
  • view.visibilitybinding.doneButton.visibility로 바꿉니다. 전달된 view 대신 binding.doneButton를 사용하면 코드가 더 일관됩니다.

    결과는 다음 코드입니다.
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE
  • 기능 변경사항은 없습니다. 이제 선택적으로 view 매개변수를 삭제하고 이 함수 내에서 binding.doneButton를 사용하도록 view의 모든 사용을 업데이트할 수 있습니다.
  1. nicknameText에는 String이 필요하고 nicknameEdit.textEditable입니다. 데이터 바인딩을 사용하는 경우 EditableString로 명시적으로 변환해야 합니다.
binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. 회색으로 표시된 가져오기를 삭제할 수 있습니다.
  2. apply{}를 사용하여 함수를 Kotlin화합니다.
binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. 앱을 빌드하고 실행하면 이전과 똑같이 표시되고 작동합니다.

데이터 바인딩을 활용하여 데이터 클래스를 뷰에서 직접 사용할 수 있습니다. 이 기법은 코드를 간소화하며, 더 복잡한 사례를 처리하는 데 매우 유용합니다.

이 예에서는 문자열 리소스를 사용하여 이름과 닉네임을 설정하는 대신 이름과 닉네임의 데이터 클래스를 만듭니다. 데이터 결합을 사용하여 데이터 클래스를 뷰에서 사용할 수 있도록 합니다.

1단계: MyName 데이터 클래스 만들기

  1. Android 스튜디오의 java 디렉터리에서 MyName.kt 파일을 엽니다. 이 파일이 없는 경우 새 Kotlin 파일을 만들고 이름을 MyName.kt로 지정합니다.
  2. 이름과 닉네임의 데이터 클래스를 정의합니다. 빈 문자열을 기본값으로 사용합니다.
data class MyName(var name: String = "", var nickname: String = "")

2단계: 레이아웃에 데이터 추가

activity_main.xml 파일에서 이름은 현재 문자열 리소스의 TextView에 설정되어 있습니다. 이름에 대한 참조를 데이터 클래스의 데이터에 대한 참조로 바꿔야 합니다.

  1. 텍스트 탭에서 activity_main.xml을 엽니다.
  2. 레이아웃 상단의 <layout><LinearLayout> 태그 사이에 <data></data> 태그를 삽입합니다. 여기에서 뷰를 데이터와 연결합니다.
<data>
  
</data>

데이터 태그 내에서 클래스 참조를 보유하는 명명된 변수를 선언할 수 있습니다.

  1. <data> 태그 내에 <variable> 태그를 추가합니다.
  2. name 매개변수를 추가하여 변수에 "myName"이라는 이름을 지정합니다. type 매개변수를 추가하고 유형을 MyName 데이터 클래스의 정규화된 이름 (패키지 이름 + 변수 이름)으로 설정합니다.
<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

이제 이름에 문자열 리소스를 사용하는 대신 myName 변수를 참조할 수 있습니다.

  1. android:text="@string/name"를 아래 코드로 바꿉니다.

@={}는 중괄호 안에 참조된 데이터를 가져오는 지시어입니다.

myName는 이전에 정의한 myName 변수를 참조하며, 이 변수는 myName 데이터 클래스를 가리키고 클래스에서 name 속성을 가져옵니다.

android:text="@={myName.name}"

3단계: 데이터 만들기

이제 레이아웃 파일에서 데이터를 참조할 수 있습니다. 다음으로 실제 데이터를 만듭니다.

  1. MainActivity.kt 파일을 엽니다.
  2. onCreate() 위에 비공개 변수를 만듭니다. 관례에 따라 myName이라고도 합니다. 변수에 MyName 데이터 클래스의 인스턴스를 할당하고 이름을 전달합니다.
private val myName: MyName = MyName("Aleks Haecky")
  1. onCreate()에서 레이아웃 파일의 myName 변수 값을 방금 선언한 myName 변수 값으로 설정합니다. XML에서 변수에 직접 액세스할 수 없습니다. 바인딩 객체를 통해 액세스해야 합니다.
binding.myName = myName
  1. 변경 후 바인딩 객체를 새로고침해야 하므로 오류가 표시될 수 있습니다. 앱을 빌드하면 오류가 사라집니다.

4단계: TextView에서 닉네임에 데이터 클래스 사용

마지막 단계는 TextView에서 닉네임에 데이터 클래스를 사용하는 것입니다.

  1. activity_main.xml를 엽니다.
  2. nickname_text 텍스트 뷰에서 text 속성을 추가합니다. 아래와 같이 데이터 클래스에서 nickname를 참조합니다.
android:text="@={myName.nickname}"
  1. ActivityMain에서
    nicknameText.text = nicknameEdit.text.toString()
    myName 변수에 닉네임을 설정하는 코드로 바꿉니다.
myName?.nickname = nicknameEdit.text.toString()

닉네임이 설정되면 코드가 새 데이터로 UI를 새로고침해야 합니다. 이렇게 하려면 올바른 데이터로 다시 생성되도록 모든 바인딩 표현식을 무효화해야 합니다.

  1. 업데이트된 바인딩 객체의 값으로 UI가 새로고침되도록 닉네임을 설정한 후 invalidateAll()를 추가합니다.
binding.apply {
   myName?.nickname = nicknameEdit.text.toString()
   invalidateAll()
   ...
}
  1. 앱을 빌드하고 실행하면 이전과 똑같이 작동합니다.

Android 스튜디오 프로젝트: AboutMeDataBinding

데이터 결합을 사용하여 findViewById() 호출을 대체하는 단계는 다음과 같습니다.

  1. build.gradle 파일의 android 섹션에서 데이터 결합을 사용 설정합니다.
    dataBinding { enabled = true }
  2. XML 레이아웃에서 <layout>을 루트 뷰로 사용합니다.
  3. 바인딩 변수를 정의합니다.
    private lateinit var binding: ActivityMainBinding
  4. MainActivity에서 결합 객체를 만들어 setContentView를 대체합니다.
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  5. findViewById() 호출을 바인딩 객체의 뷰에 대한 참조로 바꿉니다. 예:
    findViewById<Button>(R.id.done_button) ⇒ binding.doneButton
    (이 예에서 뷰의 이름은 XML의 뷰 id에서 카멜 표기법으로 생성됩니다.)

뷰를 데이터에 바인딩하는 단계는 다음과 같습니다.

  1. 데이터의 데이터 클래스를 만듭니다.
  2. <layout> 태그 내에 <data> 블록을 추가합니다.
  3. 이름과 데이터 클래스인 유형으로 <variable>을 정의합니다.
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. MainActivity에서 데이터 클래스의 인스턴스가 있는 변수를 만듭니다. 예를 들면 다음과 같습니다.
    private val myName: MyName = MyName("Aleks Haecky")
  1. 바인딩 객체에서 변수를 방금 만든 변수로 설정합니다.
    binding.myName = myName
  1. XML에서 뷰의 콘텐츠를 <data> 블록에서 정의한 변수로 설정합니다. 점 표기법을 사용하여 데이터 클래스 내부의 데이터에 액세스합니다.
    android:text="@={myName.name}"

Udacity 과정:

Android 개발자 문서:

이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 진행하는 학생에게 출제할 수 있는 과제가 나열되어 있습니다. 다음 작업은 강사가 결정합니다.

  • 필요한 경우 과제를 할당합니다.
  • 과제 제출 방법을 학생에게 알립니다.
  • 과제를 채점합니다.

강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 적절하다고 생각되는 다른 과제를 출제해도 됩니다.

이 Codelab을 직접 진행하는 경우 이러한 과제를 자유롭게 사용하여 배운 내용을 테스트해 보세요.

질문에 답하세요

질문 1

findViewById()에 대한 명시적 및 암시적 호출을 최소화해야 하는 이유는 무엇인가요?

  • findViewById()는 호출될 때마다 뷰 계층 구조를 순회합니다.
  • findViewById()는 기본 스레드나 UI 스레드에서 실행됩니다.
  • 이러한 호출은 사용자 인터페이스를 느려지게 할 수 있습니다.
  • 앱이 다운될 가능성이 낮습니다.

질문 2

데이터 바인딩을 어떻게 설명하시겠어요?

예를 들어 데이터 바인딩에 관해 다음과 같이 말할 수 있습니다.

  • 데이터 바인딩의 핵심 아이디어는 컴파일 시간에 두 개의 원격 정보를 연결/매핑/바인딩하는 객체를 만들어 런타임에 데이터를 찾지 않아도 되도록 하는 것입니다.
  • 이러한 바인딩을 표시하는 객체를 바인딩 객체라고 합니다.
  • 바인딩 객체는 컴파일러에 의해 생성됩니다.

질문 3

다음 중 데이터 바인딩의 이점이 아닌 것은 무엇인가요?

  • 코드가 더 짧고 읽고 관리하기가 더 쉽습니다.
  • 데이터와 뷰가 명확하게 분리되어 있습니다.
  • Android 시스템은 각 뷰를 가져오기 위해 뷰 계층 구조를 한 번만 순회합니다.
  • findViewById()를 호출하면 컴파일러 오류가 발생합니다.
  • 뷰 액세스를 위한 타입 안전성

질문 4

<layout> 태그의 기능은 무엇인가요?

  • 레이아웃에서 루트 뷰를 래핑합니다.
  • 레이아웃의 모든 뷰에 바인딩이 생성됩니다.
  • 데이터 결합을 사용하는 XML 레이아웃의 최상위 뷰를 지정합니다.
  • <layout> 내에서 <data> 태그를 사용하여 변수를 데이터 클래스에 바인딩할 수 있습니다.

질문 5

다음 중 XML 레이아웃에서 바인딩된 데이터를 참조하는 올바른 방법은 무엇인가요?

  • android:text="@={myDataClass.property}"
  • android:text="@={myDataClass}"
  • android:text="@={myDataClass.property.toString()}"
  • android:text="@={myDataClass.bound_data.property}"

다음 강의 시작: 3.1: 프래그먼트 만들기

이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.