Slide 1

Slide 1 text

Reactive UIs with Data Binding Hugo Visser @botteaap [email protected]

Slide 2

Slide 2 text

Data binding in a nutshell Views are encapsulated in a single ViewDataBinding class By binding data (a model) to a ViewDataBinding the view will update Click listeners and 2-way binding can update the model from the view Data is bound using expressions and converted using BindingAdapters

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Philosophical: “It’s jsp for Android” Data Binding allows you do to a lot of things and isn’t opinionated No extensive documentation or samples at launch did not help Establish best practices in your team!

Slide 5

Slide 5 text

Referencing views: findViewById class MyActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val firstName = findViewById(R.id.first_name) // "bind" the TextView to a value firstName.text = "Hugo" } }

Slide 6

Slide 6 text

Referencing views: Kotlin synthetic properties import kotlinx.android.synthetic.main.activity_main.* class MyActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // "bind" the TextView to a value first_name.text ="Hugo" } }

Slide 7

Slide 7 text

Data Binding 1. Enable in build.gradle 2. Add kotlin-kapt 3. Convert layout to data binding layout

Slide 8

Slide 8 text

build.gradle apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' android { dataBinding { enabled = true } }

Slide 9

Slide 9 text

Convert layout

Slide 10

Slide 10 text

ViewDataBinding class MyActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.firstName.text = "Hugo" } }

Slide 11

Slide 11 text

Binding a model class MyActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.person = Person("Hugo", "Visser") } }

Slide 12

Slide 12 text

Demo

Slide 13

Slide 13 text

Binding expressions android:text="@{user.displayName}" android:text="@{user.displayName ?? user.lastName}" android:padding="@{large ? @dimen/largePadding : @dimen/smallPadding}" android:text="@{@plurals/banana(bananaCount)}" android:text="@{@string/nameFormat(firstName, lastName)}" android:checked="@={viewModel.agreeToTerms}" app:clipToOutline="@{true}" app:avatarUrl="@{user.avatar}"

Slide 14

Slide 14 text

Binding expressions? android:text="@{String.valueOf(index + 1)}" android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"

Slide 15

Slide 15 text

Organising the data to bind A (single) model backing the view, e.g. a view model Works well with MVVM style architectures View model ≠ ViewModel, but it can be. Also: click listeners for actions that do not deal with model state

Slide 16

Slide 16 text

Organising the data to bind

Slide 17

Slide 17 text

Organising the data to bind

Slide 18

Slide 18 text

Binding Adapters Create framework calls that set values on views Might convert the attribute value in the process https://developer.android.com/topic/libraries/data-binding/binding-adapters

Slide 19

Slide 19 text

Binding Adapters public class BindingAdapters { @BindingAdapter("price") public static void bindPrice(TextView view, Price price) { if (price == null) { view.setText(null); } else { view.setText(PriceFormatUtil.format(view.getContext(), price)); } } }

Slide 20

Slide 20 text

Binding Adapters

Slide 21

Slide 21 text

Binding Adapters in Kotlin @BindingAdapter("price") // Note that the price object can be null! fun bindPrice(view: TextView, price: Price?) { } object BindingAdapters { @BindingAdapter("price") @JvmStatic fun bindPrice(view: TextView, price: Price?) { } }

Slide 22

Slide 22 text

Binding Adapters in Kotlin // Let’s be clever :) @BindingAdapter("price") fun TextView.bindPrice(price: Price?) { }

Slide 23

Slide 23 text

Non-static Binding Adapters in Kotlin class BindingAdapters(private val priceFormatter: MyPriceFormatter) { @BindingAdapter("price") fun bindPrice(view: TextView, price: Price?) { } } public interface DataBindingComponent { nl.littlerobots.databindingsamples.BindingAdapters getBindingAdapters(); }

Slide 24

Slide 24 text

Binding Adapters in Kotlin val myComponent: DataBindingComponent = … // Somewhere globally, or per activity etc DataBindingUtil.setDefaultComponent(myComponent) // For a specific binding val binding = DataBindingUtil.setContentView(this, R.layout.activity_main, myComponent)

Slide 25

Slide 25 text

Reactive UI Inherently async nature of applications, data loading, events etc. Update to the model updates the view, without rebinding the model Bind the model only for view setup, interact with the model for view updates

