[내일배움캠프][TIL] 24.01.03 (수) - Android 앱개발 숙련 - ViewBinding
1. Android 앱개발 숙련 - ViewBinding
앱개발에 첫 발을 내딘 지도 엊그제같은데 어느덧 입문의 단계를 지나서 숙련의 단계를 밟기 시작했다.
지난 입문 단계에서는 기초적인 UI의 설계에 대해서 알아봤다면, 이번 숙련 단계에서부터는 데이터의 효율적인 사용, 즉 CRUD(Create, Read, Update, Delete를 뜻하는 프로그래밍의 기본적인 4가지)에 익숙해지기 위한 학습을 지향한다.
그 첫번째 단계로, 오늘은 ViewBinding에 대해서 알아보도록 하겠다.
ViewBinding
뷰바인딩(ViewBinding)이란 뷰와 상호작용하는 코드를 쉽게 작성할 수 있게 하는 기능으로, findViewById를 대체할 수 있다.
ViewBinding의 필요성
그렇다면 ViewBinding은 왜 사용할까?
내가 이전에 자주 쓰던 findViewById과 비교해서 살펴보자.
1) Null 안전성 (Null Safety)
뷰 바인딩 기능을 사용하면, 앱이 레이아웃의 각 뷰를 직접 참조할 수 있게 해주는 안전한 코드를 자동으로 생성합니다. 이것은 뷰를 사용할 때 'null' 값으로 인한 오류, 즉 뷰가 아직 화면에 나타나지 않았는데 그 뷰를 사용하려고 할 때 생길 수 있는 문제들을 예방해 줍니다.
예를 들어, 만약 레이아웃에 버튼이 있어야 하는데 아직 버튼이 생성되지 않았다면, 뷰 바인딩은 이를 안전하게 처리하여 앱이 충돌하지 않도록 합니다. 또한, 만약 레이아웃의 일부만 뷰가 있다면, 뷰 바인딩은 해당 뷰가 '가능성 있는 null'(Nullable)임을 알려주어, 개발자가 더 주의 깊게 코드를 작성하도록 돕습니다.
2) 타입 안전성 (Type Safety)
XML 레이아웃 파일에서 정의된 뷰의 타입과 자동 생성된 바인딩 클래스의 필드 타입이 항상 일치하기 때문에, 타입이 서로 맞지 않아 발생할 수 있는 오류를 방지합니다.
예를 들어, 이미지 뷰(ImageView)에 텍스트를 설정하려고 하면 오류가 발생할 텐데, 뷰 바인딩을 사용하면 이런 실수를 할 가능성이 없어집니다. 즉, 이미지 뷰는 이미지 뷰로, 텍스트 뷰는 텍스트 뷰로만 사용되게 하여, 잘못된 타입 사용으로 인한 오류가 발생하지 않도록 보장합니다.
결론적으로, ViewBinding이 findViewById보다 훨씬 더 성능이 좋다는 점에 있다.
ViewBinding 사용 방법
그러면 이렇게 편리한 ViewBinding은 어떻게 사용할까?
우선, build.gradle 파일을 연다 (이 때, Module :app이 달려있는 파일을 열어야 한다)
그 다음, android 안에 다음의 문구를 넣고 Sync를 한다.
android{
...
// AndroidStudio 3.6 ~ 4.0
viewBinding{
enabled = true
}
// AndroidStudio 4.0 ~
buildFeatures{
viewBinding = true
}
}
Sync까지 완료했다면, 이제 ViewBinding을 사용할 액티비티에 가서 binding을 변수로 정의한다. 이 때, 변수형은 레이아웃의 이름 뒤에 Binding을 붙인다.
private lateinit var binding: ActivityMainBinding
그 다음 onCreate 함수 밑에 위에서 선언한 binding을 설정한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
이렇게 하면 binding을 사용할 수 있게 된다.
이제 이렇게 설정한 binding을 다음과 같이 사용할 수 있다.
binding.myButton.setOnClickListener{
binding.myTextView.text = "바인딩이 잘 되네요!"
}
원래는 setOnClickListener을 사용하려면 findViewById을 이용해서 myButton을 정의해야 하지만, binding을 사용하면 그런 번거로움을 덜 수 있다.
비단 button만 아니라 다른 종류의 Widget 역시 사용할 수 있다. 단, 레이아웃에 적힌 id를 정확하게 입력해야 한다. 그리고 스네이크케이스로 적은 id의 경우는 카멜케이스로 변환시켜야 한다.
ViewBinding 적용 예시
이제 사용하는 방법까지 알았으니 실제로 ViewBinding을 한 번 적용시켜보자.
저번주에 만들었던 팀 프로젝트 앱에서 내가 만든 부분인 MyPageActivity를 ViewBinding을 이용해서 한 번 고쳐보자.
다음은 ViewBinding을 이용해서 고친 MyPageActivity의 전체 코드이다.
class MyPageActivity : AppCompatActivity() {
private val tvId:TextView by lazy { findViewById(R.id.txt_id) }
private val tvTrack:TextView by lazy { findViewById(R.id.txt_track) }
private lateinit var binding: ActivityMyPageBinding
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMyPageBinding.inflate(layoutInflater)
setContentView(binding.root)
// 객체에서 정보 받아오기
if (!CurrentUser.isSignedIn()) {
Toast.makeText(this, "오류: 로그인 되어있지 않습니다.", Toast.LENGTH_SHORT).show()
} else {
tvId.text = "${getString(R.string.txt_id)} : " + (CurrentUser.user?.id ?: "not signed in")
tvTrack.text = "${getString(R.string.txt_track)} : " + (CurrentUser.user?.track ?: "not signed in")
}
// 프로필사진 분류
val image = when (CurrentUser.user?.track) {
"Android" -> R.drawable.sparta
"iOS" -> R.drawable.sparta2
"Unity" -> R.drawable.sparta3
else -> R.drawable.snake_sparta
}
binding.ivProfil.setImageDrawable(ResourcesCompat.getDrawable(resources, image, null))
// 자신의 트랙에 따라서 보여지는 트랙과 게시글 제목 분류
binding.txtApptrack.text = CurrentUser.user?.track
binding.txtWriting.text = when(CurrentUser.user?.track) {
"Android" -> "스파르타 친구들 새해 잘 보내!"
"iOS" -> "코딩 너무 어려워.."
"Unity" -> "UNITY 팁 몇가지 알려줄게!"
else -> "최초 AI의 탄생 알고있어??"
}
binding.btnLanguageSetting.setOnClickListener{
val intent = Intent(this, LanguageSettingActivity::class.java)
startActivity(intent)
}
binding.ivReturn.setOnClickListener{
finish()
}
binding.btnGoDetail.setOnClickListener{
val intent = when(CurrentUser.user?.track) {
"Android" -> Intent(this, DetailPageActivity::class.java)
"iOS" -> Intent(this, DetailPageActivity2::class.java)
"Unity" -> Intent(this, DetailPageActivity3::class.java)
else -> Intent(this, DetailPageActivity4::class.java)
}
startActivity(intent)
}
binding.btnLogout.setOnClickListener{
CurrentUser.user = null
finish()
}
}
}
(깨알같이 어제 자체적으로 피드백한 부분인 '각 트랙 별 DetailPageActivity 이동'도 추가)
class MyPageActivity : AppCompatActivity() {
private val tvId:TextView by lazy { findViewById(R.id.txt_id) }
private val tvTrack:TextView by lazy { findViewById(R.id.txt_track) }
private lateinit var tvTrackName: TextView
private lateinit var tvWritting: TextView
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my_page)
// 객체에서 정보 받아오기
if (!CurrentUser.isSignedIn()) {
Toast.makeText(this, "오류: 로그인 되어있지 않습니다.", Toast.LENGTH_SHORT).show()
} else {
val user = CurrentUser.user
tvId.text = "${getString(R.string.txt_id)} : " + (CurrentUser.user?.id ?: "not signed in")
tvTrack.text = "${getString(R.string.txt_track)} : " + (CurrentUser.user?.track ?: "not signed in")
}
// 프로필사진 분류
val ivProfil = findViewById<ImageView>(R.id.iv_profil)
val image = when (CurrentUser.user?.track) {
"Android" -> R.drawable.sparta
"iOS" -> R.drawable.sparta2
"Unity" -> R.drawable.sparta3
else -> R.drawable.snake_sparta
}
ivProfil.setImageDrawable(ResourcesCompat.getDrawable(resources, image, null))
// 자신의 트랙에 따라서 보여지는 트랙과 게시글 제목 분류
tvTrackName = findViewById(R.id.txt_apptrack)
tvTrackName.text = CurrentUser.user?.track
tvWritting = findViewById(R.id.txt_writing)
tvWritting.text = when(CurrentUser.user?.track) {
"Android" -> "스파르타 친구들 새해 잘 보내!"
"iOS" -> "코딩 너무 어려워.."
"Unity" -> "UNITY 팁 몇가지 알려줄게!"
else -> "최초 AI의 탄생 알고있어??"
}
// 버튼 클릭
val btnLanguageSetting = findViewById<Button>(R.id.btn_languageSetting)
val btnBackToMain = findViewById<ImageView>(R.id.iv_return)
val btnGoToDetail = findViewById<Button>(R.id.btn_goDetail)
val btnLogout = findViewById<Button>(R.id.btn_logout)
btnLanguageSetting.setOnClickListener{
val intent = Intent(this, LanguageSettingActivity::class.java)
startActivity(intent)
}
btnBackToMain.setOnClickListener{
finish()
}
btnGoToDetail.setOnClickListener{
val intent = when(CurrentUser.user?.track) {
"Android" -> Intent(this, DetailPageActivity::class.java)
"iOS" -> Intent(this, DetailPageActivity2::class.java)
"Unity" -> Intent(this, DetailPageActivity3::class.java)
else -> Intent(this, DetailPageActivity4::class.java)
}
startActivity(intent)
}
btnLogout.setOnClickListener{
CurrentUser.user = null
finish()
}
}
}
참고로 이건 ViewBinding을 사용하기 이전의 코드이다.
한 눈에 봐도 ViewBinding을 사용했을 때 코드의 길이라던가 편의성 측면에서 크게 개선되었다는 걸 알 수 있다.