이 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 파일에서 데이터 바인딩을 사용 설정해야 합니다. 데이터 바인딩은 컴파일 시간을 늘리고 앱 시작 시간에 영향을 줄 수 있기 때문입니다.
- 이전 Codelab의 AboutMe 앱이 없는 경우 GitHub에서 AboutMeDataBinding-Starter 코드를 가져옵니다. Android 스튜디오에서 엽니다.
build.gradle (Module: app)
파일을 엽니다.android
섹션 내에서 닫는 중괄호 앞에dataBinding
섹션을 추가하고enabled
를true
로 설정합니다.
dataBinding {
enabled = true
}
- 메시지가 표시되면 프로젝트를 동기화합니다. 메시지가 표시되지 않으면 File > Sync Project with Gradle Files를 선택합니다.
- 앱을 실행할 수는 있지만 변경사항은 표시되지 않습니다.
2단계: 데이터 결합과 함께 사용할 수 있도록 레이아웃 파일 변경
데이터 바인딩을 사용하려면 XML 레이아웃을 <layout>
태그로 래핑해야 합니다. 이렇게 하면 루트 클래스가 더 이상 뷰 그룹이 아니라 뷰 그룹과 뷰를 포함하는 레이아웃이 됩니다. 그러면 바인딩 객체가 레이아웃과 레이아웃의 뷰를 알 수 있습니다.
activity_main.xml
파일을 엽니다.- 텍스트 탭으로 전환합니다.
<LinearLayout>
주위에 가장 바깥쪽 태그로<layout></layout>
를 추가합니다.
<layout>
<LinearLayout ... >
...
</LinearLayout>
</layout>
- Code > Reformat code를 선택하여 코드 들여쓰기를 수정합니다.
레이아웃의 네임스페이스 선언은 가장 바깥쪽 태그에 있어야 합니다.
<LinearLayout>
에서 네임스페이스 선언을 잘라내어<layout>
태그에 붙여넣습니다. 여는<layout>
태그는 아래와 같이 표시되어야 하며<LinearLayout>
태그에는 뷰 속성만 포함되어야 합니다.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
- 앱을 빌드하고 실행하여 올바르게 수행했는지 확인합니다.
3단계: 기본 활동에서 바인딩 객체 만들기
바인딩 객체에 대한 참조를 기본 활동에 추가하여 뷰에 액세스할 수 있도록 합니다.
MainActivity.kt
파일을 엽니다.onCreate()
앞에 최상위 수준에서 바인딩 객체의 변수를 만듭니다. 이 변수는 일반적으로binding
라고 합니다.binding
의 유형인ActivityMainBinding
클래스는 이 기본 활동을 위해 컴파일러에 의해 특별히 생성됩니다. 이름은 레이아웃 파일 이름(activity_main + Binding
)에서 파생됩니다.
private lateinit var binding: ActivityMainBinding
- Android 스튜디오에서 메시지가 표시되면
ActivityMainBinding
를 가져옵니다. 메시지가 표시되지 않으면ActivityMainBinding
를 클릭하고Alt+Enter
(Mac의 경우Option+Enter
)를 눌러 누락된 클래스를 가져옵니다. (단축키에 관한 자세한 내용은 단축키를 참고하세요.)import
문은 아래와 비슷해야 합니다.
import com.example.android.aboutme.databinding.ActivityMainBinding
다음으로 현재 setContentView()
함수를 다음 작업을 실행하는 명령어로 바꿉니다.
- 바인딩 객체를 만듭니다.
DataBindingUtil
클래스의setContentView()
함수를 사용하여activity_main
레이아웃을MainActivity
와 연결합니다. 이setContentView()
함수는 뷰의 데이터 바인딩 설정도 처리합니다.
onCreate()
에서setContentView()
호출을 다음 코드 줄로 바꿉니다.
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
DataBindingUtil
를 가져옵니다.
import androidx.databinding.DataBindingUtil
4단계: 바인딩 객체를 사용하여 findViewById()에 대한 모든 호출 바꾸기
이제 findViewById()
에 대한 모든 호출을 바인딩 객체에 있는 뷰에 대한 참조로 바꿀 수 있습니다. 바인딩 객체가 생성되면 컴파일러는 레이아웃의 뷰 ID에서 바인딩 객체의 뷰 이름을 생성하여 카멜 표기법으로 변환합니다. 따라서 예를 들어 done_button
는 바인딩 객체에서 doneButton
가 되고 nickname_edit
는 nicknameEdit
가 되며 nickname_text
는 nicknameText
가 됩니다.
onCreate()
에서findViewById()
을 사용하여done_button
를 찾는 코드를 바인딩 객체의 버튼을 참조하는 코드로 바꿉니다.
이 코드를 다음 코드로 대체합니다.findViewById<Button>(R.id.
done_button
)
binding.doneButton
onCreate()
에서 클릭 리스너를 설정하는 완성된 코드는 다음과 같습니다.
binding.doneButton.setOnClickListener {
addNickname(it)
}
addNickname()
함수의 모든findViewById()
호출에 대해서도 동일한 작업을 실행합니다.findViewById<
View
>(R.id.
id_view
)
의 모든 일치하는 항목을binding.
idView
로 바꿉니다. 다음과 같은 방법으로 이 작업을 수행하세요.
editText
및nicknameTextView
변수의 정의와findViewById()
호출을 삭제합니다. 이렇게 하면 오류가 발생합니다.- (삭제된) 변수 대신
binding
객체에서nicknameText
,nicknameEdit
,doneButton
뷰를 가져와 오류를 수정합니다. view.visibility
를binding.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
의 모든 사용을 업데이트할 수 있습니다.
nicknameText
에는String
이 필요하고nicknameEdit.text
는Editable
입니다. 데이터 바인딩을 사용하는 경우Editable
을String
로 명시적으로 변환해야 합니다.
binding.nicknameText.text = binding.nicknameEdit.text.toString()
- 회색으로 표시된 가져오기를 삭제할 수 있습니다.
apply{}
를 사용하여 함수를 Kotlin화합니다.
binding.apply {
nicknameText.text = nicknameEdit.text.toString()
nicknameEdit.visibility = View.GONE
doneButton.visibility = View.GONE
nicknameText.visibility = View.VISIBLE
}
- 앱을 빌드하고 실행하면 이전과 똑같이 표시되고 작동합니다.
데이터 바인딩을 활용하여 데이터 클래스를 뷰에서 직접 사용할 수 있습니다. 이 기법은 코드를 간소화하며, 더 복잡한 사례를 처리하는 데 매우 유용합니다.
이 예에서는 문자열 리소스를 사용하여 이름과 닉네임을 설정하는 대신 이름과 닉네임의 데이터 클래스를 만듭니다. 데이터 결합을 사용하여 데이터 클래스를 뷰에서 사용할 수 있도록 합니다.
1단계: MyName 데이터 클래스 만들기
- Android 스튜디오의
java
디렉터리에서MyName.kt
파일을 엽니다. 이 파일이 없는 경우 새 Kotlin 파일을 만들고 이름을MyName.kt
로 지정합니다. - 이름과 닉네임의 데이터 클래스를 정의합니다. 빈 문자열을 기본값으로 사용합니다.
data class MyName(var name: String = "", var nickname: String = "")
2단계: 레이아웃에 데이터 추가
activity_main.xml
파일에서 이름은 현재 문자열 리소스의 TextView
에 설정되어 있습니다. 이름에 대한 참조를 데이터 클래스의 데이터에 대한 참조로 바꿔야 합니다.
- 텍스트 탭에서
activity_main.xml
을 엽니다. - 레이아웃 상단의
<layout>
및<LinearLayout>
태그 사이에<data></data>
태그를 삽입합니다. 여기에서 뷰를 데이터와 연결합니다.
<data>
</data>
데이터 태그 내에서 클래스 참조를 보유하는 명명된 변수를 선언할 수 있습니다.
<data>
태그 내에<variable>
태그를 추가합니다.name
매개변수를 추가하여 변수에"myName"
이라는 이름을 지정합니다.type
매개변수를 추가하고 유형을MyName
데이터 클래스의 정규화된 이름 (패키지 이름 + 변수 이름)으로 설정합니다.
<variable
name="myName"
type="com.example.android.aboutme.MyName" />
이제 이름에 문자열 리소스를 사용하는 대신 myName
변수를 참조할 수 있습니다.
android:text="@string/name"
를 아래 코드로 바꿉니다.
@={}
는 중괄호 안에 참조된 데이터를 가져오는 지시어입니다.
myName
는 이전에 정의한 myName
변수를 참조하며, 이 변수는 myName
데이터 클래스를 가리키고 클래스에서 name
속성을 가져옵니다.
android:text="@={myName.name}"
3단계: 데이터 만들기
이제 레이아웃 파일에서 데이터를 참조할 수 있습니다. 다음으로 실제 데이터를 만듭니다.
MainActivity.kt
파일을 엽니다.onCreate()
위에 비공개 변수를 만듭니다. 관례에 따라myName
이라고도 합니다. 변수에MyName
데이터 클래스의 인스턴스를 할당하고 이름을 전달합니다.
private val myName: MyName = MyName("Aleks Haecky")
onCreate()
에서 레이아웃 파일의myName
변수 값을 방금 선언한myName
변수 값으로 설정합니다. XML에서 변수에 직접 액세스할 수 없습니다. 바인딩 객체를 통해 액세스해야 합니다.
binding.myName = myName
- 변경 후 바인딩 객체를 새로고침해야 하므로 오류가 표시될 수 있습니다. 앱을 빌드하면 오류가 사라집니다.
4단계: TextView에서 닉네임에 데이터 클래스 사용
마지막 단계는 TextView
에서 닉네임에 데이터 클래스를 사용하는 것입니다.
activity_main.xml
를 엽니다.nickname_text
텍스트 뷰에서text
속성을 추가합니다. 아래와 같이 데이터 클래스에서nickname
를 참조합니다.
android:text="@={myName.nickname}"
ActivityMain
에서nicknameText.text = nicknameEdit.text.toString()
를myName
변수에 닉네임을 설정하는 코드로 바꿉니다.
myName?.nickname = nicknameEdit.text.toString()
닉네임이 설정되면 코드가 새 데이터로 UI를 새로고침해야 합니다. 이렇게 하려면 올바른 데이터로 다시 생성되도록 모든 바인딩 표현식을 무효화해야 합니다.
- 업데이트된 바인딩 객체의 값으로 UI가 새로고침되도록 닉네임을 설정한 후
invalidateAll()
를 추가합니다.
binding.apply {
myName?.nickname = nicknameEdit.text.toString()
invalidateAll()
...
}
- 앱을 빌드하고 실행하면 이전과 똑같이 작동합니다.
Android 스튜디오 프로젝트: AboutMeDataBinding
데이터 결합을 사용하여 findViewById()
호출을 대체하는 단계는 다음과 같습니다.
build.gradle
파일의 android 섹션에서 데이터 결합을 사용 설정합니다.dataBinding { enabled = true }
- XML 레이아웃에서
<layout>
을 루트 뷰로 사용합니다. - 바인딩 변수를 정의합니다.
private lateinit var binding: ActivityMainBinding
MainActivity
에서 결합 객체를 만들어setContentView
를 대체합니다.binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
findViewById()
호출을 바인딩 객체의 뷰에 대한 참조로 바꿉니다. 예:findViewById<Button>(R.id.done_button) ⇒ binding.doneBu
tton
(이 예에서 뷰의 이름은 XML의 뷰id
에서 카멜 표기법으로 생성됩니다.)
뷰를 데이터에 바인딩하는 단계는 다음과 같습니다.
- 데이터의 데이터 클래스를 만듭니다.
<layout>
태그 내에<data>
블록을 추가합니다.- 이름과 데이터 클래스인 유형으로
<variable>
을 정의합니다.
<data>
<variable
name="myName"
type="com.example.android.aboutme.MyName" />
</data>
MainActivity
에서 데이터 클래스의 인스턴스가 있는 변수를 만듭니다. 예를 들면 다음과 같습니다.private val myName: MyName = MyName("Aleks Haecky")
- 바인딩 객체에서 변수를 방금 만든 변수로 설정합니다.
binding.myName = myName
- 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}"
다음 강의 시작:
이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.