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

20201031 Binding libraries, Devfest at Singapore

Veronikapj
October 31, 2020

20201031 Binding libraries, Devfest at Singapore

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

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