본문

180413(금) - Data Binding Library

Data Binding Library

- data binding libary를 사용해서 declarative layouts를 사용

- application logic 및 layout binding에서 필요한 glue code를 minimize 


- support library이므로 Android 2.1 (API level 7+) 이상에서 사용 가능

- Android Plugin for Gradle 1.5.0-alpha1 or higher 필요


Build Environment

- Support library에 포함

- data binding을 사용하는 library를 사용하고 있어도 data binding enabled 필요

- Android studio 1.3 이상이면 databinding 지원

android {
   
....
    dataBinding
{
        enabled
= true
   
}
}


Data Binding Compiler V2

- Android Gradle Plugin 3.1.0 Canary 6 이상이면 new compiler를 optional하게 사용 가능

- gradle.properties에 다음과 같이 include

  android.databinding.enableV2=true

- ViewBinding class는 java compiler 이전에 Android Gradle Plugin에 의해서 generated 된다.

-> java compiler가 data binding과 관련없는 이유로 fail되면, 너무많은 false positive error를 getting하는것을 피할 수 있다.

- V1에서 binding classes는 app이 compiled될 때 (share generated code and access BR and R files) regenerated된다.

-> V2 에서는 generated binding classes 뿐만 아니라 multi-module projects의 data binding performance를 크게 향상시키는 mapper information을 유지한다.


- v1 cannot used by v2 or vice versa

- V2는 거의 사용하지 않는 functionality를 삭제

1. V1에서 app은 dependencies를 override할 수 있는 binding adapter를 제공

-> V2에서는 only effect in your own module / application and dependencies


2. 이전에는 2개 이상의 different resource configurations의 layout files에 동일한 id 이지만, 다른 class인 경우, Data Binding은 최상위 parent class를 찾았다. 

-> V2는 configuration이 일치하지 않으면 항상 default view


3. V2에서 Data Binding은 binding mapper class를 생성하기 위해서 해당 package name을 사용하기 때문에 다른 module은 manifest 에서 same package name을 사용 할 수 없다.


Data Binding Layout Files

- Writing your first set of data binding expressions

- data-binding layout file은 기존 xml과 약간 다르다

- layout root tag + data element + view root element

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   
<data>
       
<variable name="user" type="com.example.User"/>
   
</data>

   
<LinearLayout
       
android:orientation="vertical"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent">
       
<TextView android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:text="@{user.firstName}"/>
       
<TextView android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:text="@{user.lastName}"/>
   
</LinearLayout>
</layout>

- data describes안의 variable user는 layout 안에서 사용할 수 있는 property

<variable name="user" type="com.example.User"/>
- "@{}" syntax를 사용하여 layout상의 attribute properties에 작성된다.
<TextView android:layout_width="wrap_content"
         
android:layout_height="wrap_content"
         
android:text="@{user.firstName}"/>
- Data Object
- 평범한 POJO User가 있다고 가정
- 아래 코드에는 never changes object가 있다.
- common applications는 read once and never change data를 쓴다.
public class User {
   
public final String firstName;
   
public final String lastName;
   
public User(String firstName, String lastName) {
       
this.firstName = firstName;
       
this.lastName = lastName;
   
}
}

- data binding의 관점에서 이 두개의 classes는 같다.

- TextView의 android:text attribute에 사용된 @{user.firstNmae}은 위에서 firstName, 아래에선 getFirstName() method에 access한다.

- 해당 method가 있으면 firstName()도 가능

public class User {
   
private final String firstName;
   
private final String lastName;
   
public User(String firstName, String lastName) {
       
this.firstName = firstName;
       
this.lastName = lastName;
   
}
   
public String getFirstName() {
       
return this.firstName;
   
}
   
public String getLastName() {
       
return this.lastName;
   
}
}

- Binding Data

- Binding class는 layout file의 이름을 기반으로 Pascal case로 변환되며, suffixing으로 "Binding"이 붙는다.

- main_activity.xml -> MainAcitivtyBinding

- layout properties에서 layout's View까지의 모든 binding을 보유하고, binding expressions에 values를 assign하는 방법을 알고 있다.

- 가장 쉬운 방법은 inflating하면서 binding하는 방법

