ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [AAC] DataBinding 2 - generated binding class
    Android/ArchitectureComponent 2021. 9. 11. 14:10

    2021.09.09 - [Android/DataBinding] - [Android] DataBinding 1- 데이터바인딩 기본 사용법

    • ViewDataBinding

    데이터 바인딩 클래스들( <layout>태그를 사용한 layout들의 각각에 해당하는 바인딩 클래스들) 의 Base class.

     

    • DataBindingUtil 

    레이아웃으로부터 ViewDataBinding클래스 인스턴스를 생성하기 위한 유틸리티 클래스

    클래스 메서드로 데이터 바인딩과 관련된 기능을 수행한다.

    (inflate, bind, setContentView)

     

    • 우리가 사용하는 바인딩 클래스

    ViewDataBinding의 자손으로 구현되며, 내부에 <variable>변수가 포함되어있다.

     

    activity_main.xml -> ActivityMainBinding extends ViewDataBinding


    • layout과 <layout/>데이터간의 바인딩(데이터 바인딩) 후에 setContentView의 컨텐트 뷰를 전달하기

    데이터 바인딩을 사용하는(<layout>태그를 설정한) 레이아웃은

    추가적으로 <layout>태그내의 데이터와 layout간에 바인딩하는 작업이 필요하게 된다.

    이 작업의 결과로 리턴된 바인딩 인스턴스를 사용하여 레이아웃에 바인딩된 데이터를 변경할 수 있다.

     

    데이터 바인딩을 사용했을 때, 기존의 setContentView로는 바인딩 작업이 포함되지 않으므로 

    바인딩하는 작업을 포함하는 적절한 코드를 설정하여야 한다.

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            //setContentView(R.layout.activity_main) 
            //레이아웃 인플레이션만 진행되고, 데이터와 layout간의 바인딩이 진행되지 않는다.
            
            val activityMainBinding: ActivityMainBinding =
                DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
            //레이아웃 인플레이션 진행, 데이터와 layout간 바인딩 진행, 액티비티(this)의 컨텐트 뷰로 설정 
            //후에 바인딩 인스턴스가 리턴됨
        }
    }

    다른 방법으로는 유틸리티 클래스 DataBindingUtil을 사용하지 않고 자기 자신 바인딩 클래스의 inflate메서드를 사용하는 것이다.

    바인딩 클래스나 DataBindingUtil의 inflate메서드는 레이아웃 인플레이션이외에도 바인딩작업도 포함된다.

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            val activityMainBinding2 = ActivityMainBinding.inflate(layoutInflater)
            //layout을 인플레이션 한 후에 데이터와 레이아웃간 바인딩 후 바인딩 인스턴스 리턴
    
            setContentView(activityMainBinding2.root)
            //바인딩 인스턴스의 getRoot(java)메서드를 통해 레이아웃의 루트 뷰를 참조하여
            //기존 액티비티의 setContentView에 루트 뷰의 참조를 전달
    
        }
    }

     

    reference에서 찾아보니 ViewDataBinding의 inflate는 존재하지 않았는데 ViewDataBinding을 상속받아 독자적으로 만들어지는 메서드인 것 같다.

    ActivityMainBinding의 inflate메서드는 레이아웃 인플레이션, 바인딩 과정이 모두 담겨져있다.

    레이아웃 인플레이션이 이미 진행되었고, 바인딩 인스턴스의 root를 참조할 수 있는 getRoot메서드로 

    기존의 setContentView에 전달하면 된다.

     

    앞선 두가지의 방법을 통해 activityMainBinding변수를 통해

    코드상에서 <variable>에 해당하는 변수에 값을 대입할수도

    layout expression으로 레이아웃에 데이터를 설정할 수도 있다.


    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <layout 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">
        <data>
    
            <variable
                name="varMain"
                type="String" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#8E4545"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#C87777"
                android:gravity="center"
                android:text="@{varMain}" /> //<- layout expression으로 변수의 값으로 텍스트 설정
    
            <LinearLayout
                android:id="@+id/container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" />
        </LinearLayout>
    </layout>

     

    MainActivity.kt

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            val activityMainBinding: ActivityMainBinding =
                DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
                
            activityMainBinding.varMain="코드상에서 데이터를 대입하면 layout의 TextView의 텍스트가 자동으로 변경됨"
        }
    }

    또는

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            val activityMainBinding: ActivityMainBinding =
                ActivityMainBinding.inflate(layoutInflater)
            setContentView(activityMainBinding.root) //액티비티의 setContentView메서드
            
            activityMainBinding.varMain = "코드상에서 데이터를 대입하여 layout의 TextView의 텍스트가 자동으로 변경됨"
        }
    }

     

    현재 테스트 처럼 바인딩될 데이터를 view에 layout expression으로 설정했는데 

    바인딩을 과정이 진행되지 않았다면(기존의 setContentView를 그대로 사용한 경우)

    바인딩 변수(<variable>)들은 기본값들이 들어가게 된다. String의 경우 null이다. 


    • 데이터 바인딩을 사용하는 다른 레이아웃을 동적으로 activity_main의 자식으로 추가할 경우 레이아웃에 해당하는 데이터 바인딩 인스턴스 가져오기

     activity_main의 자식으로 추가하면 해당되는 바인딩 인플레이션, 바인딩, 부모ViewGroup에 붙여넣기 세가지를 동시에 하고 싶으면 ActvitiyMainBinding의 파라메터가 다른 inflate메서드를 사용할 수 있다.

    (기존의 LayoutInflater의 inflate에서 layout리소스 파라메터만 빠진 형태의 메서드들)

     

    acticity_main의 중간에 동적으로 붙여질 included.xml 추가 및 수정

    <?xml version="1.0" encoding="utf-8"?>
    
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <variable
                name="varIncluded"
                type="String" />
        </data>
    
        <LinearLayout
            android:id="@+id/root_included"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="#5C756B"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/tv_included"
                android:gravity="center"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#92B37D"
                android:text="@{varIncluded}" />
        </LinearLayout>
    </layout>

     

    MainActivity.kt

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            //1. activity_main.xml 인플레이션 + 2. activity_main.xml과 <layout>에 선언된 데이터 바인딩
            val activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
    
            //바인딩된 데이터 사용
            activityMainBinding.varMain = "activity_main의 text"
    
            //액티비티의 콘텐트 뷰 설정
            setContentView(activityMainBinding.root)
    
            //바인딩 인스턴스의 뷰바인딩 가능으로 id가 container인 리니어 레이아웃 ViewGroup을 View로 참조
            val container: View = activityMainBinding.container
            container as ViewGroup
    
            //1. included.xml 인플레이션
            val rootIncluded: View = layoutInflater.inflate(R.layout.included, container, false)
            //attachToRoot가 false라
            //rootIncluded는 included.xml의 루트 뷰 참조가 들어간다.
    
            //2. included.xml과 데이터 바인딩
            val includedBinding = IncludedBinding.bind(rootIncluded)
            includedBinding.varIncluded = "include.xml's text설정됨"
            //바인딩된 변수 사용
            
            //2.의 다른 방법
            //val includedBinding: IncludedBinding? = DataBindingUtil.bind<IncludedBinding>(rootIncluded)
            //includedBinding?.varIncluded = "include.xml's text설정됨"
            //generic메서드 바인딩 클래스 타입변수 필요
            
            //3. activity_main의 LinearLayout에 해당하는 container에 자식으로 추가
            container.addView(rootIncluded)
        }
    }

     

    바인딩 클래스, DataBindingUtil의

    inflate 메서드는 레이아웃 인플레이션 및 바인딩 작업을 하고

    bind 메서드의 경우 바인딩 작업만 한다.

     

    DataBindingUtil, 바인딩 클래스의 bind 메서드를 사용할 때는 인자로 인플레이션된 레이아웃의 루트 뷰를 전달한다. 

     

    리턴 값

    모두 바인딩된 바인딩 인스턴스를 내놓는데 DataBindingUtil의 경우 이미 layout에 바인딩이 되어있다면 기존에 존재하는 인스턴스를 리턴.  바인딩이 되어있지 않는다면 바인딩 진행 후 인스턴스를 내놓는다.

     

    바인딩 클래스의 경우 바인딩이 되었는데 bind 메서드를 또 호출하니 에러가 발생하였는데 몇시간쨰 원인을 못찾고 있다.........


    자기 자신인 IncludedBinding클래스를 통하여 bind할 경우

    return되는 것이 자기임을 안다.

    타입 추론은 IncludedBinding클래스로 된다.

     

    반면 DataBindingUtil클래스를 통하여 bind할 경우 bind메서드가 지네릭 메서드이므로 ViewDataBinding포함, 자손의 타입을 타입변수에 명시해야 한다. 

     

    바인딩 된 데이터들은 IncludedBinding클래스의 멤버로 구현되므로 IncludedBinding타입으로 형변환이 필요하다.


    DataBindingUtil의 bind를 여러번 호출할 경우  처음에는 만들어진 바인딩 인스턴스, 이후에는 이미 만들어진 바인딩 인스턴스가 리턴되어 문제가 발생하지 않았는데

     

    ActvitiyMainBinding의 bind메서드를 사용한 경우에는 바인딩 인스턴스가 존재하지 않은 경우 에러가 발생하지 않았지만, 바인딩이 되있는 레이아웃의 루트 뷰를 넣어 또 bind를 호출한 경우 에러가 발생하였다..

     

    class TestActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            Log.d("DataBinding", "onCreate")
    
            setContentView(R.layout.activity_test)
            val rootView: View = findViewById(R.id.rootMain2)
    
            try {
                val binding=ActivityTestBinding.bind(rootView) 
                binding.varMain2="첫번째"
            } catch (e: Exception) {
                Log.d("DataBinding", e.toString())
            }
    
            try {
                val binding=DataBindingUtil.bind<ActivityTestBinding>(rootView)
                binding?.varMain2="두번째"
            } catch (e: Exception) {
                Log.d("DataBinding", e.toString())
            }
    
            try {
                val binding=DataBindingUtil.bind<ActivityTestBinding>(rootView)
                binding?.varMain2="세번째"
            } catch (e: Exception) {
                Log.d("DataBinding", e.toString())
            }
    
            try { //바인딩이 이미 진행된 layout의 루트 뷰를 넣어주니 에러 발생- View must have a tag
                val binding=ActivityTestBinding.bind(rootView)
                binding.varMain2="네번째"
            } catch (e: Exception) {
                Log.d("DataBinding", e.toString())
            }
        }
    }

     

     

     

     

     

    댓글

Designed by Tistory.