[TIL][내일배움캠프]

[내일배움캠프][TIL] 24.01.03 (수) - Android 앱개발 숙련 - ViewBinding

kimlaurant 2024. 1. 3. 20:34
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을 사용했을 때 코드의 길이라던가 편의성 측면에서 크게 개선되었다는 걸 알 수 있다.