@Override
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
   
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   
User user = new User("Test", "User");
   binding
.setUser(user);
}
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

-> KakaoT에서 BaseActivity()를 상속받는 모든 activity는 해당 방법 사용 불가.

-> val binding = MainActivityBinding.bind(...) 이런식으로 사용해야 한다.


- ListView or RecyclerView를 사용하는 경우 아래와 같이 사용

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);


- Event Handling

- onClick과 같은 event handling expression도 allows

- event attribute name는 few exceptions를 제외하고 listener method의 이름에 의해서 governed된다.

- ex) View.OnLongClickListener has onLongClick() -> android:onLongClick

- two way to handle event

1. Method References

<요약>

- expressions에서 listener method의 signature를 conform하는 method를 reference할 수 있다.

- expression이 method reference로 판단되면, Data Binding은 method reference and owner object를 listener에 wraps하고, 해당 listener를 target view에 sets한다.

- expressions가 null이면 Data Binding은 listener를 만들지 않고 null listener를 sets한다.


<상세>

- android:onClick를 Activity의 method에 직접 bound할 수 있는 방식과 비슷하게, event를 handler method에 직접 bound할 수 있다.

- View#onClick attribute와 비교해 볼때 한가지 장점은 expression이 compile time에 processed

- 그래서 method not exist or signature not correct이면 compile time에 error가 발생


- listener bindings와 주요한 차이점은 event가 triggered될 때가 아니라 data binding될 때 실제 listener implementation is create

- event가 happens되었을 때 expression evaluate되기를 원한다면 listener binding을 써야 한다.


- handler에 event를 assign하려면 call할 method name으로 normal binding expression을 사용

- expression의 signature는 listener object method signature와 정확히 일치해야 한다.

