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

20201031 Binding libraries, Devfest at Singapore

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Veronikapj Veronikapj
October 31, 2020

20201031 Binding libraries, Devfest at Singapore

How they work - FindViewById, Butter Knife, Kotlin Android Extension, DataBinding, ViewBinding

Avatar for Veronikapj

Veronikapj

October 31, 2020
Tweet

More Decks by Veronikapj

Other Decks in Programming

Transcript

  1. <Button android:id=“@+id/button_ok" /> findViewById How to use public final class

    R { /* ... */ public static final int button_ok = 0x7f090133; /* ... */ } Button okButton = findViewById(R.id.button_ok) AAPT generates
  2. Button okButton = findViewById(R.id.button_ok) <Button android:id=“@+id/button_ok" /> findViewById Problems 1.

    Casting Button okButton = (Button)findViewById(R.id.button_ok) ImageView okButton = findViewById(R.id.button_ok) API 25 API 26 Class Cast Exception
  3. findViewById Problems 1. Casting API 25 View.java @Nullable public final

    View findViewById(@IdRes int id) { if (id < 0) { return null; } return findViewTraversal(id); } Button okButton = (Button)findViewById(R.id.button_ok)
  4. findViewById Problems 1. Casting API 26 Button okButton = findViewById(R.id.button_ok)

    @Nullable public final <T extends View> T findViewById(@IdRes int id){ if (id == NO_ID) { return null; } return findViewTraversal(id); } View.java
  5. findViewById Problems 2. Null Pointer Exception API 26 Button okButton

    = findViewById(R.id.button_ok) @Nullable public final <T extends View> T findViewById(@IdRes int id){ if (id == NO_ID) { return null; } return findViewTraversal(id); } View.java
  6. findViewById Problems 2. Null Pointer Exception API 26 Button okButton

    = findViewById(R.id.button_ok) @Nullable public final <T extends View> T findViewById(@IdRes int id){ if (id == NO_ID) { return null; } return findViewTraversal(id); } View.java Null Pointer Exception
  7. findViewById Problems 3. Boilerplate code class SomethingView @JvmOverloads constructor( context:

    Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RelativeLayout(context, attrs, defStyleAttr) { private val itemGroup: LinearLayout private val itemGroupTitle: TextView private val someText: ImageView private val someText2: TextView private val someImage: ImageView private val someImage2: ImageView private val itemImage: ImageView private val itemImage2: ImageView /* .... */ init { View.inflate(context, R.layout.some_view, this) itemGroup = findViewById(R.id.item_group) itemGroupTitle = findViewById(R.id.item_group_title) someText = findViewById(R.id.some_text) someText2 = findViewById(R.id.some_text2) someImage = findViewById(R.id.some_image) someImage2 = findViewById(R.id.some_image2) itemImage = findViewById(R.id.item_image) itemImage2 = findViewById(R.id.item_image2) /* ... */ } }
  8. findViewById Hidden costs @Nullable public final <T extends View> T

    findViewById(@IdRes int id) { if (id == NO_ID) { return null; } return findViewTraversal(id); } View.java
  9. findViewById Hidden costs @Nullable public final <T extends View> T

    findViewById(@IdRes int id) { if (id == NO_ID) { return null; } return findViewTraversal(id); } protected <T extends View> T findViewTraversal(@IdRes int id) { if (id == mID) { return (T) this; } return null; } View.java
  10. @Override protected <T extends View> T findViewTraversal(@IdRes int id) {

    if (id == mID) { return (T) this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return (T) v; } } } return null; } ViewGroup.java
  11. @Override protected <T extends View> T findViewTraversal(@IdRes int id) {

    if (id == mID) { return (T) this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return (T) v; } } } return null; } ViewGroup.java Same as View
  12. @Override protected <T extends View> T findViewTraversal(@IdRes int id) {

    if (id == mID) { return (T) this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return (T) v; } } } return null; } ViewGroup.java Poor performance
  13. Butter Knife How to use <Button android:id=“@+id/hello” /> @BindView(R.id.hello) Button

    helloButton; helloButton.setText(sayHello); ButterKnife.bind(this);
  14. Butter Knife How it works ButterKnife.bind(this); public class SimpleActivity_ViewBinding implements

    Unbinder { /* ... */ view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', . . .); target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class); } New Java Class : Original Class_ViewBinding
  15. Butter Knife How it works view = Utils.findRequiredView(source, R.id.hello, "field

    'hello', method 'sayHello', . . .”) public static View findRequiredView(View source, @IdRes int id, String who){ View view = source.findViewById(id); if (view != null) { return view; } /* … */ }
  16. Kotlin Android Extensions How to use “Deprecate Kotlin Android Extensions

    compiler plugin.” As the @Parcelize functionality is extracted the rest of the Android Extensions functionality can be deprecated in favour of View Binding. The existing Android Extensions plugin will continue to work, however, a warning message will be shown.
  17. Kotlin Android Extensions How to use apply plugin: 'kotlin-android-extensions' build.gradle

    <TextView android:id=“@+id/textViewFirst"/> import kotlinx.android.synthetic.main.fragment_first.* import kotlinx.android.synthetic.main.fragment_first.view.* textViewFirst.text = "Hello, World"
  18. Kotlin Android Extensions How to use <TextView android:id=“@+id/textViewFirst"/> import kotlinx.android.synthetic.main.fragment_first.*

    import kotlinx.android.synthetic.main.fragment_first.view.* textViewFirst.text = "Hello, World" import kotlinx.android.synthetic.{product flavor}.{layout}.*
  19. Kotlin Android Extensions How to use <TextView android:id=“@+id/textViewFirst"/> import kotlinx.android.synthetic.main.fragment_first.*

    import kotlinx.android.synthetic.main.fragment_first.view.* textViewFirst.text = "Hello, World" import kotlinx.android.synthetic.{product flavor}.{layout}.* Synthetic Property
  20. Kotlin Android Extensions How it works import kotlinx.android.synthetic.main.fragment_first.* textViewFirst.text =

    "Hello, World" TextView var10000 = (TextView)this._$_findCachedViewById(id.textViewFirst) Intrinsics.checkExpressionValueIsNotNull(var10000, "textViewFirst") var10000.setText((CharSequence)"Hello, World") Tools → Kotlin, Show Kotlin ByteCode Bytecode Decompile
  21. Kotlin Android Extensions How it works private HashMap _$_findViewCache; public

    View _$_findCachedViewById(int var1) { if (this._$_findViewCache == null) { this._$_findViewCache = new HashMap(); } View var2 = (View)this._$_findViewCache.get(var1); if (var2 == null) { var2 = this.findViewById(var1); this._$_findViewCache.put(var1, var2); } return var2; }
  22. Kotlin Android Extensions How it works private HashMap _$_findViewCache; public

    View _$_findCachedViewById(int var1) { if (this._$_findViewCache == null) { this._$_findViewCache = new HashMap(); } View var2 = (View)this._$_findViewCache.get(var1); if (var2 == null) { var2 = this.findViewById(var1); this._$_findViewCache.put(var1, var2); } return var2; }
  23. Kotlin Android Extensions How it works public void onDestroyView() {

    super.onDestroyView(); this._$_clearFindViewByIdCache(); } public void _$_clearFindViewByIdCache() { if(this._$_findViewCache != null) { this._$_findViewCache.clear(); } } Fragment
  24. Kotlin Android Extensions Caution <TextView android:id=“@+id/textViewFirst" /> fun FirstFragment.a() {

    textViewFirst.text = "Hidden view" textViewFirst.visibility = View.INVISIBLE } import kotlinx.android.synthetic.main.activity_main.* class FirstFragment : Fragment() Used View Cache
  25. Kotlin Android Extensions Caution <TextView android:id=“@+id/textViewFirst" /> fun Fragment.b() {

    textViewFirst.text = "Hidden view" textViewFirst.visibility = View.INVISIBLE } fun FirstFragment.a() { textViewFirst.text = "Hidden view" textViewFirst.visibility = View.INVISIBLE } import kotlinx.android.synthetic.main.activity_main.* class FirstFragment : Fragment() Used View Cache Not Used View Cache
  26. Kotlin Android Extensions Caution fun FirstFragment.a() { textViewFirst.text = "Hidden

    view" textViewFirst.visibility = View.INVISIBLE } public final void a(@NotNull FirstFragment $this$a) { . . . TextView var10000 = (TextView)$this$a._$_findCachedViewById(id.textViewFirst) . . . var10000 = (TextView)$this$a._$_findCachedViewById(id.textViewFirst) . . . var10000.setVisibility(4) } Used View Cache Decompile
  27. Kotlin Android Extensions Caution fun Fragment.b() { textViewFirst.text = "Hidden

    view" textViewFirst.visibility = View.INVISIBLE } Not Used View Cache public final void b(@NotNull Fragment $this$b) { TextView var10000 = (TextView)$this$b.getView().findViewById(id.textViewFirst); . . . var10000.setText((CharSequence)"Hidden view"); var10000 = (TextView)$this$b.getView().findViewById(id.textViewFirst); . . . var10000.setVisibility(4); } Decompile
  28. Kotlin Android Extensions Caution <TextView android:id=“@+id/textViewFirst" /> import kotlinx.android.synthetic.main.activity_main.* fun

    FirstFragment.a() { textViewFirst.text = "Hidden view" textViewFirst.visibility = View.INVISIBLE } fun Fragment.b() { textViewFirst.text = "Hidden view" textViewFirst.visibility = View.INVISIBLE } Used View Cache Not Used View Cache class FirstFragment : Fragment()
  29. Kotlin Android Extensions Caution Custom Layout layout_custom.xml <merge> <TextView android:id=“@+id/itemTitle1"

    /> <TextView android:id=“@+id/itemTitle2" /> </merge> Common Mistake Case
  30. import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(.

    . .) : RecyclerView.ViewHolder { return CustomViewHolder(CustomLayout(parent.context)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is CustomViewHolder) { holder.itemView.itemTitle1.text = "hello1" with(holder.itemView) { itemTitle2.text = "hello1" } } } class CustomViewHolder(private val customLayout: CustomLayout) : RecyclerView.ViewHolder(customLayout) } Kotlin Android Extensions
  31. import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(.

    . .) : RecyclerView.ViewHolder { return CustomViewHolder(CustomLayout(parent.context)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is CustomViewHolder) { holder.itemView.itemTitle1.text = "hello1" with(holder.itemView) { itemTitle2.text = "hello1" } } } class CustomViewHolder(private val customLayout: CustomLayout) : RecyclerView.ViewHolder(customLayout) } Kotlin Android Extensions
  32. Kotlin Android Extensions Caution Decompile import kotlinx.android.synthetic.main.layout_custom.view.* public void onBindViewHolder(

    @NotNull ViewHolder holder, int position) { if (holder instanceof SearchAdapter.CustomViewHolder) { View var10000 = holder.itemView; TextView var8 = (TextView)var10000.findViewById(id.itemTitle1); var8.setText((CharSequence)”hello1"); View var3 = holder.itemView; var8 = (TextView)var3.findViewById(id.itemTitle2); var8.setText((CharSequence)"hello2"); } }
  33. Kotlin Android Extensions Caution Decompile import kotlinx.android.synthetic.main.layout_custom.view.* public void onBindViewHolder(

    @NotNull ViewHolder holder, int position) { if (holder instanceof SearchAdapter.CustomViewHolder) { View var10000 = holder.itemView; TextView var8 = (TextView)var10000.findViewById(id.itemTitle1); var8.setText((CharSequence)”hello1"); View var3 = holder.itemView; var8 = (TextView)var3.findViewById(id.itemTitle2); var8.setText((CharSequence)"hello2"); } } Calling every time
  34. import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(.

    . .) : RecyclerView.ViewHolder { return CustomViewHolder(CustomLayout(parent.context)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is CustomViewHolder) { holder.itemView.itemTitle1.text = "hello1" with(holder.itemView) { itemTitle2.text = "hello1" } } } class CustomViewHolder(private val customLayout: CustomLayout) : RecyclerView.ViewHolder(customLayout) } Kotlin Android Extensions A common class
  35. import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter<CustomViewHolder>() { override fun onCreateViewHolder(.

    . .) : RecyclerView.ViewHolder { return CustomViewHolder(CustomLayout(parent.context)) } override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { if (holder is CustomViewHolder) { holder.itemView.itemTitle1.text = "hello1" with(holder.itemView) { itemTitle2.text = "hello1" } } } class CustomViewHolder(private val customLayout: CustomLayout) : RecyclerView.ViewHolder(customLayout) } Kotlin Android Extensions
  36. import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(.

    . .) : RecyclerView.ViewHolder { return CustomViewHolder(CustomLayout(parent.context)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is CustomViewHolder) { holder.itemView.itemTitle1.text = "hello1" with(holder.itemView) { itemTitle2.text = "hello1" } } } class CustomViewHolder(private val customLayout: CustomLayout) : RecyclerView.ViewHolder(customLayout) } Kotlin Android Extensions
  37. import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(.

    . .) : RecyclerView.ViewHolder { return CustomViewHolder(CustomLayout(parent.context)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is CustomViewHolder) { holder.customLayout.itemTitle1.text = "hello1" with(holder.customLayout) { itemTitle2.text = "hello1" } } } class CustomViewHolder(private val customLayout: CustomLayout) : RecyclerView.ViewHolder(customLayout) } Kotlin Android Extensions
  38. import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(.

    . .) : RecyclerView.ViewHolder { return CustomViewHolder(CustomLayout(parent.context)) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is CustomViewHolder) { holder.customLayout.itemTitle1.text = "hello1" with(holder.customLayout) { itemTitle2.text = "hello1" } } } class CustomViewHolder(private val customLayout: CustomLayout) : RecyclerView.ViewHolder(customLayout) { // declare view properties - itemTitle1, itemTitle2 } } Kotlin Android Extensions
  39. DataBinding, ViewBinding How to use build.gradle DataBinding ViewBinding Gradle 3.6

    dataBinding { enabled = true } viewBinding { enabled = true } Gradle 4.0 buildFeatures { dataBinding = true } buildFeatures { viewBinding = true }
  40. DataBinding How to use activity_sample.xml <layout> <LinearLayout> <TextView android:id=“@+id/data_binding_first" />

    </LinearLayout> </layout> Elements are wrapped in a layout tag <data> </data> Expressions are defined inside a data
  41. DataBinding How to use SampleActivity.xml class SampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding : ActivitySampleBinding = DataBindingUtil.setContentView( this,R.layout.activity_sample) binding.dataBindingFirst.text = "hello data binding!” } } (android:id=“@+id/data_binding_first”) ActivitySampleBinding = LayoutFileName + Binding = activity_sample.xml + Binding
  42. ViewBinding How to use SampleViewActivity.xml class SampleViewActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityViewSampleBinding.inflate(layoutInflater) setContentView(binding.root) } } binding.viewBindingFirst.text = "hello view binding!" No! ( R.layout.activity_view_sample )
  43. ViewBinding Caution private var _binding: ResultProfileBinding? = null private val

    binding get() = _binding!! override fun onCreateView(. . .): View? { _binding = ResultProfileBinding.inflate( inflater, container, false) val view = binding.root return view } override fun onDestroyView() { super.onDestroyView() _binding = null } Fragment Should be released
  44. DataBinding How it works val binding : ActivitySampleBinding = DataBindingUtil.setContentView(this,

    R.layout.activity_sample) create binding class public abstract class ActivitySampleBinding extends ViewDataBinding { } <TextView android:id=“@+id/data_binding_first" /> ???
  45. DataBinding How it works public abstract class ActivitySampleBinding extends ViewDataBinding

    { @NonNull public final TextView dataBindingFirst; protected ActivitySampleBinding(... TextView dataBindingFirst) { super(...); this.dataBindingFirst = dataBindingFirst; } @NonNull public static ActivitySampleBinding inflate(. . .) { return inflate(..., DataBindingUtil.getDefaultComponent()); } } generated from SampleActivity
  46. DataBinding How it works <Layout layout="activity_sample" filePath=“app/src/main/res/layout/activity_sample.xml" . . .

    rootNodeType="android.widget.LinearLayout"> <Targets> <Target tag="layout/activity_sample_0" view="LinearLayout"> <Expressions/> <location startLine="2" startOffset="4" endLine="14" endOffset="18"/> </Target> <Target id="@+id/data_binding_first" view="TextView"> <Expressions/> <location startLine="8" startOffset="8" endLine="13" endOffset="13"/> </Target> </Targets> </Layout>
  47. DataBinding How it works <Layout layout="activity_sample" filePath=“app/src/main/res/layout/activity_sample.xml" . . .

    rootNodeType="android.widget.LinearLayout"> <Targets> <Target tag="layout/activity_sample_0" view="LinearLayout"> <Expressions/> <location startLine="2" startOffset="4" endLine="14" endOffset="18"/> </Target> <Target id="@+id/data_binding_first" view="TextView"> <Expressions/> <location startLine="8" startOffset="8" endLine="13" endOffset="13"/> </Target> </Targets> </Layout>
  48. DataBinding How it works <Target tag=“layout/activity_sample_0"> activity_sample-layout.xml DataBinderMapperImpl.class @Override public

    ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) { final Object tag = view.getTag(); . . . switch (localizedLayoutId) { case LAYOUT_ACTIVITYSAMPLE: { if ("layout/activity_sample_0".equals(tag)) return new ActivitySampleBindingImpl(component, view); . . . } }
  49. DataBinding public class ActivitySampleBindingImpl extends ActivitySampleBinding{ @Nullable 
 private static

    final SparseIntArray sViewsWithIds; static { . . . sViewsWithIds = new android.util.SparseIntArray(); sViewsWithIds.put(R.id.data_binding_first, 1); } public ActivitySampleBindingImpl( @Nullable ..DataBindingComponent bindingComponent, @NonNull View root) { this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds)); } } activity_sample.xml activity_sample-layout.xml <Target id="@+id/data_binding_first">
  50. DataBinding private static void mapBindings(. . .) { . .

    . Object objTag = view.getTag(); final String tag = (objTag instanceof String) ? (String) objTag : null; boolean isBound = false; if (isRoot && tag != null && tag.startsWith("layout")) { . . . } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { . . . if (bindings[tagIndex] == null) { bindings[tagIndex] = view; } isBound = true; } else { . . . } if (!isBound) { final int id = view.getId(); if (id > 0) { int index; if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 && bindings[index] == null) { bindings[index] = view; } } } } Activity_sample-layout.xml <Target id="@+id/data_binding_first">
  51. DataBinding private static void mapBindings(. . .) { . .

    . Object objTag = view.getTag(); final String tag = (objTag instanceof String) ? (String) objTag : null; boolean isBound = false; if (isRoot && tag != null && tag.startsWith("layout")) { . . . } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { . . . if (bindings[tagIndex] == null) { bindings[tagIndex] = view; } isBound = true; } else { . . . } if (!isBound) { final int id = view.getId(); if (id > 0) { int index; if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 && bindings[index] == null) { bindings[index] = view; } } } } Activity_sample-layout.xml <Target id="@+id/data_binding_first">
  52. DataBinding How it works activity_sample.xml <layout> <data> <variable name=“textValue" type="String"

    /> </data> <TextView android:id=“@+id/data_binding_first”> </layout> android:text=“@{textValue}” activity_sample-layout.xml <Target id="@+id/data_binding_first"> tag=“binding_1”>
  53. ViewBinding public final class ActivityViewSampleBinding implements ViewBinding { @NonNull public

    final TextView viewBindingFirst; ... @NonNull public static ActivityViewSampleBinding bind(@NonNull View rootView) ... missingId: { id = R.id.data_binding_first; TextView viewBindingFirst = rootView.findViewById(id); if (dataBindingFirst == null) { break missingId; } return new ActivityViewSampleBinding( (LinearLayout) rootView, dataBindingFirst); } )); } }
  54. ViewBinding public final class ActivityViewSampleBinding implements ViewBinding { @NonNull public

    final TextView viewBindingFirst; ... @NonNull public static ActivityViewSampleBinding bind(@NonNull View rootView) ... missingId: { id = R.id.data_binding_first; TextView viewBindingFirst = rootView.findViewById(id); if (dataBindingFirst == null) { break missingId; } return new ActivityViewSampleBinding( (LinearLayout) rootView, dataBindingFirst); } )); } }
  55. findViewById Hidden Costs, Exceptions Butter Knife Annotation Processor, findViewbyId Kotlin

    Android Extensions View Caching, findViewbyId DataBinding Annotation Processor, Tag ViewBinding findViewById