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

이 Codelab은 Android Kotlin 기초 교육 과정의 일부입니다. Codelab을 순서대로 진행한다면 이 과정을 통해 최대한의 가치를 얻을 수 있을 것입니다. 모든 과정 Codelab은 Android Kotlin 기초 Codelab 방문 페이지에 나열되어 있습니다.

소개

이 과정의 이전 Codelab에서는 findViewById() 함수를 사용하여 뷰 참조를 가져왔습니다. 앱에 복잡한 뷰 계층 구조가 있는 경우 findViewById()는 비용이 많이 들고 앱의 속도가 느려집니다. Android가 원하는 뷰를 찾을 때까지 루트부터 시작하여 뷰 계층 구조를 순회하기 때문입니다. 다행히 더 좋은 방법을 찾고 있습니다.

뷰에서 데이터를 설정하려면 문자열 리소스를 사용하고 활동에서 데이터를 설정해야 합니다. 뷰가 데이터를 알고 있다면 더 효율적입니다. 다행히도 가능합니다.

이 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. <layout></layout><LinearLayout> 주변의 가장 바깥쪽 태그로 추가합니다.
<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_buttondoneButton이고, 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. Text 탭에서 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 방문 페이지를 참고하세요.