Slide 1

Slide 1 text

Android

Slide 2

Slide 2 text

FindViewBindings, All ways to bind views Veronika Bae Android Developer. GDG Incheon Korea, veronika.bae

Slide 3

Slide 3 text

1. Butter Knife 2. Kotlin Android Extensions 3. Data Binding 4. View Binding Intro findViewById?

Slide 4

Slide 4 text

findViewById

Slide 5

Slide 5 text

findViewById How to use public final class R { /* ... */ public static final int button_ok = 0x7f090133; /* ... */ } Button okButton = findViewById(R.id.button_ok) AAPT generates

Slide 6

Slide 6 text

Button okButton = findViewById(R.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

Slide 7

Slide 7 text

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)

Slide 8

Slide 8 text

findViewById Problems 1. Casting API 26 Button okButton = findViewById(R.id.button_ok) @Nullable public final T findViewById(@IdRes int id){ if (id == NO_ID) { return null; } return findViewTraversal(id); } View.java

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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) /* ... */ } }

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

findViewById Hidden costs @Nullable public final T findViewById(@IdRes int id) { if (id == NO_ID) { return null; } return findViewTraversal(id); } protected T findViewTraversal(@IdRes int id) { if (id == mID) { return (T) this; } return null; } View.java

Slide 14

Slide 14 text

@Override protected 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

Slide 15

Slide 15 text

@Override protected 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

Slide 16

Slide 16 text

@Override protected 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

Slide 17

Slide 17 text

Butter Knife

Slide 18

Slide 18 text

Butter Knife How to use http://jakewharton.github.io/butterknife/ Deprecated This tool is now deprecated. Please switch to view binding

Slide 19

Slide 19 text

Butter Knife How to use @BindView(R.id.hello) Button helloButton; helloButton.setText(sayHello); ButterKnife.bind(this);

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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; } /* … */ }

Slide 22

Slide 22 text

Kotlin Android Extensions

Slide 23

Slide 23 text

Kotlin Android Extensions How to use https://youtrack.jetbrains.com/issue/KT-42121 “Deprecate Kotlin Android Extensions compiler plugin.”

Slide 24

Slide 24 text

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.

Slide 25

Slide 25 text

Kotlin Android Extensions How to use apply plugin: 'kotlin-android-extensions' build.gradle

Slide 26

Slide 26 text

Kotlin Android Extensions How to use apply plugin: 'kotlin-android-extensions' build.gradle import kotlinx.android.synthetic.main.fragment_first.* import kotlinx.android.synthetic.main.fragment_first.view.* textViewFirst.text = "Hello, World"

Slide 27

Slide 27 text

Kotlin Android Extensions How to use 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}.*

Slide 28

Slide 28 text

Kotlin Android Extensions How to use 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

Slide 29

Slide 29 text

Kotlin Android Extensions How it works Tools → Kotlin, Show Kotlin ByteCode Bytecode Decompile

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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; }

Slide 32

Slide 32 text

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; }

Slide 33

Slide 33 text

Kotlin Android Extensions How it works public void onDestroyView() { super.onDestroyView(); this._$_clearFindViewByIdCache(); } public void _$_clearFindViewByIdCache() { if(this._$_findViewCache != null) { this._$_findViewCache.clear(); } } Fragment

Slide 34

Slide 34 text

Fragment View Created Fragment View Destroyed Fragment View LifeCycle Fragment Added Fragment Destroyed Fragment LifeCycle

Slide 35

Slide 35 text

Kotlin Android Extensions Caution Synthetic Property Not Real Property View Cache

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Kotlin Android Extensions Caution 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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Kotlin Android Extensions Caution 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()

Slide 41

Slide 41 text

Kotlin Android Extensions Caution Custom Layout layout_custom.xml Common Mistake Case

Slide 42

Slide 42 text

import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter() { 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

Slide 43

Slide 43 text

import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter() { 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

Slide 44

Slide 44 text

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"); } }

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter() { 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

Slide 47

Slide 47 text

import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter() { 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

Slide 48

Slide 48 text

import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter() { 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

Slide 49

Slide 49 text

import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter() { 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

Slide 50

Slide 50 text

import kotlinx.android.synthetic.main.layout_custom.view.* class SearchAdapter : RecyclerView.Adapter() { 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

Slide 51

Slide 51 text

DataBinding, ViewBinding

Slide 52

Slide 52 text

DataBinding, ViewBinding How to use Google IO 19

Slide 53

Slide 53 text

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 }

Slide 54

Slide 54 text

DataBinding How to use activity_sample.xml Elements are wrapped in a layout tag Expressions are defined inside a data

Slide 55

Slide 55 text

DataBinding How to use activity_sample.xml Elements are wrapped in a layout tag

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

ViewBinding How to use activity_view_sample.xml

Slide 58

Slide 58 text

ViewBinding How to use activity_view_sample.xml

Slide 59

Slide 59 text

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 )

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Fragment View Created Fragment View Destroyed Fragment View LifeCycle Fragment Added Fragment Destroyed Fragment LifeCycle

Slide 62

Slide 62 text

DataBinding How it works val binding : ActivitySampleBinding = DataBindingUtil.setContentView(this, R.layout.activity_sample) create binding class public abstract class ActivitySampleBinding extends ViewDataBinding { } ???

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

DataBinding How it works activity_sample.xml activity_sample-layout.xml

Slide 65

Slide 65 text

DataBinding How it works

Slide 66

Slide 66 text

DataBinding How it works

Slide 67

Slide 67 text

DataBinding How it works 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); . . . } }

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

DataBinding How it works activity_sample.xml android:text=“@{textValue}” activity_sample-layout.xml tag=“binding_1”>

Slide 72

Slide 72 text

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); } )); } }

Slide 73

Slide 73 text

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); } )); } }

Slide 74

Slide 74 text

findViewById Hidden Costs, Exceptions Butter Knife Annotation Processor, findViewbyId Kotlin Android Extensions View Caching, findViewbyId DataBinding Annotation Processor, Tag ViewBinding findViewById

Slide 75

Slide 75 text

ViewBinding? Find yours!

Slide 76

Slide 76 text

Thank you [email protected]