안드로이드 view binding

PEPPERMINT100·2020년 12월 27일
0
post-thumbnail

서론

항상 관심이 있었던 모바일 개발을 최근에 해보고 있다. 리액트처럼 클라이언트 단 개발이라는 점이 비슷하게 느껴졌지만 확실히 새로운 분야이기에 공부 방향을 어떻게 잡아야 할지 고민을 해보았다.

  1. 언어적인 측면
    Java 언어는 이 전에 Spring Boot에서 사용해보았고 알고리즘 문제도 Java로 꽤 풀어보았기에 JVM 기반의 Kotlin 언어도 공식 문서의 요약본만 간단하게 읽어도 충분할 것 같았다.(절대 충분하지 않지만 안드로이드 스튜디오를 공부해나가며 모르거나 부족한 부분을 채울 수 있을것이라 판단하였다.)

  2. 프레임워크
    안드로이드 스튜디오의 사용법을 익히면 된다. React의 경우 처음 접했을 때는 비교적 신기술이어서 영어로 컨텐츠를 많이 접했는데, 안드로이드는 오래된 기술이어서 그런지 한글로도 양질의 컨텐츠가 많았다. 하지만 문제점이 하나 있었는데 그것이 오늘 적어볼 내용이다.

유튜브 튜토리얼

유튜브 튜토리얼을 보고 열심히 따라서 치다보면(안드로이드의 경우 객체지향에 대한 이해, 그리고 빠른 흡수를 위해 클라이언트 개발에 대한 이해가 필요하다) 에러가 조금씩 발생한다. 가장 먼저 만난 오류가 바로 view binding 이슈였다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="#D8D8D8"
    >
    <FrameLayout
        android:id="@+id/fragments_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottom_nav"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        app:menu="@menu/bottom_nav_menu"
        android:id="@+id/bottom_nav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:background="#fff"
        />
    <TextView
        android:id="@+id/my_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello world"
        />

</RelativeLayout>

여기서 가장 아래 TextViewmy_text_view id의 텍스트 내용을 바꾸기 위해 액티비티에서 선택을 하는 내용이 있었다.

val my_text_view.text = "hello world"

액티비티의 setContentView를 통해 xml 레이아웃 파일과 연결하면 바로 id를 불러와서 텍스트를 고치는 내용을 볼 수 있었다. 하지만 내 코드에선 먹히지 않았다.

FindViewById

튜토리얼 영상은 6개월 정도 지난 영상으로 안드로이드 영상 기준 비교적 최근에 올라온 글이다. 하지만 벌써 안되는 부분을 발견할 수 있었다. 플랫폼 의존적인 개발의 문제인지 아니면 내가 재수가 없는건지 이유를 구글링 해보았고 이유는

apply plugin: 'kotlin-android-extensions'

디폴트로 설정된 위 플러그인이 deprecated 되었다고 한다. 따라서 id를 바로 가져올 수는 없고 findViewById를 통해서 가져와야 했다.

val bottom_nav = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottom_nav.setOnNavigationItemSelectedListener(onBottomNavigationSelectedListener)

이런식으로 View의 타입을 제네릭으로 하고 R.id<ID>를 통해서 가져온다음 뷰에 사용가능한 메소드들을 사용하면 되었다. 하지만 이 방법을 보자마자 만약 복잡한 개발을 한다면 코드가 굉장히 길어질 것 같았고 분명히 대체제가 있을 것이라 생각했다. 당연히 있었고 뷰 바인딩을 통해서 원래 방식과 비슷하게 개발을 할 수 있었다.

View binding

먼저 gradle 모듈 파일에

android {
        ...
        viewBinding {
            enabled = true
        }
    }

뷰 바인딩을 활성화 시킨다. 그리고 액티비티나 프래그먼트에서 사용하면 되는데, 각각 사용 방법이 다르다. 액티비티에서는

   private lateinit var binding: ResultProfileBinding

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        binding = ResultProfileBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }
    