public class MyHandlers {
   
public void onClickFriend(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   
<data>
       
<variable name="handlers" type="com.example.MyHandlers"/>
       
<variable name="user" type="com.example.User"/>
   
</data>
   
<LinearLayout
       
android:orientation="vertical"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent">
       
<TextView android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:text="@{user.firstName}"
           
android:onClick="@{handlers::onClickFriend}"/>
   
</LinearLayout>
</layout>


2. Listener Bindings

<요약>

- event happens일 때 evaluated되는 lambda expressions

- Data Binding은 항상 listener를 만들고 view에 sets

- event is dispatched, listener evaluates the lambda expression


<상세>

- evnet happens시 run되는 binding expressions

- method reference와 비슷하지만 arbitrary data binding expressions를 실행 가능

- Android Gradle Plugin for Gradle version 2.0 and later에서 사용 가능


- method reference에서 method parameter의 개수는 event listener의 parameters 개수와 must match해야 한다.

- listener binding에서는 return value만 listner의 expected return value와 일치하면 된다. (void가 예상되는 경우는 제외)

public class Presenter {
   
public void onSaveClick(Task task){}
}
  <?xml version="1.0" encoding="utf-8"?>
 
<layout xmlns:android="http://schemas.android.com/apk/res/android">
     
<data>
         
<variable name="task" type="com.android.example.Task" />
         
<variable name="presenter" type="com.android.example.Presenter" />
     
</data>
     
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
         
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
         
android:onClick="@{() -> presenter.onSaveClick(task)}" />
     
</LinearLayout>
 
</layout>

- listener는 expressions의 root elements로 allowed 되는 lambda expressions로 표현된다.

- expression에 callback이 사용되면 data binding은 자동으로 필요한 listener create and event registers

- view가 event를 발생시키면 data binding은 expression을 evaluates

- regular binding expressions와 마찬가지로 listener expressions가 evaluated되는 동안 null and thread safety 가 유지된다.


- listener parameters

1. either ignore all parameters to method

2. all name을 지정 가능

expression에서 name 지정 가능

  android:onClick="@{(view) -> presenter.onSaveClick(task)}"


public class Presenter {
   
public void onSaveClick(View view, Task task){}
}
  android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"


public class Presenter {
   
public void onCompletedChanged(Task task, boolean completed){}
}
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
       
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />


public class Presenter {
   
public boolean onLongClick(View view, Task task){}
}
  android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"


- if expression이 null object때문에 evaluate 할 수 없으면 data binding은 Java value type을 return

- reference type -> null

- int -> 0

- boolean -> false


- ternary predicate를 사용해야 한다면 void symbol을 사용 할 수 있다.

  android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

- Avoid Complex Listeners
- listener expressions는 very powerful and code very easy to read
- but, complex expressions를 포함하는 listener는 layout을 읽기 어렵고 유지하기 어렵게 만든다.
- this expressions는 UI -> callback method 로 available data를 passing하는것처럼 간단해야 한다.
- listener expression에서 invoked한 callback method inside에 business logic implement!
- some specialized click event handlers가 있으며, conflict avoid를 위해 android:onclick 이외의 attribute가 필요하다.


Layout Details

- import

- data element 내부에서 zero or more import elements used

- allow easy reference java classes

- Android Studio IDE 에선 아직 imported variables에 대해서 autocomplete가 되지 않는다.

<data>
   
<import type="android.view.View"/>
</data>
<TextView
   
android:text="@{user.lastName}"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
       
alias="Vista"/>
<data>
   
<import type="com.example.User"/>
   
<import type="java.util.List"/>
   
<variable name="user" type="User"/>
   
<variable name="userList" type="List&lt;User&gt;"/>
</data>
<TextView
   
android:text="@{((User)(user.connection)).lastName}"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>
<data>
   
<import type="com.example.MyStringUtils"/>
   
<variable name="user" type="com.example.User"/>
</data>

<TextView
   
android:text="@{MyStringUtils.capitalize(user.lastName)}"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>


- Variables

- data element에서 any number of variable elements를 사용 가능

- 각 variable elements는 layout file에서 binding expressions에 설정할 수 있는 property를 describes

<data>
   
<import type="android.graphics.drawable.Drawable"/>
   
<variable name="user"  type="com.example.User"/>
   
<variable name="image" type="Drawable"/>
   
<variable name="note"  type="String"/>

</data>

- variable types는 compile time에 inspected되므로 android.databinding.Observable or obsavable collection인 경우 해당 type에 reflected되어야 한다.

- variavle이 Observable* interface 를 implement하지 않는 base class or interface인 경우 not be observed!

- various configurations (landscape or portrait)인 different layout files이 있는 경우 variables will be combined

- 이런 layout files 간에는 must not be conflicting variable definitions


- generated binding class는 각 described variables에 대한 setter and getter가 있다.

- variables는 setter가 called될 때까지

- null for reference types

- 0 for int

- false for boolean

- etc...


- needed에 따라서 binding expressions에 사용하기 위해 context라는 special variable이 생긴다.

- context value는 root View's getContext()

- context variable는 explicit variable declarations에 의해서 overridden된다.


- Custom Binding Class Names

- 생성된 Binding class는 module package under의 databinding package에 배치된다.

- ex) contact_item.xml -> ContactItemBinding

- ex) com.example.my.app under com.example.my.app.databinding


- data element의 attribute를 조정하여 binding class를 renamed or different package에 배치 가능

<data class="ContactItem">
    ...
</data>

<data class=".ContactItem"> // module package에 directly created
    ...
</data>

<data class="com.example.ContactItem"> // full package가 provided되면 any package can be used.
    ...
</data>


- include

- variables는 application namespace and attribute variable name을 사용해서 included layout으로 binding 가능하다.

- name and contact layout에는 user variable이 반드시 있어야 한다.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
       
xmlns:bind="http://schemas.android.com/apk/res-auto">
   
<data>
       
<variable name="user" type="com.example.User"/>
   
</data>
   
<LinearLayout
       
android:orientation="vertical"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent">
       
<include layout="@layout/name"
           
bind:user="@{user}"/>
       
<include layout="@layout/contact"
           
bind:user="@{user}"/>

   
</LinearLayout>
</layout>

- data binding not support merge element의 include를 지원하지 않는다.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
       
xmlns:bind="http://schemas.android.com/apk/res-auto">
   
<data>
       
<variable name="user" type="com.example.User"/>
   
</data>
   
<merge>
       
<include layout="@layout/name"
           
bind:user="@{user}"/>
       
<include layout="@layout/contact"
           
