android jetpack data binding
https://developer.android.com/topic/libraries/data-binding
android official docs
https://youtu.be/v4XO_y3RErI
40분 분량 java coding with mitch
product는 xml안에서 사용할 변수 이름이고 보통 obj, data class obj를 담게 된다. type은 obj의 class
일반 함수를 사용할수 있다. (정확히 어떤 함수가 일반 함수에 해당하는지는 확인요망)
외부의 class를 xml내로 가져와 사용하고자 하는 경우
이 강의에서는 StringUtil이라는 helper class를 xml에 가져와 사용하는 것을 가정한다.
xml안 변수로 사용하게될 class Product 의 내부함수를 xml에서 사용하는 것을 보여준다.
xml안 변수로 사용하게될 class Product 의 내부함수를 xml에서 사용하는 것을 보여준다.
xml내로 android view class를 import 해서 가져와 view의 속성을 변경하는 것을 보여준다.
클릭이벤트를 이 강의에서는 activity가 처리한다고 가정한다.
클릭이벤트를 위한 interface를 implement한다.
클릭이벤트를 처리하는 class를 변수로 가져온다.
binding adapter를 통해 view를 외부로 보내 추가 처리를 하는 것을 보여준다. 이강의에서는 glide를 사용하는 것을 보여준다.
app:바인딩어댑터이름 즉 @BindingAdapter() 안에 들어간 이름
========================================================================================================================
https://medium.com/androiddevelopers/no-more-findviewbyid-457457644885
android {
…
dataBinding.enabled = true
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</layout>
HelloWorldBinding binding =
DataBindingUtil.setContentView(this, R.layout.hello_world);
binding.hello.setText("Hello World"); // you should use resources!
https://medium.com/androiddevelopers/android-data-binding-that-include-thing-1c8791dd6038
hello_world.xml<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<include
android:id="@+id/included"
layout="@layout/included_layout"/>
</LinearLayout>
</layout>
included_layout.xml<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/world"/>
</layout>
HelloWorldBinding binding =
HelloWorldBinding.inflate(getLayoutInflater());
binding.hello.setText(“Hello”);
binding.included.world.setText(“World”);
https://medium.com/google-developers/android-data-binding-adding-some-variability-1fe001b3abcc
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.myapp.model.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:src="@{user.image}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{user.firstName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
You can see in the layout above that the Views no longer have IDs. (above)
private void setUser(User user, ViewGroup root) {
UserInfoBinding binding =
UserInfoBinding.inflate(getLayoutInflater(), root, true);
binding.setUser(user);
}
-------------------------------------------------------------------------------------------
user_name.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.myapp.model.User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<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>
<?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="user"
type="com.example.myapp.model.User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@{user.image}"/>
<include
layout="@layout/user_name"
app:user="@{user}"/>
</LinearLayout>
</layout>
https://medium.com/androiddevelopers/android-data-binding-express-yourself-c931d1f90dfe
The expression parser automatically tries to find the Java Bean accessor name (getXxx() or isXxx()) for your property. The same expression will work fine when your class has accessor methods. (기본적으로 xml에 @{a.bbb}가 있다고 한다면 parser가 알아서 a 클래스에서 getbbb()를 찾고 없다면 bbb()를 사용한다는 이야기 이다.)
If it can’t find a method named like getXxx(), it will also look for a method named xxx(), so you can use user.hasFriend to access method hasFriend().
Android Data Binding expression syntax also supports array access using brackets, just like Java:
android:text="@{user.friends[0].firstName}"
It also allows almost all java language expressions, including method calls, ternary operators, and math operations.
there is a null-coalescing operator ?? to shorten the simple ternary expressions:(즉 ?? 사용가능하다는 이야기)
android:text=”@{user.firstName ?? user.userName}”
which is essentially the same as:
android:text=”@{user.firstName != null ? user.firstName : user.userName}”
One really cool thing you can do with binding expressions is use resources:
android:padding=”@{@dim/textPadding + @dim/headerPadding}
You can also use string, quantity, and fraction formatting following the syntax from Resources methods getString, getQuantityString, and getFraction. You just pass the parameters as arguments to the resource:
android:text=”@{@string/nameFormat(user.firstName, user.lastName)}”
One very convenient thing is that data binding expressions always check for null values during evaluation. That means that if you have an expression like:
android:text=”@{user.firstName ?? user.userName}”
If user is null, user.firstName and user.userName will evaluate to null and the text will be set to null. No NullPointerException.
This doesn’t mean that it is impossible to get a NullPointerException. If, for example, you have an expression:
android:text=”@{com.example.StringUtils.capitalize(user.firstName)}”
And your StringUtils had:
public static String capitalize(String str) {
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
}
You’ll definitely see a NullPointerException when a null is passed to capitalize.
In the example above, the expression to capitalize the name was very long. What we really want is to be able to import types so that they can be used as a shortened expression. You can do that by importing them in the data section:
<data>
<variable
name="user"
type="com.example.myapp.model.User"/>
<import
type="com.example.StringUtils"/>
</data>
Now our expression can be simplified to:
android:text=”@{StringUtils.capitalize(user.firstName)}”
(본래는 android:text=”@{com.example.StringUtils.capitalize(user.firstName)}” 이렇게 쓸것을 줄인결과)
Expressions are pretty much Java syntax with the few exceptions mentioned above. If you think it will work, it probably will, so just give it a go.
https://medium.com/androiddevelopers/android-data-binding-the-big-event-2697089dd0d7
data binding에서 listener를 view에 덧붙이는 방법은 아래와 같이3가지가 있다.
<View android:onClickListener="@{callbacks.clickListener}" .../>
<View android:onClick="@{listeners.clickListener}" .../>
public class Callbacks {
public View.OnClickListener clickListener;
}
<EditText
android:afterTextChanged="@{callbacks::nameChanged}" .../>
public class Callbacks {
public void nameChanged(Editable editable) {
//...
}
}
아래와 같이 논리구조를 추가해서 상황에 따라 다른 리스너를 이용할수도 있다.
<EditText android:afterTextChanged=
"@{user.hasName?callbacks::nameChanged:callbacks::idChanged}"
.../>
https://developer.android.com/reference/android/text/TextWatcher#afterTextChanged(android.text.Editable)
본래 afterTextChanged는 Editable 를 parameter로 받는다. 아래서 e는 Editable이다.
<EditText
android:afterTextChanged="@{(e)->callbacks.textChanged(user, e)}"
... />
public class Callbacks {
public void textChanged(User user, Editable editable) {
if (user.hasName()) {
//...
} else {
//...
}
}
}
https://medium.com/androiddevelopers/android-data-binding-lets-flip-this-thing-dc17792d6c24
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.firstName}"/>
아래에서는 xml에 있는 다른 view의 id를 이용 접근하는 것을 보여주고 있다.
<CheckBox
android:id="@+id/showName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/> <TextView
android:text="@{user.firstName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{showName.checked ? View.VISIBLE : View.GONE}"
/>
<CheckBox
android:id="@+id/showName"
android:focusableInTouchMode="@{model.allowFocusInTouchMode}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/> <TextView
android:text="@{user.firstName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusableInTouchMode="@{showName.focusableInTouchMode}"
/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/firstName"
android:text="@={user.firstName}" /> <CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{()->handler.checked(firstName)}" />
======================================================================================================================
https://codelabs.developers.google.com/codelabs/kotlin-android-training-data-binding-basics/index.html?index=..%2F..android-kotlin-fundamentals#3
ViewDataBinding.invalidateAll()
Invalidates all binding expressions and requests a new rebind to refresh UI.
binding.apply { myName?.nickname = nicknameEdit.text.toString() invalidateAll() ... }
https://codelabs.developers.google.com/codelabs/kotlin-android-training-create-and-add-fragment/index.html?index=..%2F..android-kotlin-fundamentals#3
fragment에서 data binding을 사용하는 경우
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val binding = DataBindingUtil.inflate<FragmentTitleBinding>(inflater, R.layout.fragment_title,container,false) return binding.root }
https://codelabs.developers.google.com/codelabs/kotlin-android-training-interacting-with-items/index.html?index=..%2F..android-kotlin-fundamentals#3
data binding을 사용한 recyclerview에서 클릭 처리
package com.example.android.trackmysleepquality.sleeptracker import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.example.android.trackmysleepquality.database.SleepNight import com.example.android.trackmysleepquality.databinding.ListItemSleepNightBinding class SleepNightAdapter(val clickListener: SleepNightListener) : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) { override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(getItem(position)!!, clickListener) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder.from(parent) } class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){ fun bind(item: SleepNight, clickListener: SleepNightListener) { binding.sleep = item binding.clickListener = clickListener binding.executePendingBindings() } companion object { fun from(parent: ViewGroup): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = ListItemSleepNightBinding.inflate(layoutInflater, parent, false) return ViewHolder(binding) } } } } class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() { override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean { return oldItem.nightId == newItem.nightId } override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean { return oldItem == newItem } } class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) { fun onClick(night: SleepNight) = clickListener(night.nightId) }
data binding 을 fragment에서 사용하는 경우
https://stackoverflow.com/a/34719627/3151712
The data binding implementation must be in the onCreateView method of the fragment
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { MartianDataBinding binding = DataBindingUtil.inflate( inflater, R.layout.martian_data, container, false); View view = binding.getRoot(); //here data must be an instance of the class MarsDataProvider binding.setMarsdata(data); return view; }
custom view , custom attributes
https://youtu.be/CoOcEv6sFkI
google official doc for two way data binding
https://developer.android.com/topic/libraries/data-binding/two-way
https://youtu.be/T-nQP9fidKU
Bindable must be on a member in an Observable class. MainViewModel is not Observable