Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reactive UIs with Data Binding

Reactive UIs with Data Binding

Presented at the AppDevcon conference in Amsterdam

Hugo Visser

March 15, 2019
Tweet

More Decks by Hugo Visser

Other Decks in Technology

Transcript

  1. 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
  2. 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!
  3. Referencing views: findViewById class MyActivity: AppCompatActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val firstName = findViewById<TextView>(R.id.first_name) // "bind" the TextView to a value firstName.text = "Hugo" } }
  4. 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" } }
  5. Data Binding 1. Enable in build.gradle 2. Add kotlin-kapt 3.

    Convert layout to data binding layout
  6. ViewDataBinding class MyActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) binding.firstName.text = "Hugo" } }
  7. Binding a model class MyActivity: AppCompatActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) binding.person = Person("Hugo", "Visser") } }
  8. 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}"
  9. 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
  10. 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
  11. 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)); } } }
  12. 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?) { } }
  13. 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(); }
  14. Binding Adapters in Kotlin val myComponent: DataBindingComponent = … //

    Somewhere globally, or per activity etc DataBindingUtil.setDefaultComponent(myComponent) // For a specific binding val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main, myComponent)
  15. 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
  16. Reactive UI override fun onCreate(savedInstanceState: Bundle?) { val binding: ActivityCheckOutBinding

    = … binding.viewModel = checkOutViewModel checkOutViewModel.loadTicketPrice() } class CheckOutViewModel { var price: Price? = null fun loadTicketPrice() { } }
  17. Data Binding Observables class CheckOutViewModel: BaseObservable() { @Bindable var price:

    Price? = null set(value) { field = value notifyPropertyChanged(BR.price) } fun loadTicketPrice() { } }
  18. 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 }
  19. Dependencies class CheckOutViewModel { val price = ObservableField<Price>() val orderButtonEnabled

    = ObservableBoolean() fun loadTicketPrice() { val result = repository.retrieveTicketPrice() price.set(result) orderButtonEnabled.set(true) } }
  20. 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
  21. Using LiveData class CheckOutViewModel { // requires a cast to

    update, but doesn't expose mutability val price: LiveData<Price> = MutableLiveData() val couponCode = MutableLiveData<String>() // for two-way data binding }
  22. Pass the lifecycle owner to the binding! class CheckOutViewModel {

    // requires a cast to update, but doesn't expose mutability val price: LiveData<Price> = MutableLiveData() val couponCode = MutableLiveData<String>() // for two-way data binding } // In Activity.onCreate() val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) binding.lifecycleOwner = this
  23. 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 } }
  24. 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
  25. 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
  26. In practice Implement Observable in your ViewModel Mix and match

    the use of @Bindable properties and LiveData
  27. Using includes for reusable components <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable

    name="model" type="nl.littlerobots.databindingsamples.MessageBannerModel"/> </data> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{model.message}" /> </layout>
  28. Using includes for reusable components <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable

    name="viewModel" type="..."/> <variable name="messageBannerModel" type="nl.littlerobots.databindingsamples.MessageBannerModel"/> </data> <LinearLayout> <include layout="@layout/include_message_banner" app:model="@{messageBannerModel}"/> </LinearLayout> </layout>
  29. Applying bindings immediately When inflating a binding and adding it

    to a view group RecyclerView view holders ViewDataBinding.executePendingBindings()
  30. 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
  31. Data bound Item example class SongItem(private val song: Song) :

    BindableItem<SongBinding>() { override fun getLayout(): Int = R.layout.song override fun bind(binding: SongBinding, position: Int) { binding.setSong(song) } }
  32. 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!
  33. Tips & tricks Custom views (includes)? Custom setters? executePendingBindings() Binding

    both model + click listeners DataBindingComponent Groupie for RV Callback with context