bind:user="@{user}"/>
   
</merge>

</layout>


- Expression Language

 - Common Features

- Java expression과 비슷


          • Mathematical + - / * %
          • String concatenation +
          • Logical && ||
          • Binary & | ^
          • Unary + - ! ~
          • Shift >> >>> <<
          • Comparison == > < >= <=
          • instanceof
          • Grouping ()
          • Literals - character, String, numeric, null
          • Cast
          • Method calls
          • Field access
          • Array access []
          • Ternary operator ?:
android:text="@{String.valueOf(index + 1)}"
android
:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android
:transitionName='@{"image_" + id}'
- Missing Operations
- java에서 쓸 수 있는 expression syntax중 few operations missing
  • this
  • super
  • new
  • Explicit generic invocation


- Null Coalescing Operator

- null coalescing operator는 null이 아닐경우 left operand, null일 경우 right operand

android:text="@{user.displayName ?? user.lastName}"

android:text="@{user.displayName != null ? user.displayName : user.lastName}" // Ternary operator사용


- Property Reference

- class property expression references는 getter, fields and ObservableField에 대해서 동일한 format을 사용

android:text="@{user.lastName}"

- Avoiding NullPointerException

- generated data binding code는 automatically checks null and avoid null point exceptions

- ex) @{user.name} 에서 만약 user가 null이면 user.name은 default value(null) assigned

- ex) @{user.age} 에서 age가 int이면 default 는 0


- Collections

- common collections (array, lists, sparse lists, maps)는 [] operator로 access가능

<data>
   
<import type="android.util.SparseArray"/>
   
<import type="java.util.Map"/>
   
<import type="java.util.List"/>
   
<variable name="list" type="List&lt;String&gt;"/>
   
<variable name="sparse" type="SparseArray&lt;String&gt;"/>
   
<variable name="map" type="Map&lt;String, String&gt;"/>
   
<variable name="index" type="int"/>
   
<variable name="key" type="String"/>
</data>

android:text="@{list[index]}"

android:text="@{sparse[index]}"

android:text="@{map[key]}"

- String Literals

