SlideShare uma empresa Scribd logo
1 de 121
Baixar para ler offline
Yongjun Kim, kakaobank
@imkimkevin
Seoul
Android DataBinding
UI Modularization, ViewModel and Testing
AGENDA
Korea
● The reasons to use Data Binding
● How Data Binding works
● UI Modularization
● Unit Testing
Warming UP!
https://www.pexels.com
as Android Developer
Activity (Fragment, View)
Drawing UI
Receiving User Interactions Service Broadcast Receiver Content Provider
UI
as Android Developer
Activity (Fragment, View)
Drawing UI
Receiving User Interactions Service Broadcast Receiver Content Provider
ViewModel
Repository
Presenter
And more…as API Developer
Use Case
Business
UI
Provides

APIs
as Android Developer
Activity (Fragment, View)
Drawing UI
Receiving User Interactions Service Broadcast Receiver Content Provider
ViewModel
Repository
Presenter
And more…as API Developer
Use Case
Business
UI
Provides

APIs
Testable Code
as Android Developer
Activity (Fragment, View)
Drawing UI
Receiving User Interactions Service Broadcast Receiver Content Provider
ViewModel
Repository
Presenter
And more…as API Developer
Use Case
Business
UI
Provides

APIs
Testable Code
Presenter
The reasons to use
Data Binding
Seoul
The past : Model-View-Presenter
fun setTitleText(title: String) {
titleText.text = title
}
fun setTitleVisible(visible: Boolean) {
titleText.visibility = if (visible)
View.VISIBLE else View.GONE
}
fun setButtonEnabled(enable: Boolean) {
titleText.isEnabled = enable
}
Activity Presenter
fun init() {
if (isFirstLogin) {
view.setTitleText("Welcome To DevFest 2018")
view.setTitleVisible(true)
}
}
fun validateDateOfBirth(number: String) {
view.setButtonEnabled(number.length >= 6)
}
.
.
.
The past : Model-View-Presenter
Activity Presenter
fun init() {
if (isFirstLogin) {
view.setTitleText("Welcome To DevFest 2018")
view.setTitleVisible(true)
}
}
fun validateDateOfBirth(number: String) {
view.setButtonEnabled(number.length >= 6)
}
Giant Glue Code
if (isFirstLogin) {
view.setTitleText("Welcome To DevFest 2018")
view.setTitleVisible(true)
}
view.setButtonEnabled(number.length >= 6)
fun setTitleText(title: String) {
titleText.text = title
}
fun setTitleVisible(visible: Boolean) {
titleText.visibility = if (visible)
View.VISIBLE else View.GONE
}
fun setButtonEnabled(enable: Boolean) {
titleText.isEnabled = enable
}
.
.
.
So far, It s ok
BusinessUI
Activity
Activity
PresenterActivity
Presenter
Presenter
So far, It s ok
one to one
but testable
BusinessUI
Activity
Activity
PresenterActivity
Presenter
Presenter
So far, It s ok
one to one
but testable
non-reusable
can be solved by
- use case
- abstraction
BusinessUI
Activity
Activity
PresenterActivity
Presenter
Presenter
So far, It s ok
one to one
but testable
non-reusable
can be solved by
- use case
- abstraction
hard to maintain
BusinessUI
Activity
Activity
PresenterActivity
Presenter
Presenter
- abstraction
DataBinding
● ?
"16
https://unsplash.com
android {
. . .
dataBinding {
enabled = true
}
}
build.gradle in app module
$gradle_version = Android Plugin for Gradle 1.5.0-alpha1 and higher
android_studio_version = Android Studio 1.3 and higher
// For Kotlin
dependencies {
kapt “com.android.databinding:compiler:$gradle_version"
}
Sample Application
Sample Application
TextView
Welcome message
for Devfest Seoul of the year
Requirements
EditText
6 numbers for authentication Button
Enabled for login
with 6 numbers
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
Activity
String.format(getString(R.string.title), presenter.year)
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
“Welcome to Devfest Seoul %s”
Activity
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
presenter.year
presenter.validate(s.toString())
fun validate(number: String) {
view.setButtonEnabled(number.isCodeValid())
}
Activity Presenter
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
presenter.year
presenter.validate(s.toString())
fun validate(number: String) {
view.setButtonEnabled(number.isCodeValid())
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
Activity Presenter
Activity
view xml
Presenter
Data Binding
MVP with Data Binding
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
Activity Presenterxml Data Binding
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
setContentView(R.layout.activity_main)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this) // LiveData
binding.setPresenter(presenter)
Activity Presenterxml Data Binding
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this) // LiveData
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
setContentView(R.layout.activity_main)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this) // LiveData
binding.setPresenter(presenter)
<layout>
<android.support.constraint.ConstraintLayout …>
<TextView
android:id=“@+id/textView" ... />
<EditText
android:id=“@+id/editText" ... />
<Button
android:id=“@+id/button" ... />
</android.support.constraint.ConstraintLayout>
</layout>
<layout>
</layout>
R.layout.activity_main
Activity Presenterxml Data Binding
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.activity_main)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
setContentView(R.layout.activity_main)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this) // LiveData
binding.setPresenter(presenter)binding.setPresenter(presenter)
<layout>
<data>
<variable
name=“presenter” type=“com...MainPresenter"/>
</data>
<android.support.constraint.ConstraintLayout …>
<EditText
android:id=“@+id/editText" ... />
<data>
<variable
name=“presenter” type=“com...MainPresenter"/>
</data>
Activity Presenterxml Data Binding
delete
override fun onCreate(savedInstanceState: Bundle?) {
...
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
binding.setPresenter(presenter)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
Activity Presenterxml Data Binding
https://www.pexels.com
Activity Presenterxml Data Binding
override fun onCreate(savedInstanceState: Bundle?) {
...
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
binding.setPresenter(presenter)
textView.text = String.format(getString(R.string.title), presenter.year)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
<TextView
<layout>
...
<android.support.constraint.ConstraintLayout ...>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{@string/title(presenter.year)}” />android:text=“@{@string/title(presenter.year)}”
textView.text = String.format(getString(R.string.title), presenter.year)
TextView
android:text=“@{@string/title(presenter.year)}”
Expression Language
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{size < 15 ? View.GONE : View.VISIBLE}”
android:textColor=“@{colorText}” ???
android:text=“@{@string/title(presenter.year)}”
Expression Language
android:textColor=“#fe3dfc”
@InverseMethod
colorText.value = "#000000"
android:textColor=“#fe3dfc”
android:textColor=“@{colorText}”
@InverseMethod
android:textColor=“#fe3dfc”
android:textColor=“@{colorText}” // ERROR!!
@InverseMethod
colorText.value = "#000000"
object Converters {
@JvmStatic
@InverseMethod(“toColor")
fun fromColorString(colorString: String): Int {
return Color.parseColor(colorString)
}
@JvmStatic // Two-way Binding
fun toColor(color: Int): String {
return color.toString()
}
}
android:textColor="@{Converters.fromColorString(colorText)}"
@InverseMethod
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{size < 15 ? View.GONE : View.VISIBLE}”
android:textColor=“@{colorText}” // @InverseMethod
android:visibility=“@{title != null}” ???
android:text=“@{@string/title(presenter.year)}”
Expression Language
android:visibility=“@{title != null}”
visible | invisible | gone
@BindingConversion
object Conversions {
@JvmStatic
@BindingConversion
fun convertBooleanToVisibility(visible: Boolean)
= if (visible) View.VISIBLE else View.GONE
}
android:visibility=“@{title != null}”
visible | invisible | gone
Normally
1. visible | invisible ???
2. visible | gone ???
android:visibility=“@{title != null}”
visible | invisible | gone
Normally
1. visible | invisible
2. visible | gone
app:visible=“@{title != null}”
app:visibleOrGone=“@{title != null}”
@BindingAdapter
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String[] value();
boolean requireAll() default true;
}
@BindingAdapter
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String[] value();
boolean requireAll() default true;
}
// Example
@BindingAdapter("visible")
@BindingAdapter(value={"url", "size"},
requireAll=false)
app:visible=“@{title != null}”
app:visibleOrGone=“@{title != null}”
@BindingAdapter
@JvmStatic
@BindingAdapter("visible")
fun View.setVisible(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
@JvmStatic
@BindingAdapter("visibleOrGone")
fun View.setVisibleOrGone(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.GONE
}
Activity Presenterxml Data Binding
override fun onCreate(savedInstanceState: Bundle?) {
...
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
binding.setPresenter(presenter)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(...) { }
override fun afterTextChanged(s: Editable?) {
presenter.validate(s.toString())
}
override fun onTextChanged(...) { }
})
<layout>
...
<android.support.constraint.ConstraintLayout ...>
<EditText
android:id=“@+id/editText"
android:text=“@={presenter.authCode}”android:text=“@={presenter.authCode}”
EditText
class MainPresenter {
val authCode = MutableLiveData<String>()
...
}
android:text=“@={presenter.authCode)}”
Two-way data binding
android:text=“@={presenter.authCode)}”
Two-way data binding
OK
presenter.authCode = null
android:text=“@={presenter.authCode)}”
Two-way data binding
OKOK
123456 presenter.authCode = “123456”
android:text=“@={presenter.authCode)}”
Two-way data binding
OK
1234
OK
presenter.authCode = “1234”
android:text=“@={presenter.authCode)}”
InverseBindingAdapter in ActivityMainBinding.java
@InverseBindingAdapter(attribute = "android:text",
event = "android:textAttrChanged")
public static String getTextString(TextView view) {
return view.getText().toString();
}
android:text=“@={presenter.authCode)}”
TextViewBindingAdapter.setTextWatcher(this.editText,
null, null, null, editTextandroidTextAttrChanged);
BindingAdapter in ActivityMainBinding.java
private InverseBindingListener editTextandroidTextAttrChanged
= new InverseBindingListener() {
@Override
public void onChange() {
String callbackArg_0 =
TextViewBindingAdapter.getTextString(editText);
...
vmAuthCode.setValue(((String) (callbackArg_0)));
}
editTextandroidTextAttrChanged
android:text=“@={presenter.authCode)}”
TextViewBindingAdapter.setTextWatcher(this.editText,
null, null, null, editTextandroidTextAttrChanged);
BindingAdapter in ActivityMainBinding.java
private InverseBindingListener editTextandroidTextAttrChanged
= new InverseBindingListener() {
@Override
public void onChange() {
String callbackArg_0 =
TextViewBindingAdapter.getTextString(editText);
...
vmAuthCode.setValue(((String) (callbackArg_0)));
}
editTextandroidTextAttrChanged
TextViewBindingAdapter.getTextString(editText);
android:text=“@={presenter.authCode)}”
TextViewBindingAdapter.setTextWatcher(this.editText,
null, null, null, editTextandroidTextAttrChanged);
BindingAdapter in ActivityMainBinding.java
TextViewBindingAdapter.setTextWatcher
@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
"android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
public static void setTextWatcher(TextView view, final BeforeTextChanged before,
final OnTextChanged on, final AfterTextChanged after,
final InverseBindingListener textAttrChanged) {
final TextWatcher newValue;
...
newValue = new TextWatcher() {
...
@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
if (textAttrChanged != null) {
textAttrChanged.onChange();
android:text=“@={presenter.authCode)}”
Default BindingAdapters
android.databinding.adapters
@BindingMethods({
@BindingMethod(type = TextView.class, attribute =
"android:inputType", method = "setRawInputType"),
@BindingMethod(type = TextView.class, attribute =
"android:textAllCaps", method = “setAllCaps"),
...
})
public class TextViewBindingAdapter {
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
Activity Presenterxml Data Binding
override fun onCreate(savedInstanceState: Bundle?) {
...
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
binding.setPresenter(presenter)
button.setOnClickListener { }
}
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
button.setOnClickListener { }
fun setButtonEnabled(enable: Boolean) {
button.isEnabled = enable
}
<Button
…
android:enabled="@{ValidationUtil.isCodeValid(vm.authCode)}"
android:onClick="@{() -> vm.login()}"
<layout>
<data>
<import type=“com....ValidationUtil"/>
<variable
<import type=“com....ValidationUtil"/>
object ValidationUtil {
@JvmStatic
fun String?.isCodeValid() = this?.length ?: 0 >= 6
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
binding.setPresenter(presenter)
}
}
Activity
Activity xml
<layout>
<data>
<import type=“com....ValidationUtil"/>
<variable name=“presenter” type=“com...MainPresenter"/>
</data>
<android.support.constraint.ConstraintLayout ...>
<TextView
android:text=“@{@string/title(presenter.year)}” .../>
<EditText
android:text=“@={presenter.authCode}” .../>
<Button
android:enabled=“@{ValidationUtil.isCodeValid(presenter.authCode)}”
android:onClick="@{() -> presenter.login()}” .../>
</android.support.constraint.ConstraintLayout>
</layout>
class MainPresenter {
var year : String
val authCode = MutableLiveData<String>()
init {
year = "2018"
}
fun login() {
// do something!
}
}
Activity Presenterxml Data Binding
Activity
view xml
Presenter
Data Binding
AS-IS : MVP with Data Binding
class MainViewModel {
var year : String
val authCode = MutableLiveData<String>()
init {
year = "2018"
}
fun login() {
// do something!
}
}
Activity ViewModelxml Data Binding
TO-BE : MVVM with Data Binding
Activity
view xml
ViewModel
Data Binding
How DataBinding works
Seoul
OK
<android.support.constraint.ConstraintLayout ...>
...
<EditText
android:id=“@+id/editText“
android:text=“@={vm.authCode}” .../>
<Button
android:id=“@+id/button“
android:enabled="@{ValidationUtil.isCodeValid(vm.authCode)}"
OK
OK
123456
<android.support.constraint.ConstraintLayout ...>
...
<EditText
android:id=“@+id/editText“
android:text=“@={vm.authCode}” .../>
<Button
android:id=“@+id/button“
android:enabled="@{ValidationUtil.isCodeValid(vm.authCode)}"
OK
OK
123456
<android.support.constraint.ConstraintLayout ...>
...
<EditText
android:id=“@+id/editText“
android:text=“@={vm.authCode}” .../>
<Button
android:id=“@+id/button“
android:enabled="@{ValidationUtil.isCodeValid(vm.authCode)}"
How this works in generated class?
OK
OK
123456
<android.support.constraint.ConstraintLayout ...>
...
<EditText
android:id=“@+id/editText“
android:text=“@={vm.authCode}” .../>
<Button
android:id=“@+id/button“
android:enabled="@{ValidationUtil.isCodeValid(vm.authCode)}"
DirtyFlag
activity_main.xml
<layout>
<data>
<import type=“com....ValidationUtil"/>
<variable name=“vm” type=“com...MainViewModel"/>
</data>
<android.support.constraint.ConstraintLayout ...>
<TextView
android:text=“@{@string/title(vm.year)}” .../>
<EditText
android:text=“@={presenter.authCode}” .../>
<Button
android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}”
android:onClick="@{() -> vm.login()}” .../>
</android.support.constraint.ConstraintLayout>
</layout>
activity_main.xml Layout Processor
<layout>
<data>
<import type=“com....ValidationUtil"/>
<variable name=“vm” type=“com...MainViewModel"/>
</data>
<android.support.constraint.ConstraintLayout ...>
<TextView
android:text=“@{@string/title(vm.year)}” .../>
<EditText
android:text=“@={presenter.authCode}” .../>
<Button
android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}”
android:onClick="@{() -> vm.login()}” .../>
</android.support.constraint.ConstraintLayout>
</layout>
ActivityMainBinding.java Layout Processor
public class ActivityMainTestBinding extends android.databinding.ViewDataBinding implements
android.databinding.generated.callback.OnClickListener.Listener {
...
@NonNull
public final android.widget.Button button;
@NonNull
public final android.widget.EditText editText;
@NonNull
public final android.widget.TextView textView;
@NonNull
private final android.support.constraint.ConstraintLayout mboundView0;
@Nullable
private com.github.kimkevin.devfestseoul18.MainViewModel mVm;
@Nullable
private final android.view.View.OnClickListener mCallback1;
Variable
Listener
View
@NonNull
public final android.widget.Button button;
@NonNull
public final android.widget.EditText editText;
@NonNull
public final android.widget.TextView textView;
@Nullable
private com.github.kimkevin.devfestseoul18.MainViewModel mVm;
@Nullable
private final android.view.View.OnClickListener mCallback1;
ActivityMainTestBinding android.databinding.ViewDataBinding
DirtyFlag mapping
Dealing with updates
When data is changed, mDirtyFlags is updated
0 0 1
0 1 0
1 0 0
0x1L
0x2L
0x4L
/* flag mapping
flag 0 : vm.authCode
flag 1 : vm
flag 2 : null
flag mapping end*/
ActivityMainBinding.java
DirtyFlag mapping
Dealing with updates
When data is changed, mDirtyFlags is updated
0 0 1
0 1 0
1 0 0
0x1L
0x2L
0x4L
/* flag mapping
flag 0 : vm.authCode
flag 1 : vm
flag 2 : initialized
flag mapping end*/
ActivityMainBinding.java
/* flag mapping
vm.authCode : 0x1L
vm : 0x2L
initialized : 0x4L
flag mapping end*/
private boolean onChangeVmAuthCode(MutableLiveData<String> VmAuthCode,
int fieldId) {
if (fieldId == BR._all) {
synchronized(this) {
mDirtyFlags |= 0x1L;
}
return true;
}
return false;
ActivityMainBinding.java
mDirtyFlag
vm.authCode : 0x1L
/* flag mapping
vm.authCode : 0x1L
vm : 0x2L
initialized : 0x4L
flag mapping end*/
ActivityMainBinding.java
mDirtyFlag
vm : 0x2L
public void setVm(@Nullable com...MainViewModel Vm) {
this.mVm = Vm;
synchronized(this) {
mDirtyFlags |= 0x2L;
}
notifyPropertyChanged(BR.vm);
super.requestRebind();
}
/* flag mapping
vm.authCode : 0x1L
vm : 0x2L
initialized : 0x4L
flag mapping end*/
ActivityMainBinding.java
mDirtyFlag
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x4L;
}
requestRebind();
}
initialized : 0x4L
ActivityMainBinding.java
flag initialized : 0x4L
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm = MainViewModel()
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
binding.setVm(viewModel)
}
}
mDirtyFlags = 0x4L
ActivityMainBinding.java
flag vm : 0x2L
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm = MainViewModel()
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
binding.setVm(viewModel)
}
}
mDirtyFlags = 0x4L | 0x2L = 0x6L
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if ((dirtyFlags & 0x7L) != 0) {
if ((dirtyFlags & 0x6L) != 0) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if ((dirtyFlags & 0x7L) != 0) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if ((dirtyFlags & 0x4L) != 0) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if ((dirtyFlags & 0x6L) != 0) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
mDirtyFlags = 0x6L
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if ((0x6L & 0x7L) != 0) {
if ((0x6L & 0x6L) != 0) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if ((0x6L & 0x7L) != 0) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if ((0x6L & 0x4L) != 0) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if ((0x6L & 0x6L) != 0) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
mDirtyFlags = 0x6L
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if ((0x6L & 0x7L) != 0) {
if ((0x4L & 0x6L) != 0) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if ((0x4L & 0x7L) != 0) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if ((0x4L & 0x4L) != 0) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if ((0x4L & 0x6L) != 0) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
mDirtyFlags = 0x4L
1 1 0
1 1 1
0x6L
0x7L
(0x6L & 0x7L) != 0
&
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if ((0x6L & 0x7L) != 0) {
if ((0x4L & 0x6L) != 0) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if ((0x4L & 0x7L) != 0) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if ((0x4L & 0x4L) != 0) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if ((0x4L & 0x6L) != 0) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
(0x6L & 0x7L) != 0
mDirtyFlags = 0x4L
1 1 1
0
0x6L
0x7L
&
1 1 0
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if ((0x6L & 0x7L) != 0) {
if ((0x4L & 0x6L) != 0) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if ((0x4L & 0x7L) != 0) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if ((0x4L & 0x4L) != 0) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if ((0x4L & 0x6L) != 0) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
(0x6L & 0x7L) != 0
mDirtyFlags = 0x4L
1 1 1
1 1 0
0x7L
0x6L
&
0x6L 1 1 0
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if ((0x6L & 0x7L) != 0) {
if ((0x4L & 0x6L) != 0) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if ((0x4L & 0x7L) != 0) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if ((0x4L & 0x4L) != 0) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if ((0x4L & 0x6L) != 0) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
(0x6L & 0x7L) != 0
mDirtyFlags = 0x4L
1 1 1
1 1 0
0x7L
0x6L
&
0x6L 1 1 0
!= 0 true
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if (true) {
if (true) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if (true) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if (true) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if (true) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
mDirtyFlags = 0x6L
Binding
- Initialized
- set Variables
OK
1
OK
ActivityMainBinding.java
flag vm.authCode : 0x1L
Input 1
1
OK
ActivityMainBinding.java
flag vm.authCode : 0x1L
mDirtyFlags = 0x1L
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if ((0x1L & 0x7L) != 0) {
if ((0x1L & 0x6L) != 0) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if ((0x1L & 0x7L) != 0) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if ((0x1L & 0x4L) != 0) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if ((0x1L & 0x6L) != 0) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
mDirtyFlags = 0x1L
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if (true) {
if (false) {
textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear);
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if (true) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if (false) {
this.button.setOnClickListener(mCallback1);
TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null,
editTextandroidTextAttrChanged);
}
if (false) {
TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear);
}
}
mDirtyFlags = 0x1L
@Override
protected void executeBindings() {
...
dirtyFlags = mDirtyFlags;
if (true) {
if (false) {
}
// read vm.authCode.getValue()
vmAuthCodeGetValue = vmAuthCode.getValue();
// read ValidationUtil.isCodeValid(vm.authCode.getValue())
vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength);
}
if (true) {
this.button.setEnabled(vmAuthCodeLengthInt6);
TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue);
}
if (false) {
}
if (false) {
}
}
UI Modularization
Seoul
Reusable View
ItemScheduleinding.java
"95
Includes data class SpeakerSchedule(val name: String, val subject: String)
<layout>
<data>
<variable
name="schedule"
type=“com...SpeakerSchedule” />
</data>
<android.support.constraint.ConstraintLayout ...>
<TextView
android:id="@+id/subjectTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{schedule.subject}"/>
<TextView
android:id="@+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“@{schedule.name}" />
...
</android.support.constraint.ConstraintLayout>
</layout>
item_schedule.xml
"96
Includes
<layout>
<data>
<variable name="schedule"
type="com...SpeakerSchedule" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<include
android:layout=“@layout/item_schedule”
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:schedule=“@{schedule}” />
...
</android.support.constraint.ConstraintLayout>
</layout>
<include
android:layout=“@layout/item_schedule”
app:schedule=“@{schedule}” />
<variable name="schedule"
type="com...SpeakerSchedule" />
Includes
binding.schedule = SpeakerSchedule(“KimKevin”, “Android...”)
<include
android:layout=“@layout/item_schedule”
app:schedule=“@{schedule}” .../>
<variable name="schedule"
type=“com.github.kimkevin…SpeakerSchedule" />
Listing & BindingAdapter
@BindingAdapter("schedules")
fun setSchedules(container: LinearLayout,
schedules: List<SpeakerSchedule>) {
container.removeAllViews()
val inflater = container.context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
schedules.forEach {
val binding = DataBindingUtil.inflate<ItemScheduleBinding>(
inflater, R.layout.item_schedule, container, true)
binding.schedule = it
}
}
Listing & BindingAdapter
<LinearLayout
android:orientation="vertical"
app:schedules="@{schedules}" .../>
<variable name="schedules"
type="List&lt;SpeakerSchedule&gt;" />
val schedules = mutableListOf<SpeakerSchedule>().apply {
add(SpeakerSchedule("Kevin", "Android"))
add(SpeakerSchedule("David", “Data Binding”))
}
binding.schedule = schedules
"100
Listing & ViewHolder
Listing & ViewHolder
ItemViewHolder
class ItemViewHolder(v: View) : RecyclerView.ViewHolder
Listing & ViewHolder
HeaderViewHolder
ItemViewHolder
class ItemViewHolder(v: View) : RecyclerView.ViewHolder
class HeaderViewHolder(v: View) : RecyclerView.ViewHolder
Listing & ViewHolder
HeaderViewHolder
ItemViewHolder
ProgressViewHolder
class ItemViewHolder(v: View) : RecyclerView.ViewHolder
class HeaderViewHolder(v: View) : RecyclerView.ViewHolder
class ProgressViewHolder(v: View) : RecyclerView.ViewHolder
class BindingViewHolder<T: ViewDataBinding>
constructor(val binding: T): RecyclerView.ViewHolder(binding.root)
class BindingViewHolder<T: ViewDataBinding>
constructor(val binding: T): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, position: Int):
BindingViewHolder<ViewDataBinding> {
return BindingViewHolder(ItemScheduleBinding.inflate(
LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(
viewHolder: BindingViewHolder<ViewDataBinding>, position: Int) {
val binding = viewHolder.binding as ItemScheduleBinding
binding.schedule = items.get(position)
}
Adapter
Unit Testing
Seoul
TextView
Welcome message
for Devfest Seoul of the year
Requirements
EditText
6 numbers for authentication Button
Enabled for login
with 6 numbers
TextView
Welcome message
for Devfest Seoul of the year
Requirements
EditText
6 numbers for authentication Button
Enabled for login
with 6 numbers
Business
UI
<TextView
android:text=“@{@string/title(vm.year)}”
.../>
???
<TextView
android:text=“@{@string/title(vm.year)}”
.../>
Business
UI
???
<TextView
android:text=“@{@string/title(vm.year)}”
.../>
Business
UI
vm.year
<TextView
android:text=“@{@string/title(vm.year)}”
.../>
Business
UI
@Test
fun initVm_setYear() {
vm = MainViewModel()
assertTrue(vm.year, deafest.year)
}
val devfest = repository.getDevfest()
vm.year = devfest.year
MainViewModel
<Button
android:enabled=“@{vm.authCode >= 6}”
.../>
Business
UI
<Button
android:enabled=“@{vm.authCode >= 6}”
.../>
???
<Button
android:enabled=“@{vm.authCode >= 6}”
.../>
Business
UI
vm.authCode >= 6
<Button
android:enabled=“@{vm.authCode >= 6}”
.../>
Business
UI
fun String?.isCodeValid()
= this?.length ?: 0 >= 6
<Button
android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}”
.../>
Business
UI
<Button
android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}”
.../>
@Test
fun validateLessThan6Numbers() {
assertFalse(“1234".isCodeValid())
}
Business
UI
<Button
android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}”
.../>
@Test
fun validate6Numbers() {
assertTrue("123456".isCodeValid())
}
Business
UI
Seoul
Thank you
Yongjun Kim, kakaobank
@imkimkevin
Q&A
imkimkevin@gmail.com
https://www.facebook.com/imkimkevin
Busan

Mais conteúdo relacionado

Semelhante a Android DataBinding (ViewModel, UI Modularization and Testing)

Declarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeDeclarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeRamon Ribeiro Rabello
 
CodeMash - Building Rich Apps with Groovy SwingBuilder
CodeMash - Building Rich Apps with Groovy SwingBuilderCodeMash - Building Rich Apps with Groovy SwingBuilder
CodeMash - Building Rich Apps with Groovy SwingBuilderAndres Almiray
 
The Workflow Pattern, Composed (2021)
The Workflow Pattern, Composed (2021)The Workflow Pattern, Composed (2021)
The Workflow Pattern, Composed (2021)Zach Klippenstein
 
Svcc Building Rich Applications with Groovy's SwingBuilder
Svcc Building Rich Applications with Groovy's SwingBuilderSvcc Building Rich Applications with Groovy's SwingBuilder
Svcc Building Rich Applications with Groovy's SwingBuilderAndres Almiray
 
Building an Android app with Jetpack Compose and Firebase
Building an Android app with Jetpack Compose and FirebaseBuilding an Android app with Jetpack Compose and Firebase
Building an Android app with Jetpack Compose and FirebaseMarina Coelho
 
MOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app developmentMOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app developmentanistar sung
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackNelson Glauber Leal
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0Jeado Ko
 
React & Redux for noobs
React & Redux for noobsReact & Redux for noobs
React & Redux for noobs[T]echdencias
 
Prompt engineering for iOS developers (How LLMs and GenAI work)
Prompt engineering for iOS developers (How LLMs and GenAI work)Prompt engineering for iOS developers (How LLMs and GenAI work)
Prompt engineering for iOS developers (How LLMs and GenAI work)Andrey Volobuev
 
Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...Matteo Collina
 
JSLab. Алексей Волков. "React на практике"
JSLab. Алексей Волков. "React на практике"JSLab. Алексей Волков. "React на практике"
JSLab. Алексей Волков. "React на практике"GeeksLab Odessa
 
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bisReactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bisDroidConTLV
 
Net conf BG xamarin lecture
Net conf BG xamarin lectureNet conf BG xamarin lecture
Net conf BG xamarin lectureTsvyatko Konov
 
Designing REST API automation tests in Kotlin
Designing REST API automation tests in KotlinDesigning REST API automation tests in Kotlin
Designing REST API automation tests in KotlinDmitriy Sobko
 

Semelhante a Android DataBinding (ViewModel, UI Modularization and Testing) (20)

Declarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeDeclarative UIs with Jetpack Compose
Declarative UIs with Jetpack Compose
 
CodeMash - Building Rich Apps with Groovy SwingBuilder
CodeMash - Building Rich Apps with Groovy SwingBuilderCodeMash - Building Rich Apps with Groovy SwingBuilder
CodeMash - Building Rich Apps with Groovy SwingBuilder
 
The Workflow Pattern, Composed (2021)
The Workflow Pattern, Composed (2021)The Workflow Pattern, Composed (2021)
The Workflow Pattern, Composed (2021)
 
Svcc Building Rich Applications with Groovy's SwingBuilder
Svcc Building Rich Applications with Groovy's SwingBuilderSvcc Building Rich Applications with Groovy's SwingBuilder
Svcc Building Rich Applications with Groovy's SwingBuilder
 
mobl
moblmobl
mobl
 
Building an Android app with Jetpack Compose and Firebase
Building an Android app with Jetpack Compose and FirebaseBuilding an Android app with Jetpack Compose and Firebase
Building an Android app with Jetpack Compose and Firebase
 
shiny.pdf
shiny.pdfshiny.pdf
shiny.pdf
 
Scala on Your Phone
Scala on Your PhoneScala on Your Phone
Scala on Your Phone
 
MOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app developmentMOPCON 2014 - Best software architecture in app development
MOPCON 2014 - Best software architecture in app development
 
compose_speaker_session.pdf
compose_speaker_session.pdfcompose_speaker_session.pdf
compose_speaker_session.pdf
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com Jetpack
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0
 
React & Redux for noobs
React & Redux for noobsReact & Redux for noobs
React & Redux for noobs
 
Prompt engineering for iOS developers (How LLMs and GenAI work)
Prompt engineering for iOS developers (How LLMs and GenAI work)Prompt engineering for iOS developers (How LLMs and GenAI work)
Prompt engineering for iOS developers (How LLMs and GenAI work)
 
Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...Designing and developing mobile web applications with Mockup, Sencha Touch an...
Designing and developing mobile web applications with Mockup, Sencha Touch an...
 
JSLab. Алексей Волков. "React на практике"
JSLab. Алексей Волков. "React на практике"JSLab. Алексей Волков. "React на практике"
JSLab. Алексей Волков. "React на практике"
 
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bisReactive UI in android - Gil Goldzweig Goldbaum, 10bis
Reactive UI in android - Gil Goldzweig Goldbaum, 10bis
 
Android crashcourse
Android crashcourseAndroid crashcourse
Android crashcourse
 
Net conf BG xamarin lecture
Net conf BG xamarin lectureNet conf BG xamarin lecture
Net conf BG xamarin lecture
 
Designing REST API automation tests in Kotlin
Designing REST API automation tests in KotlinDesigning REST API automation tests in Kotlin
Designing REST API automation tests in Kotlin
 

Mais de Yongjun Kim

Android Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and TestingAndroid Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and TestingYongjun Kim
 
Google I/O 2018: All of the News from Keynote
Google I/O 2018: All of the News from KeynoteGoogle I/O 2018: All of the News from Keynote
Google I/O 2018: All of the News from KeynoteYongjun Kim
 
Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징
Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징
Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징Yongjun Kim
 
How to use vim in Android Studio, Useful customization IdeaVim
How to use vim in Android Studio, Useful customization IdeaVimHow to use vim in Android Studio, Useful customization IdeaVim
How to use vim in Android Studio, Useful customization IdeaVimYongjun Kim
 
플리토 코드리뷰 - Code Review in Flitto
플리토 코드리뷰 - Code Review in Flitto플리토 코드리뷰 - Code Review in Flitto
플리토 코드리뷰 - Code Review in FlittoYongjun Kim
 

Mais de Yongjun Kim (6)

Android Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and TestingAndroid Jetpack: ViewModel and Testing
Android Jetpack: ViewModel and Testing
 
Google I/O 2018: All of the News from Keynote
Google I/O 2018: All of the News from KeynoteGoogle I/O 2018: All of the News from Keynote
Google I/O 2018: All of the News from Keynote
 
Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징
Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징
Android Studio에서 vim사용과 오픈소스 ideavim 커스터마이징
 
How to use vim in Android Studio, Useful customization IdeaVim
How to use vim in Android Studio, Useful customization IdeaVimHow to use vim in Android Studio, Useful customization IdeaVim
How to use vim in Android Studio, Useful customization IdeaVim
 
Where is CEO?
Where is CEO?Where is CEO?
Where is CEO?
 
플리토 코드리뷰 - Code Review in Flitto
플리토 코드리뷰 - Code Review in Flitto플리토 코드리뷰 - Code Review in Flitto
플리토 코드리뷰 - Code Review in Flitto
 

Último

introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfVishalKumarJha10
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsJhone kinadey
 
10 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 202410 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 2024Mind IT Systems
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplatePresentation.STUDIO
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnAmarnathKambale
 
How to Choose the Right Laravel Development Partner in New York City_compress...
How to Choose the Right Laravel Development Partner in New York City_compress...How to Choose the Right Laravel Development Partner in New York City_compress...
How to Choose the Right Laravel Development Partner in New York City_compress...software pro Development
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfproinshot.com
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension AidPhilip Schwarz
 

Último (20)

introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
10 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 202410 Trends Likely to Shape Enterprise Technology in 2024
10 Trends Likely to Shape Enterprise Technology in 2024
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
How to Choose the Right Laravel Development Partner in New York City_compress...
How to Choose the Right Laravel Development Partner in New York City_compress...How to Choose the Right Laravel Development Partner in New York City_compress...
How to Choose the Right Laravel Development Partner in New York City_compress...
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
Exploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdfExploring the Best Video Editing App.pdf
Exploring the Best Video Editing App.pdf
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 

Android DataBinding (ViewModel, UI Modularization and Testing)

  • 1. Yongjun Kim, kakaobank @imkimkevin Seoul Android DataBinding UI Modularization, ViewModel and Testing
  • 2. AGENDA Korea ● The reasons to use Data Binding ● How Data Binding works ● UI Modularization ● Unit Testing
  • 4.
  • 5. as Android Developer Activity (Fragment, View) Drawing UI Receiving User Interactions Service Broadcast Receiver Content Provider UI
  • 6. as Android Developer Activity (Fragment, View) Drawing UI Receiving User Interactions Service Broadcast Receiver Content Provider ViewModel Repository Presenter And more…as API Developer Use Case Business UI Provides APIs
  • 7. as Android Developer Activity (Fragment, View) Drawing UI Receiving User Interactions Service Broadcast Receiver Content Provider ViewModel Repository Presenter And more…as API Developer Use Case Business UI Provides APIs Testable Code
  • 8. as Android Developer Activity (Fragment, View) Drawing UI Receiving User Interactions Service Broadcast Receiver Content Provider ViewModel Repository Presenter And more…as API Developer Use Case Business UI Provides APIs Testable Code Presenter
  • 9. The reasons to use Data Binding Seoul
  • 10. The past : Model-View-Presenter fun setTitleText(title: String) { titleText.text = title } fun setTitleVisible(visible: Boolean) { titleText.visibility = if (visible) View.VISIBLE else View.GONE } fun setButtonEnabled(enable: Boolean) { titleText.isEnabled = enable } Activity Presenter fun init() { if (isFirstLogin) { view.setTitleText("Welcome To DevFest 2018") view.setTitleVisible(true) } } fun validateDateOfBirth(number: String) { view.setButtonEnabled(number.length >= 6) } . . .
  • 11. The past : Model-View-Presenter Activity Presenter fun init() { if (isFirstLogin) { view.setTitleText("Welcome To DevFest 2018") view.setTitleVisible(true) } } fun validateDateOfBirth(number: String) { view.setButtonEnabled(number.length >= 6) } Giant Glue Code if (isFirstLogin) { view.setTitleText("Welcome To DevFest 2018") view.setTitleVisible(true) } view.setButtonEnabled(number.length >= 6) fun setTitleText(title: String) { titleText.text = title } fun setTitleVisible(visible: Boolean) { titleText.visibility = if (visible) View.VISIBLE else View.GONE } fun setButtonEnabled(enable: Boolean) { titleText.isEnabled = enable } . . .
  • 12. So far, It s ok BusinessUI Activity Activity PresenterActivity Presenter Presenter
  • 13. So far, It s ok one to one but testable BusinessUI Activity Activity PresenterActivity Presenter Presenter
  • 14. So far, It s ok one to one but testable non-reusable can be solved by - use case - abstraction BusinessUI Activity Activity PresenterActivity Presenter Presenter
  • 15. So far, It s ok one to one but testable non-reusable can be solved by - use case - abstraction hard to maintain BusinessUI Activity Activity PresenterActivity Presenter Presenter - abstraction
  • 17.
  • 18. android { . . . dataBinding { enabled = true } } build.gradle in app module $gradle_version = Android Plugin for Gradle 1.5.0-alpha1 and higher android_studio_version = Android Studio 1.3 and higher // For Kotlin dependencies { kapt “com.android.databinding:compiler:$gradle_version" }
  • 21. TextView Welcome message for Devfest Seoul of the year Requirements EditText 6 numbers for authentication Button Enabled for login with 6 numbers
  • 22. override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } Activity
  • 23. String.format(getString(R.string.title), presenter.year) override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } “Welcome to Devfest Seoul %s” Activity
  • 24. override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } presenter.year presenter.validate(s.toString()) fun validate(number: String) { view.setButtonEnabled(number.isCodeValid()) } Activity Presenter
  • 25. override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } presenter.year presenter.validate(s.toString()) fun validate(number: String) { view.setButtonEnabled(number.isCodeValid()) } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } Activity Presenter
  • 27. override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } Activity Presenterxml Data Binding
  • 28. override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } setContentView(R.layout.activity_main) val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) // LiveData binding.setPresenter(presenter) Activity Presenterxml Data Binding val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) // LiveData
  • 29. override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } setContentView(R.layout.activity_main) val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) // LiveData binding.setPresenter(presenter) <layout> <android.support.constraint.ConstraintLayout …> <TextView android:id=“@+id/textView" ... /> <EditText android:id=“@+id/editText" ... /> <Button android:id=“@+id/button" ... /> </android.support.constraint.ConstraintLayout> </layout> <layout> </layout> R.layout.activity_main Activity Presenterxml Data Binding
  • 30. override fun onCreate(savedInstanceState: Bundle?) { ... setContentView(R.layout.activity_main) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } setContentView(R.layout.activity_main) val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) // LiveData binding.setPresenter(presenter)binding.setPresenter(presenter) <layout> <data> <variable name=“presenter” type=“com...MainPresenter"/> </data> <android.support.constraint.ConstraintLayout …> <EditText android:id=“@+id/editText" ... /> <data> <variable name=“presenter” type=“com...MainPresenter"/> </data> Activity Presenterxml Data Binding
  • 31. delete override fun onCreate(savedInstanceState: Bundle?) { ... val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) binding.setPresenter(presenter) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } Activity Presenterxml Data Binding
  • 33. Activity Presenterxml Data Binding override fun onCreate(savedInstanceState: Bundle?) { ... val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) binding.setPresenter(presenter) textView.text = String.format(getString(R.string.title), presenter.year) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } <TextView <layout> ... <android.support.constraint.ConstraintLayout ...> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=“@{@string/title(presenter.year)}” />android:text=“@{@string/title(presenter.year)}” textView.text = String.format(getString(R.string.title), presenter.year) TextView
  • 35. android:text="@{String.valueOf(index + 1)}" android:visibility="@{size < 15 ? View.GONE : View.VISIBLE}” android:textColor=“@{colorText}” ??? android:text=“@{@string/title(presenter.year)}” Expression Language
  • 39. object Converters { @JvmStatic @InverseMethod(“toColor") fun fromColorString(colorString: String): Int { return Color.parseColor(colorString) } @JvmStatic // Two-way Binding fun toColor(color: Int): String { return color.toString() } } android:textColor="@{Converters.fromColorString(colorText)}" @InverseMethod
  • 40. android:text="@{String.valueOf(index + 1)}" android:visibility="@{size < 15 ? View.GONE : View.VISIBLE}” android:textColor=“@{colorText}” // @InverseMethod android:visibility=“@{title != null}” ??? android:text=“@{@string/title(presenter.year)}” Expression Language
  • 41. android:visibility=“@{title != null}” visible | invisible | gone @BindingConversion object Conversions { @JvmStatic @BindingConversion fun convertBooleanToVisibility(visible: Boolean) = if (visible) View.VISIBLE else View.GONE }
  • 42. android:visibility=“@{title != null}” visible | invisible | gone Normally 1. visible | invisible ??? 2. visible | gone ???
  • 43. android:visibility=“@{title != null}” visible | invisible | gone Normally 1. visible | invisible 2. visible | gone app:visible=“@{title != null}” app:visibleOrGone=“@{title != null}”
  • 44. @BindingAdapter @Target(ElementType.METHOD) public @interface BindingAdapter { String[] value(); boolean requireAll() default true; }
  • 45. @BindingAdapter @Target(ElementType.METHOD) public @interface BindingAdapter { String[] value(); boolean requireAll() default true; } // Example @BindingAdapter("visible") @BindingAdapter(value={"url", "size"}, requireAll=false)
  • 46. app:visible=“@{title != null}” app:visibleOrGone=“@{title != null}” @BindingAdapter @JvmStatic @BindingAdapter("visible") fun View.setVisible(visible: Boolean) { visibility = if (visible) View.VISIBLE else View.INVISIBLE } @JvmStatic @BindingAdapter("visibleOrGone") fun View.setVisibleOrGone(visible: Boolean) { visibility = if (visible) View.VISIBLE else View.GONE }
  • 47. Activity Presenterxml Data Binding override fun onCreate(savedInstanceState: Bundle?) { ... val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) binding.setPresenter(presenter) editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(...) { } override fun afterTextChanged(s: Editable?) { presenter.validate(s.toString()) } override fun onTextChanged(...) { } }) <layout> ... <android.support.constraint.ConstraintLayout ...> <EditText android:id=“@+id/editText" android:text=“@={presenter.authCode}”android:text=“@={presenter.authCode}” EditText class MainPresenter { val authCode = MutableLiveData<String>() ... }
  • 52. android:text=“@={presenter.authCode)}” InverseBindingAdapter in ActivityMainBinding.java @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged") public static String getTextString(TextView view) { return view.getText().toString(); }
  • 53. android:text=“@={presenter.authCode)}” TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); BindingAdapter in ActivityMainBinding.java private InverseBindingListener editTextandroidTextAttrChanged = new InverseBindingListener() { @Override public void onChange() { String callbackArg_0 = TextViewBindingAdapter.getTextString(editText); ... vmAuthCode.setValue(((String) (callbackArg_0))); } editTextandroidTextAttrChanged
  • 54. android:text=“@={presenter.authCode)}” TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); BindingAdapter in ActivityMainBinding.java private InverseBindingListener editTextandroidTextAttrChanged = new InverseBindingListener() { @Override public void onChange() { String callbackArg_0 = TextViewBindingAdapter.getTextString(editText); ... vmAuthCode.setValue(((String) (callbackArg_0))); } editTextandroidTextAttrChanged TextViewBindingAdapter.getTextString(editText);
  • 55. android:text=“@={presenter.authCode)}” TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); BindingAdapter in ActivityMainBinding.java TextViewBindingAdapter.setTextWatcher @BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false) public static void setTextWatcher(TextView view, final BeforeTextChanged before, final OnTextChanged on, final AfterTextChanged after, final InverseBindingListener textAttrChanged) { final TextWatcher newValue; ... newValue = new TextWatcher() { ... @Override public void onTextChanged(CharSequence s, int start, int before, int count) if (textAttrChanged != null) { textAttrChanged.onChange();
  • 56. android:text=“@={presenter.authCode)}” Default BindingAdapters android.databinding.adapters @BindingMethods({ @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"), @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = “setAllCaps"), ... }) public class TextViewBindingAdapter { @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { final CharSequence oldText = view.getText(); if (text == oldText || (text == null && oldText.length() == 0)) { return; }
  • 57. Activity Presenterxml Data Binding override fun onCreate(savedInstanceState: Bundle?) { ... val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) binding.setPresenter(presenter) button.setOnClickListener { } } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } button.setOnClickListener { } fun setButtonEnabled(enable: Boolean) { button.isEnabled = enable } <Button … android:enabled="@{ValidationUtil.isCodeValid(vm.authCode)}" android:onClick="@{() -> vm.login()}" <layout> <data> <import type=“com....ValidationUtil"/> <variable <import type=“com....ValidationUtil"/> object ValidationUtil { @JvmStatic fun String?.isCodeValid() = this?.length ?: 0 >= 6 }
  • 58. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) binding.setPresenter(presenter) } } Activity
  • 59. Activity xml <layout> <data> <import type=“com....ValidationUtil"/> <variable name=“presenter” type=“com...MainPresenter"/> </data> <android.support.constraint.ConstraintLayout ...> <TextView android:text=“@{@string/title(presenter.year)}” .../> <EditText android:text=“@={presenter.authCode}” .../> <Button android:enabled=“@{ValidationUtil.isCodeValid(presenter.authCode)}” android:onClick="@{() -> presenter.login()}” .../> </android.support.constraint.ConstraintLayout> </layout>
  • 60. class MainPresenter { var year : String val authCode = MutableLiveData<String>() init { year = "2018" } fun login() { // do something! } } Activity Presenterxml Data Binding
  • 62. class MainViewModel { var year : String val authCode = MutableLiveData<String>() init { year = "2018" } fun login() { // do something! } } Activity ViewModelxml Data Binding
  • 63. TO-BE : MVVM with Data Binding Activity view xml ViewModel Data Binding
  • 69. activity_main.xml <layout> <data> <import type=“com....ValidationUtil"/> <variable name=“vm” type=“com...MainViewModel"/> </data> <android.support.constraint.ConstraintLayout ...> <TextView android:text=“@{@string/title(vm.year)}” .../> <EditText android:text=“@={presenter.authCode}” .../> <Button android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}” android:onClick="@{() -> vm.login()}” .../> </android.support.constraint.ConstraintLayout> </layout>
  • 70. activity_main.xml Layout Processor <layout> <data> <import type=“com....ValidationUtil"/> <variable name=“vm” type=“com...MainViewModel"/> </data> <android.support.constraint.ConstraintLayout ...> <TextView android:text=“@{@string/title(vm.year)}” .../> <EditText android:text=“@={presenter.authCode}” .../> <Button android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}” android:onClick="@{() -> vm.login()}” .../> </android.support.constraint.ConstraintLayout> </layout>
  • 71. ActivityMainBinding.java Layout Processor public class ActivityMainTestBinding extends android.databinding.ViewDataBinding implements android.databinding.generated.callback.OnClickListener.Listener { ... @NonNull public final android.widget.Button button; @NonNull public final android.widget.EditText editText; @NonNull public final android.widget.TextView textView; @NonNull private final android.support.constraint.ConstraintLayout mboundView0; @Nullable private com.github.kimkevin.devfestseoul18.MainViewModel mVm; @Nullable private final android.view.View.OnClickListener mCallback1; Variable Listener View @NonNull public final android.widget.Button button; @NonNull public final android.widget.EditText editText; @NonNull public final android.widget.TextView textView; @Nullable private com.github.kimkevin.devfestseoul18.MainViewModel mVm; @Nullable private final android.view.View.OnClickListener mCallback1; ActivityMainTestBinding android.databinding.ViewDataBinding
  • 72. DirtyFlag mapping Dealing with updates When data is changed, mDirtyFlags is updated 0 0 1 0 1 0 1 0 0 0x1L 0x2L 0x4L /* flag mapping flag 0 : vm.authCode flag 1 : vm flag 2 : null flag mapping end*/ ActivityMainBinding.java
  • 73. DirtyFlag mapping Dealing with updates When data is changed, mDirtyFlags is updated 0 0 1 0 1 0 1 0 0 0x1L 0x2L 0x4L /* flag mapping flag 0 : vm.authCode flag 1 : vm flag 2 : initialized flag mapping end*/ ActivityMainBinding.java
  • 74. /* flag mapping vm.authCode : 0x1L vm : 0x2L initialized : 0x4L flag mapping end*/ private boolean onChangeVmAuthCode(MutableLiveData<String> VmAuthCode, int fieldId) { if (fieldId == BR._all) { synchronized(this) { mDirtyFlags |= 0x1L; } return true; } return false; ActivityMainBinding.java mDirtyFlag vm.authCode : 0x1L
  • 75. /* flag mapping vm.authCode : 0x1L vm : 0x2L initialized : 0x4L flag mapping end*/ ActivityMainBinding.java mDirtyFlag vm : 0x2L public void setVm(@Nullable com...MainViewModel Vm) { this.mVm = Vm; synchronized(this) { mDirtyFlags |= 0x2L; } notifyPropertyChanged(BR.vm); super.requestRebind(); }
  • 76. /* flag mapping vm.authCode : 0x1L vm : 0x2L initialized : 0x4L flag mapping end*/ ActivityMainBinding.java mDirtyFlag @Override public void invalidateAll() { synchronized(this) { mDirtyFlags = 0x4L; } requestRebind(); } initialized : 0x4L
  • 77. ActivityMainBinding.java flag initialized : 0x4L class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val vm = MainViewModel() val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) binding.setVm(viewModel) } } mDirtyFlags = 0x4L
  • 78. ActivityMainBinding.java flag vm : 0x2L class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val vm = MainViewModel() val binding = DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main) binding.setLifecycleOwner(this) binding.setVm(viewModel) } } mDirtyFlags = 0x4L | 0x2L = 0x6L
  • 79. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if ((dirtyFlags & 0x7L) != 0) { if ((dirtyFlags & 0x6L) != 0) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if ((dirtyFlags & 0x7L) != 0) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if ((dirtyFlags & 0x4L) != 0) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if ((dirtyFlags & 0x6L) != 0) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } mDirtyFlags = 0x6L
  • 80. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if ((0x6L & 0x7L) != 0) { if ((0x6L & 0x6L) != 0) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if ((0x6L & 0x7L) != 0) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if ((0x6L & 0x4L) != 0) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if ((0x6L & 0x6L) != 0) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } mDirtyFlags = 0x6L
  • 81. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if ((0x6L & 0x7L) != 0) { if ((0x4L & 0x6L) != 0) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if ((0x4L & 0x7L) != 0) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if ((0x4L & 0x4L) != 0) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if ((0x4L & 0x6L) != 0) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } mDirtyFlags = 0x4L 1 1 0 1 1 1 0x6L 0x7L (0x6L & 0x7L) != 0 &
  • 82. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if ((0x6L & 0x7L) != 0) { if ((0x4L & 0x6L) != 0) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if ((0x4L & 0x7L) != 0) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if ((0x4L & 0x4L) != 0) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if ((0x4L & 0x6L) != 0) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } (0x6L & 0x7L) != 0 mDirtyFlags = 0x4L 1 1 1 0 0x6L 0x7L & 1 1 0
  • 83. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if ((0x6L & 0x7L) != 0) { if ((0x4L & 0x6L) != 0) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if ((0x4L & 0x7L) != 0) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if ((0x4L & 0x4L) != 0) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if ((0x4L & 0x6L) != 0) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } (0x6L & 0x7L) != 0 mDirtyFlags = 0x4L 1 1 1 1 1 0 0x7L 0x6L & 0x6L 1 1 0
  • 84. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if ((0x6L & 0x7L) != 0) { if ((0x4L & 0x6L) != 0) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if ((0x4L & 0x7L) != 0) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if ((0x4L & 0x4L) != 0) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if ((0x4L & 0x6L) != 0) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } (0x6L & 0x7L) != 0 mDirtyFlags = 0x4L 1 1 1 1 1 0 0x7L 0x6L & 0x6L 1 1 0 != 0 true
  • 85. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if (true) { if (true) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if (true) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if (true) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if (true) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } mDirtyFlags = 0x6L
  • 89. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if ((0x1L & 0x7L) != 0) { if ((0x1L & 0x6L) != 0) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if ((0x1L & 0x7L) != 0) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if ((0x1L & 0x4L) != 0) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if ((0x1L & 0x6L) != 0) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } mDirtyFlags = 0x1L
  • 90. @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if (true) { if (false) { textViewAndroidStringTitleVmYear = textView...getString(R.string.title, vmYear); } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if (true) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if (false) { this.button.setOnClickListener(mCallback1); TextViewBindingAdapter.setTextWatcher(this.editText, null, null, null, editTextandroidTextAttrChanged); } if (false) { TextViewBindingAdapter.setText(this.textView, textViewAndroidStringTitleVmYear); } } mDirtyFlags = 0x1L @Override protected void executeBindings() { ... dirtyFlags = mDirtyFlags; if (true) { if (false) { } // read vm.authCode.getValue() vmAuthCodeGetValue = vmAuthCode.getValue(); // read ValidationUtil.isCodeValid(vm.authCode.getValue()) vmAuthCodeLengthInt6 = ValidationUtil.isCodeValid(vmAuthCodeLength); } if (true) { this.button.setEnabled(vmAuthCodeLengthInt6); TextViewBindingAdapter.setText(this.editText, vmAuthCodeGetValue); } if (false) { } if (false) { } }
  • 92.
  • 95. "95 Includes data class SpeakerSchedule(val name: String, val subject: String) <layout> <data> <variable name="schedule" type=“com...SpeakerSchedule” /> </data> <android.support.constraint.ConstraintLayout ...> <TextView android:id="@+id/subjectTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=“@{schedule.subject}"/> <TextView android:id="@+id/nameTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=“@{schedule.name}" /> ... </android.support.constraint.ConstraintLayout> </layout> item_schedule.xml
  • 97. Includes binding.schedule = SpeakerSchedule(“KimKevin”, “Android...”) <include android:layout=“@layout/item_schedule” app:schedule=“@{schedule}” .../> <variable name="schedule" type=“com.github.kimkevin…SpeakerSchedule" />
  • 98. Listing & BindingAdapter @BindingAdapter("schedules") fun setSchedules(container: LinearLayout, schedules: List<SpeakerSchedule>) { container.removeAllViews() val inflater = container.context.getSystemService( Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater schedules.forEach { val binding = DataBindingUtil.inflate<ItemScheduleBinding>( inflater, R.layout.item_schedule, container, true) binding.schedule = it } }
  • 99. Listing & BindingAdapter <LinearLayout android:orientation="vertical" app:schedules="@{schedules}" .../> <variable name="schedules" type="List&lt;SpeakerSchedule&gt;" /> val schedules = mutableListOf<SpeakerSchedule>().apply { add(SpeakerSchedule("Kevin", "Android")) add(SpeakerSchedule("David", “Data Binding”)) } binding.schedule = schedules
  • 101. Listing & ViewHolder ItemViewHolder class ItemViewHolder(v: View) : RecyclerView.ViewHolder
  • 102. Listing & ViewHolder HeaderViewHolder ItemViewHolder class ItemViewHolder(v: View) : RecyclerView.ViewHolder class HeaderViewHolder(v: View) : RecyclerView.ViewHolder
  • 103. Listing & ViewHolder HeaderViewHolder ItemViewHolder ProgressViewHolder class ItemViewHolder(v: View) : RecyclerView.ViewHolder class HeaderViewHolder(v: View) : RecyclerView.ViewHolder class ProgressViewHolder(v: View) : RecyclerView.ViewHolder
  • 104. class BindingViewHolder<T: ViewDataBinding> constructor(val binding: T): RecyclerView.ViewHolder(binding.root)
  • 105. class BindingViewHolder<T: ViewDataBinding> constructor(val binding: T): RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, position: Int): BindingViewHolder<ViewDataBinding> { return BindingViewHolder(ItemScheduleBinding.inflate( LayoutInflater.from(parent.context), parent, false)) } override fun onBindViewHolder( viewHolder: BindingViewHolder<ViewDataBinding>, position: Int) { val binding = viewHolder.binding as ItemScheduleBinding binding.schedule = items.get(position) } Adapter
  • 107. TextView Welcome message for Devfest Seoul of the year Requirements EditText 6 numbers for authentication Button Enabled for login with 6 numbers
  • 108. TextView Welcome message for Devfest Seoul of the year Requirements EditText 6 numbers for authentication Button Enabled for login with 6 numbers Business UI
  • 112. <TextView android:text=“@{@string/title(vm.year)}” .../> Business UI @Test fun initVm_setYear() { vm = MainViewModel() assertTrue(vm.year, deafest.year) } val devfest = repository.getDevfest() vm.year = devfest.year MainViewModel
  • 117. fun String?.isCodeValid() = this?.length ?: 0 >= 6 <Button android:enabled=“@{ValidationUtil.isCodeValid(vm.authCode)}” .../> Business UI
  • 120. Seoul Thank you Yongjun Kim, kakaobank @imkimkevin