위 코드는 공식문서에 있는 내용으로 xml 파일의 이름이 만약 result_profile.xml이면 파스칼 케이스의 방식(ResultProfileBinding)으로 바인딩 클래스를 받아 올 수 있게 된다.

그리고 inflate 메소드를 통해 binding을 받아오고 setContentView를 통해 xml 파일과 연결을 해준다.

그리고

   binding.myTextView.text = "fewa"

위와 같이 binding 이후 카멜케이스 방식으로 뷰를 받아와서 사용할 수 있게 된다.

프래그먼트에서는

class HomeFragment : Fragment(){
    companion object {
        const val TAG: String = "로그"
        private var _binding: FragmentHomeBinding? = null
        private val binding get() = _binding!!

        fun newInstance(): HomeFragment {
            return HomeFragment()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "HomeFragment - onCreated - () called");
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        Log.d(TAG, "HomeFragment onAttach - () called");
    }
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

위와 같이 사용을 하는데, 먼저 xml 파일의 이름은 fragment_home.xml이라고 가정한 코드이다. 그리고

참고: 프래그먼트는 뷰보다 오래 지속됩니다. 프래그먼트의 onDestroyView() 메서드에서 결합 클래스 인스턴스 참조를 정리해야 합니다.

공식문서에서 이러한 점을 볼 수 있었는데, 뷰가 제거 되었을 때 프래그먼트의 바인딩도 정리하여 메모리 낭비를 막는 코드라고 볼 수 있겠다.

FindViewById를 써도 될 것 같다.

사용법을 대충 익히고 나니 그냥 findViewById를 통해 마음의 평온을 얻을 수도 있을 것 같아보였다. 하지만 view binding만의 장점이 있었다. 가장 먼저 xml 파일에서

<LinearLayout
            ...
            tools:viewBindingIgnore="true" >
        ...
    </LinearLayout>

위 코드로 필요 없는 부분의 view binding을 무시할 수 있었고 공식 문서를 보면

Null 안전: 뷰 결합은 뷰의 직접 참조를 생성하므로 유효하지 않은 뷰 ID로 인해 null 포인터 예외가 발생할 위험이 없습니다. 또한 레이아웃의 일부 구성에만 뷰가 있는 경우 결합 클래스에서 참조를 포함하는 필드가 @Nullable로 표시됩니다\

유형 안전: 각 바인딩 클래스에 있는 필드의 유형이 XML 파일에서 참조하는 뷰와 일치합니다. 즉, 클래스 변환 예외가 발생할 위험이 없습니다.

결국 안전한 코드를 위한 장치였고 직접 실험을 해보았는데, 예를 들면

val my_text_view = findViewById<TextView>(2)

이런식이나

val my_text_view = findViewById<TextView>(R.id.fewagfawklgja)

이런식으로 아무 아이디나 잡아도 빌드와 앱의 실행에서는 문제가 생기지 않는다.

하지만 view binding 으로 묶고 나서는

 binding.flaewef.text = "fawefw"

이러한 코드를 쓰면 빌드 자체에서 에러를 잡을 수 있게 된다. 결국 런타임에서가 아닌 컴파일에서 에러를 잡기 위한 장치가 되는 것이다.

결론

딱히 뷰 바인딩에 대한 결론은 없는것 같다. 클라이언트 개발단에서의 타입 세이프, 널 세이프를 위한 장치 중 하나였다. 안드로이드 개발을 조금씩 공부해보고 있는데 굉장히 재밌다. 모든 개발 공부의 2일차 수준에선 당연히 재밌을 수 밖에 없지만 HTML, CSS와 다른 방식의 레이아웃과 플랫폼 의존적이라는 부분이 변태같지만 굉장히 흥미롭다. 개발을 처음 시작할 때부터 모바일 파트에 많은 관심이 있었고 지속적으로 공부를 해 보고 싶다.

  
profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

0개의 댓글