View Binding, Data Binding (android developers)
- View Binding
- Data Binding Library
View Binding
https://developer.android.com/topic/libraries/view-binding#java
Data Binding
https://developer.android.com/topic/libraries/data-binding#java
-
View Binding
모듈 레벨 build.gradle 파일
android {
...
buildFeatures {
viewBinding true
}
}
바인딩 클래스를 만들지 않으려면 루트 뷰에다가 추가
<LinearLayout ... tools:viewBindingIgnore="true" > ... </LinearLayout>
바인딩 클래스 자동 생성 (클래스 이름은 camel case 그리고 뒤에 Binding이 붙음)
result_profile.xml -> ResultProfileBinding
<LinearLayout ... > <TextView android:id="@+id/name" /> <ImageView android:cropToPadding="true" /> <Button android:id="@+id/button" android:background="@drawable/rounded_button" /> </LinearLayout>
Use view binding in activities
private ResultProfileBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ResultProfileBinding.inflate(getLayoutInflater()); View view = binding.getRoot(); setContentView(view); }
the getRoot() method in the ResultProfileBinding class returns the LinearLayout root view.
binding.getName().setText(viewModel.getName()); binding.button.setOnClickListener(new View.OnClickListener() { viewModel.userClicked() });
Use view binding in fragments
private ResultProfileBinding binding; @Override public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = ResultProfileBinding.inflate(inflater, container, false); View view = binding.getRoot(); return view; } @Override public void onDestroyView() { super.onDestroyView(); binding = null; }
Note: Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment’s onDestroyView() method.
binding.getName().setText(viewModel.getName()); binding.button.setOnClickListener(new View.OnClickListener() { viewModel.userClicked() });
2. Data Binding Library
Overview
액티비티에서 사용하는 바인딩 방법
TextView textView = findViewById(R.id.sample_text); textView.setText(viewModel.getUserName());
Data Binding Library 를 사용해서 레이아웃 파일에서 직접 바인딩 하는 방법
<TextView
android:text="@{viewmodel.userName}" />
레이아웃에서 바인딩을 하게 되면 액티비티에서의 많은 UI 프레임워크 호출을 없애주고 유지보수가 더 간단하고 쉽게 만든다.
또한 앱의 성능을 향상시켜주고 메모리릭이나 널포인터예외 방지에 도움을 준다.
- Binding Library 는 자동으로 바인딩에 필요한 클래스를 생성한다.
- 세가지의 기능을 제공한다. (imports, variables, and includes)
- layout 태그가 최상위, 자식 태그로 data 태그와 루트 뷰
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.myapp.data.ViewModel" />
</data>
<ConstraintLayout... /> <!-- UI layout's root element -->
</layout>
Get started
Android 4.0 (API level 14) or higher.
build.gradle file in the app module
android {
...
buildFeatures {
dataBinding true
}
}
레이아웃 편집기의 미리보기에서 default 값이 표시된다.
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=my_default}"/>
Layouts and binding expressions
데이터 바인딩 레이아웃 파일은 layout 루트, data 태그, view 루트 태그로 구성된다.
<?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 태그에 있는 user 변수는 이 레이아웃에서 사용된다.
<variable name="user" type="com.example.User" />
문법 @{} 사용
TextView 의 text 는 user 변수의 firstName 프로퍼티로 설정된다.
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />
Data object
일반 엔티티
public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
getter, setter 이용 엔티티
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
레이아웃 파일들은 바인딩 클래스가 자동으로 생성된다.
activity_main.xml -> ActivityMainBinding
추천되는 방식
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("Test", "User"); binding.setUser(user); }
LayoutInflater 을 이용하는 방법
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
Fragment, ListView, or RecyclerView adapter 내부에서 데이터바인딩 사용
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); // or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Expression language
Mathematical + – / * %
String concatenation +
Logical && ||
Binary & | ^
Unary + – ! ~
Shift >> >>> <<
Comparison == > < >= <= (Note that < needs to be escaped as <)
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}'
없는 키워드
this
super
new
Explicit generic invocation
Null coalescing operator
왼쪽이 null 이 아니면 왼쪽, null 이면 오른쪽
android:text="@{user.displayName ?? user.lastName}"
위와 동일
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
Property references
fields, getters, and ObservableField objects 에서도 같음
android:text="@{user.lastName}"
Avoiding null pointer exceptions
생성된 바인딩 클래스는 자동을 null 체크를 하고 null 포인터 예외를 방지한다.
@{user.name} 에서 user 가 null 이이면 user.name (string)의 디폴트 값인 null 사용
@{user.age} 에서 user 가 null 이면 user.age (int)의 디폴트 값인 0 사용
View references
표현식은 ID 로 다른 뷰를 참조할 수 있다.
android:text="@{exampleText.text}"
바인딩 클래스는 ID 를 camel case 로 컨버팅한다.
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/> // EditText 뷰의 text 를 참조
Collections
arrays, lists, sparse lists, maps 같은 컬렉션들은 [] 연산자를 사용해서 접근할 수 있다.
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
map 에서 @{map[key]} 또는 @{map.key} 를 사용할 수 있다.
You can also refer to a value in the map using the object.key notation. For example, @{map[key]} in the example above can be replaced with @{map.key}.
String literals
single quotes 와 double quotes 사용
android:text='@{map["firstName"]}'
double quotes 와 back quotes 사용
android:text="@{map[`firstName`]}"
Resources
리소스 참조
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
파라미터 제공
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
프로퍼티 레퍼런스와 뷰 레퍼런스를 리소스 파라미터로 제공
android:text="@{@string/example_resource(user.lastName, exampleText.text)}"
다수의 파라미터가 필요하다면 모든 파라미터를 제공해야한다.
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
일부 리소스들은 명시적 타입 평가가 필요
| Type | Normal reference | Expression reference |
|---|---|---|
| String[] | @array | @stringArray |
| int[] | @array | @intArray |
| TypedArray | @array | @typedArray |
| Animator | @animator | @animator |
| StateListAnimator | @animator | @stateListAnimator |
| color int | @color | @color |
| ColorStateList | @color | @colorStateList |
Event handling
Method references: 리스너 메서드 시그니처에 맞는 메서드를 참조한다.
Listener bindings: 이벤트가 발생할 때 평가되는 람다 식.
Method references
메소드 핸들러에 직접 바인딩 됨
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>
Listener bindings
이벤트가 발생될 때 표현식이 실행
리턴값만 일치하면 된다.
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>
you still get null and thread safety of data binding
파라미터를 생략할 수도 있고 모두 적을 수도 있다.
아래 예에서는 view 파라미터를 지정하고 있다.
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
위의 예에서 View 파라미터를 지정했을 때.
public class Presenter { public void onSaveClick(View view, Task task){} }
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
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) { } }
onLongClick 는 boolean 값을 리턴한다.
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
표현식을 null 로 평가된다면 그 타입의 기본 값이 사용된다. 레퍼런스 타입은 null, int 형은 0, boolean 은 false.
삼항 연산자 (ternary) 에서 사용할 때 void 를 사용할 수 있다.
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Imports, variables, and includes
Imports 는 레이아웃 파일 내부에서 클래스 참조를 쉽게 해준다.
Variables 는 바인딩 표현식에서 프로퍼티를 사용할 수 있다.
Includes 는 앱에서 복잡한 레이아웃을 재사용할 수 있다.
Imports
레이아웃 파일에서 클래스 참조를 가능하게 해준다.
<data>
<import type="android.view.View"/>
</data>
View 클래스의 VISIBLE 과 GONE 상수를 참조한다.
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
Type aliases
이름 충돌이 있을 때 별칭을 부여할 수 있다.
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
Import other classes
임포트된 타입들은 variables 와 expressions 에서 타입 참조로 사용할 수 있다.
다음 예제는 변수 참조로 User 와 List<User> 을 사용한다.
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
# IDE 에서 임포트에 대해서 자동완성이 안되므로 풀 네임을 적어주어야 한다.
표현식의 일부를 캐스팅하기 위해서 타입 참조를 할 수 있다.
다음 예제는 user.connection 프로퍼티를 User 타입으로 캐스팅한다.
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
임포트된 타입은 표현식에서 static 필드와 메소드를 참조하는데 사용할 수 있다.
다음 코드는 MyStringUtils 클래스를 임포트하고 capitalize method 를 참조한다.
<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"/>
java.lang.* 자동으로 임포트된다.
Variables
data 엘리먼트 안에 여러개의 variable 를 가질 수 있다.
<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>
context 의 값은 루트뷰의 getContext() 메서드의 Context 객체이다.
Includes
Variable 은 상위 레이아웃에서 포함된 레이아웃에 전달될 수 있다. app 네임스페이스와 변수 이름을 사용해서
<?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>
데이터 바인딩은 merge 엘리먼트의 직접 자식으로 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><!-- Doesn't work -->
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
Work with observable data objects
Observable fields
ObservableBoolean
ObservableByte
ObservableChar
ObservableShort
ObservableInt
ObservableLong
ObservableFloat
ObservableDouble
ObservableParcelable
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();
Observable collections
ObservableArrayMap 클래스는 키가 String 처럼 레퍼런스 타입일 때 유용하다.
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<String, Object>"/>
</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"/>
ObservableArrayList 클래스는 키가 정수형일 때 유용하다.
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<Object>"/>
</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"/>
Observable objects
Data Binding 라이브러리는 리스너 등록 메커니즘을 등록하는 BaseObservable 클래스를 제공한다.
게터와 세터에 Bindable annotation 을 설정하고 세터의 notifyPropertyChanged() 메서드를 호출한다.
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); } }
데이터 바인딩은 모듈 패키지에 BR 클래스를 생성한다. 데이터 바인딩에 사용되는 리소스 ID 를 포함한다.
컴파일 타임에 Bindable annotation 은 BR 클래스 파일에 항목을 생성한다.
베이스 클래스를 변경할 수 없다면 PropertyChangeRegistry 객체를 사용한다.
Generated binding classes
각 레이아웃 파일에 대해서 바인딩 클래스가 생성된다.
기본적으로 바인딩되는 클래스 이름은 레이아웃 파일 이름에 기반을 둔다. Pascal case, Binding suffix
activity_main.xml -> ActivityMainBinding
Create a binding object
바인딩 객체는 레이아웃을 인플레이트 직 후에 만들어진다.
바인딩 객체와 레이아웃을 바인딩하는 하는 가장 일반적인 방법은 static 메서드를 사용하는 것이다.
바인딩 클래스의 inflate() 메서드를 사용해서 뷰 계층을 인플레이트하고 바인딩 객체에 바인딩한다.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater()); setContentView(binding.root); }
LayoutInflater 객체와 ViewGroup 객체를 가지는 inflate() 메서드의 또 다른 버전이 있다.
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false);
레이아웃이 다른 메커니즘을 사용해서 이미 인플레이트 되었다면 따로 바인딩될 수 있다.
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
때때로 바인딩 타입이 미리 알 수 었을 경우에 DataBindingUtil 클래스를 사용해서 바인딩 할 수 있다.
View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent); ViewDataBinding binding = DataBindingUtil.bind(viewRoot);
Fragment, ListView, 또는 RecyclerView adapter 내부에서 데이터 바인딩 아이템을 사용한다면 바인딩 클래스의 inflate() 메서드나 DataBindingUtil 클래스를 사용을 선호할 수 있다.
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); // or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Views with IDs
데이터 바인딩 라이브러리는 레이아웃에서 ID를 가진 각 뷰에 대해서 바인딩 클래스에 immutable 필드를 만든다.
다음 예제에서 데이터 바인딩 라이브러리는 타입 TextView 의 firstName 와 lastName 필드를 생성한다.
<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>
라이브러리는 한 번의 통과로 뷰 계층에서 ID 를 포함하는 뷰를 추출한다.
이 메커니즘은 findViewById() 메서드를 호출하는 것보다 빠르다.
ID 는 데이터바인딩이 없을 때만큼 필수적인것은 아니지만 여전히 코드에서 뷰로의 접근이 필요한 경우가 있다.
Variables
데이터 바인딩 라이브러리는 레이아웃에 선언된 각 variable 에 대해서 접근자 메서드를 생성한다.
다음 레이아웃은 user, image, note 변수에 대해서 바인딩 클래스에 게터 세터 메서드를 생성한다.
<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 에 대해서 접근자 메서드를 생성한다.
다음 레이아웃은 user, image, note 변수에 대해서 바인딩 클래스에 게터 세터 메서드를 생성한다.
Custom binding class names
기본적은 바인딩 클래스는 레이아웃 파일의 이름에 기반을 둔다.
이 클래스는 모듈 패키지의 databinding 패키지에 놓인다.
contact_item -> ContactItemBinding
모듈 패키지가 com.example.my.app 이라면 바인딩 클래스는 com.example.my.app.databinding 패키지에 놓인다.
바인딩 클래스는 data 엘리먼트의 class 어트리뷰트를 변경함으로써 이름 변경이나 다른 패키지에 놓일 수 있다.
다음 예제는 현재 모듈의 databinding 패키지에 ContactItem 바인딩 클래스를 생성한다.
<data class="ContactItem">
…
</data>
class 엘리먼트에서 점(peroid)을 앞에 붙여서 다른 패키지에 생성한다.
다음 예제는 모듈 패키지에 바인딩 클래스를 생성한다.
<data class=".ContactItem">
…
</data>
전체 패키지 이름을 사용해서 바인딩 클래스가 생성될 위치를 지정할 수 있다.
다음 예제는 com.example 패키지에 ContactItem 바인딩 클래스를 생성한다.
<data class="com.example.ContactItem">
…
</data>