- attribute에 single quotes (')를 사용하면 expression에서 double quotes (")를 사용하기 쉬워진다.

android:text='@{map["firstName"]}'
- 반대로 attribute에 double quotes를 사용 가능하다.
- string literals는 반드시 single quotes or back quote (`)를 사용해야 한다.
android:text="@{map[`firstName`}"
android
:text="@{map['firstName']}"

- Resources
- normal syntax를 사용하여 part of expressions로 resources에 access가능
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
- providing parameters에 의해서 format strings and plurals를 evaluated 가능
android:text="@{@string/nameFormat(firstName, lastName)}"
android
:text="@{@plurals/banana(bananaCount)}"
- plural이 multiple parameters를 사용하는 경우, all parameters를 사용해야 한다.

 
Have an orange
 
Have %d oranges

android
:text="@{@plurals/orange(orangeCount, orangeCount)}"
- some resources는 explicit type evaluation이 필요
TypeNormal ReferenceExpression Reference
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
Animator@animator@animator
StateListAnimator@animator@stateListAnimator
color int@color@color
ColorStateList@color@colorStateList


Data objects

- POJO는 data binding에 사용될 수 있겠지만, POJO를 수정해도 UI update가 되는건 아니다.

- data binding의 real power는 object data가 변경될 때 이를 notify 하도록 ability하는것.

- three different data change notification mechanisms

1. Observable Objects

- implementing android.databinding.Observable interface

- binding to attach single listener to bound object

- insten for change of all properties on that object


- listener를 add and remove하는 mechanism이 있지만 notifying은 developer가 결정

- listenr registration mechanism을 implement하기 위해 android.databinding.BaseObservable 이라는 base class가 있다.

- data class implementer는 properties가 언제 변경되는지 notifying해야하는 책임이 있다.

- 이 작업은 android.databinding.Bindable annotation을 getter에 assigning하고 setter에서 notifying하는 식으로 수행된다.


- android.databinding.Bindable annotaton은 compilation동안에 BR class file에 entry generates

- BR class file은 module package에서 생성된다.

- data class의 base class를 변경할 수 없는경우, android.databinding.Observable interface는 convenient android.databinding.PropertyChangeRegistry를 사용하여 listeners를 효율적으로 store and notify 하도록 implement 가능하다.

private static class User extends BaseObservable {
   
private String firstName;
   
private String lastName;
   
@Bindable
   
public String getFirstName() {
       
return this.firstName;
   
}
   
@Bindable
   
public String getLastName() {
       
return this.lastName;
   
}
   
public void setFirstName(String firstName) {
       
this.firstName = firstName;
       notifyPropertyChanged
(BR.firstName);
   
}
   
public void setLastName(String lastName) {
       
this.lastName = lastName;
       notifyPropertyChanged
(BR.lastName);
   
}
}


2. ObservableFields

- 작업해야할 내용은 대부분 android.databinding.Observable class make이므로 save time or have few properties이라면 android.databinding.ObservableField and siblings를 사용

- android.databinding.ObservableBoolean

- android.databinding.ObservableByte

- android.databinding.ObservableChar

- android.databinding.ObservableShort

- android.databinding.ObservableInt

- android.databinding.ObservableLong

- android.databinding.ObservableFloat

- android.databinding.ObservableDouble

- android.databinding.ObservableParcelable.ObservableFields

- 위 siblings들은 single field를 가지고 있는 self-contained observable objects 이다.


- primitive versions는 access작업 중 boxing and unboxing을 avoid.

- 사용하려면 data class에 public final field를 만든다.

private static class User {
   
public final ObservableField<String> firstName =
       
new ObservableField<>();
   
public final ObservableField<String> lastName =
       
new ObservableField<>();
   
public final ObservableInt age = new ObservableInt();
}
user.firstName.set("Google");
int age = user.age.get();

3. Observable Collection

- some applications use more dynamic structures to hold data

- observable collection은 이러한 data objects에 대한 access key allow

- android.databinding.ObservableArrayMap은 string key reference type에 유용

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user
.put("firstName", "Google");
user
.put("lastName", "Inc.");
user
.put("age", 17);
<data>
   
<import type="android.databinding.ObservableMap"/>
   
<variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>

<TextView
   
android:text='@{user["lastName"]}'
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>
<TextView
   
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>


- android.databinding.ObservableArrayList는 integer key reference에 유용

ObservableArrayList<Object> user = new ObservableArrayList<>();
user
.add("Google");
user
.add("Inc.");
user
.add(17);
<data>
   
<import type="android.databinding.ObservableList"/>
   
<import type="com.example.my.app.Fields"/>
   
<variable name="user" type="ObservableList&lt;Object&gt;"/>

</data>

<TextView
   
android:text='@{user[Fields.LAST_NAME]}'
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>
<TextView
   
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>

Generated Binding

- generated binding class links layout variables with layout Views 

- generated binding classes all extend android.databinding.ViewDataBinding


- Creating

- Views with expressions within the layout에 binding하기 전에 View hierarchy가 disturbed하지 않도록 binding should be created soon after inflation


- layout에 binding하는 몇가지 방법이 있다.

- inflate method는 View hierarchy inflate and binds all one step

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

- inflated와 bind 분리

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
- binding을 미리 알 수 없는경우 android.databinding.DataBindingUtil 사용
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent
, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

- Views With IDs
- layout 각 View ID에 대한 public final field generated
- binding은 View hierarchy에서 single pass를 수행하여 IDs가 있는 View를 extracting
- this mechanism은 findViewById를 calling 하는것보다 빠를 수 있다.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   
<data>
       
<variable name="user" type="com.example.User"/>
   
</data>
   
<LinearLayout
       
android:orientation="vertical"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent">
       
<TextView android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:text="@{user.firstName}"
   
android:id="@+id/firstName"/>
       
<TextView android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:text="@{user.lastName}"
 
android:id="@+id/lastName"/>
   
</LinearLayout>
</layout>
public final TextView firstName;
public final TextView lastName;


- Variables

- each variable은 accessor methods가 제공

<data>
   
<import type="android.graphics.drawable.Drawable"/>
   
<variable name="user"  type="com.example.User"/>
   
<variable name="image" type="Drawable"/>
   
<variable name="note"  type="String"/>
</data>
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);


- ViewStubs

ViewStub는 normal Views와는 약간 다르다.

- start off invisible 

- visible or explicitly inflate하게되면 replace themselves in the layout by inflating another layout


- essentially disappears from View hierarchy이기 때문에 binding object View도 collection을 allow하기위해 사라져야 한다.

- View는 final이므로 android.databinding.ViewStubProxy object가 ViewStub대신에 사용되어 devleoper가 ViewStub에 access가능하며, ViewStub가 inflated되었을 때 inflated View hierarchy에 대한 access 권한도 제공


- inflating another layout일 때 새로운 layout을 위한 binding이 established되어야 한다.

- ViewStubProxy는 must listen to the ViewStub's ViewStub.OnInflateListener and 해당 time에 establish the binding

- only one can exist, ViewStubProxy는 developer call establishing binding 후에 set an  OnInflateListener


- Advanced Binding

- Dynamic Variables

- specific binding class를 알수 없는 상황이 있다. (ex: RecyclerView.Adapter)

- it still must assign binding value during the onBindViewHolder(VH, int).

- BindingHolder는 android.databinding.ViewDataBinding을 return하는 getBinding method가 있다.

public void onBindViewHolder(BindingHolder holder, int position) {
   
final T item = mItems.get(position);
   holder
.getBinding().setVariable(BR.item, item);
   holder
.getBinding().executePendingBindings();

}

- Immediate Binding
- variable or observable changes, binding은 next frame전에 will be scheduled to change
- but binding must be executed immediately
- android.databinding.ViewDataBinding.executePendingBindings() method 사용

- Background Thread

- collection이 아니라면 background thread에서 data model을 변경 가능하다.

- data binding은 evaluating to avoid any concurrency issues 동안에 each variable / field localized.


Attribute Setters

- bound value changes될 때마다 generated binding class는 binding expression이 있는 View에서 setter method를 call해야한다.

- data binding framework에는 value를 set하기 위해 call할 method를 customize 하는 방법이 있다.


- Automatic Setters

- attribute의 경우 data binding은 setAttribute method를 찾으려고 시도

- attribute의 namespace는 중요하지 않고 attribute name자체가 중요

- ex) TextView attribute인 android:text와 연결된 expression으로 setText(String)을 찾는다.

- expression이 int를 return하면 data binding은 setText(int)를 찾는다.

- casting if necessary, expression return correct type하도록 주의할것

- no attribute exists given name일때도 data binding이 work

- data binding을 사용하여 any setter attributes를 쉽게 create가능

- ex) DrawerLayout support에는 attribute는 없지만 많은 setters가 있다. automatic setter를 사용하여 이것들 중 하나를 사용 가능

<android.support.v4.widget.DrawerLayout
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
app:scrimColor="@{@color/scrim}"
   
app:drawerListener="@{fragment.drawerListener}"/>

- Renamed Setters

- some attributes는 don't match by name인 setter가 있다

- 이러한 method의 경우 android.databinding.BindingMethod를 통해서 attribute associated setter

- must associated class이여야 하고, each renamed method마다 android.databinding.BindingMethod annotation이 붙어야 한다.

- ex) android:tint attribute는 setTint가 아니라 setImageTintList(ColorStateList) 와 연관

@BindingMethods({
       
@BindingMethod(type = "android.widget.ImageView",
                      attribute
= "android:tint",
                      method
= "setImageTintList"),
})

- android framework attributes already been implemented이기 때문에 developers가 setter rename할 필요는 없을것으로 보인다.


- Custom Setters

- some attribute need custom binding logic

- ex) android:paddingLeft 관련 setter가 없다. 대신 setPadding(left, top, right, bottom)이 있다.

- android.databinding.BindingAdatper annotation이 있는 static binding adapter method는 attribute에 대한 setter가 called되는 방법을 customize할수 있도록 한다.

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view
.setPadding(padding,
                   view
.getPaddingTop(),
                   view
.getPaddingRight(),
                   view
.getPaddingBottom());
}

- Binding adapter는 other types customization에 useful

- ex) custom loader는 can be called off-thread to load image

- custom adapter는 conflict발생시 data binding default adapter override!


- multiple parameters receive adapters도 가능

- imageUrl and error가 ImageView에 사용되고, imageUrl is string and error is drawable인 경우 adapter가 called

- custom namespace are ignored during matching

- android namespace용 adapters write가능

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

- binding adapter method는 handler에서 optionally take old values

- old and new values take method는 attributes에 대한 all old values가 먼저 와야하며 그 다음에 new values followed

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   
if (oldPadding != newPadding) {
       view
.setPadding(newPadding,
                       view
.getPaddingTop(),
                       view
.getPaddingRight(),
                       view
.getPaddingBottom());
   
}
}

- event handler only used interface or abstract classes with one abstract method

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       
View.OnLayoutChangeListener newValue) {
   
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
       
if (oldValue != null) {
            view
.removeOnLayoutChangeListener(oldValue);
       
}
       
if (newValue != null) {
            view
.addOnLayoutChangeListener(newValue);
       
}
   
}
}

- listener가 multiple methods를 가지고 있으면, must be split multiple listeners

- ex) View.OnAttachStateChangeListener -> onViewAttachedToWindow() and onViewDetachedFromWindow()

- attributes and handler를 구분하기 위해 two interfaces must create

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
   
void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
   
void onViewAttachedToWindow(View v);
}

-  하나의 listener를 변경하면 다른 listener에도 영향을 미치기 때문에, one for each attribute and for both 설정해야 하는 must have three different binding adapter가 있어야 한다.

- 아래 ex는 add and remove listener를 하기 때문에 약간 복잡하다.

android.databinding.adapters.ListenerUtil class는 previous listeners가  Binding Adapter에서 removed될 수 있도록 keep tracking

@TargetApi(VERSION_CODES.HONEYCOMB_MR1) annotation에 의해서 Honeycomb MR1 and new devices 이상에서만 동작해야함을 알고 있다.

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener
(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener
(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
       
final OnViewAttachedToWindow attach) {
   
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
       
final OnAttachStateChangeListener newListener;
       
if (detach == null && attach == null) {
            newListener
= null;
       
} else {
            newListener
= new OnAttachStateChangeListener() {
               
@Override
               
public void onViewAttachedToWindow(View v) {
                   
if (attach != null) {
                        attach
.onViewAttachedToWindow(v);
                   
}
               
}

               
@Override
               
public void onViewDetachedFromWindow(View v) {
                   
if (detach != null) {
                        detach
.onViewDetachedFromWindow(v);
                   
}
               
}
           
};
       
}
       
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener
, R.id.onAttachStateChangeListener);
       
if (oldListener != null) {
            view
.removeOnAttachStateChangeListener(oldListener);
       
}
       
if (newListener != null) {
            view
.addOnAttachStateChangeListener(newListener);
       
}
   
}
}


Converters

- Object Conversions

- binding expressions에서 Object returned, setter는 automatic, renamed, and custom setter중에서 선택된다.

- Object는 선택된 setter의 parameter type으로 casting 된다.

- ObservableMaps를 사용해서 data hold일 경우 편리하다.


- userMap은 return Object and Object will be setter setText(CharSequence) automatically cast parameter type

- parameter type에 confusion이 있을경우 need cast expression

<TextView
   
android:text='@{userMap["lastName"]}'
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>

- Custom Conversions

- 때로는 specific types간에 automatic conversions 되어야 한다.

- background는 Drawable을 사용하지만, color is an integer.

- Drawable expected and integer returned, int should converted to ColorDrawable (int -> ColorDrawable)

- 이러한 conversion은 BindingConversion annotation이 있는 static method에서 수행된다.

<View
   
android:background="@{isError ? @color/red : @color/white}"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   
return new ColorDrawable(color);
}

- conversions는 setter level에서만 일어나므로, not allowed mix types

<View
   
android:background="@{isError ? @drawable/error : @color/white}"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"/>

Android Studio Support for Data Binding

- 여러가지 data binding code에 대해서 code editing features 지원

- Preview pane displays default values for data binding expressions

<TextView android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
android:text="@{user.firstName, default=PLACEHOLDER}"/>

- 물론 tools도 사용 가능하다.

Designtime Layout Attributes

공유

댓글