Slide 26

Slide 26 text

Reactive UI override fun onCreate(savedInstanceState: Bundle?) { val binding: ActivityCheckOutBinding = … binding.viewModel = checkOutViewModel checkOutViewModel.loadTicketPrice() } class CheckOutViewModel { var price: Price? = null fun loadTicketPrice() { } }

Slide 27

Slide 27 text

Data Binding Observables android.databinding.Observable

Slide 28

Slide 28 text

Data Binding Observables class CheckOutViewModel: BaseObservable() { @Bindable var price: Price? = null set(value) { field = value notifyPropertyChanged(BR.price) } fun loadTicketPrice() { } }

Slide 29

Slide 29 text

Dependencies class CheckOutViewModel: BaseObservable() { @Bindable var price: Price? = null set(value) { field = value notifyPropertyChanged(BR.price) } @get:Bindable("price") val orderButtonEnabled: Boolean get() = price != null }

Slide 30

Slide 30 text

Dependencies class CheckOutViewModel { val price = ObservableField() val orderButtonEnabled = ObservableBoolean() fun loadTicketPrice() { val result = repository.retrieveTicketPrice() price.set(result) orderButtonEnabled.set(true) } }

Slide 31

Slide 31 text

Observable Prefer extending BaseObservable over Observable* classes Dependencies between @Bindable fields are very convenient for form input Architecture components ViewModel can also implement Observable https://developer.android.com/topic/libraries/data-binding/architecture

Slide 32

Slide 32 text

Using LiveData class CheckOutViewModel { // requires a cast to update, but doesn't expose mutability val price: LiveData = MutableLiveData() val couponCode = MutableLiveData() // for two-way data binding }

Slide 33

Slide 33 text

Pass the lifecycle owner to the binding! class CheckOutViewModel { // requires a cast to update, but doesn't expose mutability val price: LiveData = MutableLiveData() val couponCode = MutableLiveData() // for two-way data binding } // In Activity.onCreate() val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.lifecycleOwner = this

Slide 34

Slide 34 text

Pass the lifecycle owner to the binding! class MyFragment: Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val binding = MyFragmentBinding.inflate(inflater, container, false) binding.lifecycleOwner = viewLifecycleOwner return binding.root } }

Slide 35

Slide 35 text

When to use Observable Model is mutated, but changes are only observed by the view / binding Updating of properties are not tied to a lifecyle

Slide 36

Slide 36 text

When to use LiveData Model property is also observed from an Activity or Fragment Needs to be aware of lifecycle Tip: when bridging RxJava observables → lifecycle

Slide 37

Slide 37 text

In practice Implement Observable in your ViewModel Mix and match the use of @Bindable properties and LiveData

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

Using includes for reusable components

Slide 40

Slide 40 text

Using includes for reusable components

Slide 41

Slide 41 text

Providing context class MyViewModel(private val context: Context) { fun getComplicatedErrorMessage(): String { context.getString(...) } }

Slide 42

Slide 42 text

Providing context class MyViewModel { fun getComplicatedErrorMessage(context: Context): String { context.getString(...) } }

Slide 43

Slide 43 text

Providing context

Slide 44

Slide 44 text

Applying bindings immediately When inflating a binding and adding it to a view group RecyclerView view holders ViewDataBinding.executePendingBindings()

Slide 45

Slide 45 text

Groupie for RecyclerView interfaces https://github.com/lisawray/groupie Provides a GroupAdapter for RecyclerView that shows a group Group can be a single item, collection of items, mix of item types Works great with Data Binding

Slide 46

Slide 46 text

Data bound Item example class SongItem(private val song: Song) : BindableItem() { override fun getLayout(): Int = R.layout.song override fun bind(binding: SongBinding, position: Int) { binding.setSong(song) } }

Slide 47

Slide 47 text

Summary Data Binding allows for Reactive UIs w/ Observable and LiveData Adds a layer of separation and encapsulation for your screens / views Versatile and powerful: agree on usage and best patterns in your team!

Slide 48

Slide 48 text

Read the fine manual https://developer.android.com/topic/libraries/data-binding

Slide 49

Slide 49 text

Tips & tricks Custom views (includes)? Custom setters? executePendingBindings() Binding both model + click listeners DataBindingComponent Groupie for RV Callback with context