Slide 1

Slide 1 text

ViewBindingで 手軽にView操作 2020/02/12 potatotips #68

Slide 2

Slide 2 text

自己紹介 - にっちー (@nichi_yd) - Android Engineer @ FiNC Technologies, Inc - 元 青年海外協力隊(アフリカ)

Slide 3

Slide 3 text

もくじ - ViewBindingとは - これまでのView操作 - ViewBindingのメリット - 仕組み - 実装方法 - 簡略化するために - 参考URL

Slide 4

Slide 4 text

ViewBindingとは - ビューを操作するコードを簡単に記述できる機能 - Null Safety, Type Safety - Android Studio 3.6 Canary 11 以降で使える

Slide 5

Slide 5 text

これまでのView操作

Slide 6

Slide 6 text

の前に、(iOSエンジニアの方もいるので ) まず前提 - Androidでは(今の所は、基本的に)xmlファイルでレイアウトを定義

Slide 7

Slide 7 text

の前に、(iOSエンジニアの方もいるので ) まず前提 - Androidでは(今の所は、基本的に)xmlファイルでレイアウトを定義 - ActivityやFragmentといったUI提供のコンポーネントでレイアウトファイルのViewを 操作

Slide 8

Slide 8 text

これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) textView1 = findViewById(R.id.text_view1) textView2 = findViewById(R.id.text_view2) textView3 = findViewById(R.id.text_view3) textView1.text = "hoge" textView2.text = "fuga" textView3.text = "foo" } }

Slide 9

Slide 9 text

これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) textView1 = findViewById(R.id.text_view1) textView2 = findViewById(R.id.text_view2) textView3 = findViewById(R.id.text_view3) textView1.text = "hoge" textView2.text = "fuga" textView3.text = "foo" } } 冗長

Slide 10

Slide 10 text

これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // This will cause IllegalStateException: textView1 must not be null textView1 = findViewById(R.id.text_of_different_layout) textView1.text = "hoge" } } 他のレイアウトファイ ルも指定できる

Slide 11

Slide 11 text

これまでのView操作 findViewById class MainActivity : AppCompatActivity(R.layout.main_activity) { private lateinit var textView1: TextView private lateinit var textView2: TextView private lateinit var textView3: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // This will throw IllegalStateException: textView1 must not be null textView1 = findViewById(R.id.text_of_different_layout) textView1.text = "hoge" } } 他のレイアウトファイ ルも指定できる null安全でない

Slide 12

Slide 12 text

これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" } }

Slide 13

Slide 13 text

これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" } } スネークケース

Slide 14

Slide 14 text

これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_fragment.* class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" // This will throw llegalStateException: textView1 must not be null text_of_different_layout.text = "huga" } } 他のレイアウトファイ ルも指定できる

Slide 15

Slide 15 text

これまでのView操作 Kotlin Android Extension import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_fragment.* class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) text_view1.text = "hoge" text_view2.text = "fuga" text_view3.text = "foo" // This will throw llegalStateException: textView1 must not be null text_of_different_layout.text = "huga" } } null安全でない 他のレイアウトファイ ルも指定できる

Slide 16

Slide 16 text

これまでのView操作 Data Binding (this, R.layout.main_activity) binding.user = User("TARO") binding.text1.text = "hoge" } }

Slide 17

Slide 17 text

これまでのView操作 Data Binding (this, R.layout.main_activity) binding.user = User("TARO") binding.text1.text = "hoge" } } ちょっと大変 コンパイルに 時間かかる

Slide 18

Slide 18 text

ViewBindingのメリット - 簡単 - Null安全 - Type安全

Slide 19

Slide 19 text

仕組み - レイアウトファイルを作成すると、自動で ViewDataBindingを継承したクラスが作成さ れる - main_activity.xml => MainActivityBinding - 生成されたコードは、ビルド後に以下で確認 できる - ~/app/build/generated/data_binding_base_ class_source_out/

Slide 20

Slide 20 text

仕組み // Generated by data binding compiler. Do not edit! package com.example.nichiyoshi.viewbindingsample.databinding; import ... public abstract class MainActivityBinding extends ViewDataBinding { @NonNull public final FrameLayout container; @NonNull public final TextView textView1; protected MainActivityBinding(Object _bindingComponent, View _root, int _localFieldCount, FrameLayout container, TextView textView1) { super(_bindingComponent, _root, _localFieldCount); this.container = container; this.textView1 = textView1; } @NonNull public static MainActivityBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot) { return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent()); } - レイアウトファイルを作成すると、自動で ViewDataBindingを継承したクラスが作成さ れる - ビルドしなくても参照できる - main_activity.xml => MainActivityBinding - 生成されたコードは、ビルド後に以下で確認 できる - ~/app/build/generated/data_binding_base_ class_source_out/

Slide 21

Slide 21 text

実装方法 準備 - Android Studio 3.6 Canary 11 以降をインストール - gradle pluginのバージョンを合わせる - Android Studio 3.6 Canary 11 の場合(プロジェクトレベルの build.gradle) - classpath 'com.android.tools.build:gradle:3.6.0-rc02' - モジュールのbuild.gradleに以下を追加 - android { - viewBinding.enabled = true - } - sync

Slide 22

Slide 22 text

実装方法 Activityの場合 import com.example.nichiyoshi.viewbindingsample.databinding.MainActi vityBinding class MainActivity : AppCompatActivity() { private lateinit var binding: MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) setContentView(binding.root) } override fun onStart() { super.onStart() binding.textView1.text = "hoge" } }

Slide 23

Slide 23 text

実装方法 Activityの場合 import com.example.nichiyoshi.viewbindingsample.databinding.MainActi vityBinding class MainActivity : AppCompatActivity() { private lateinit var binding: MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) setContentView(binding.root) } override fun onStart() { super.onStart() binding.textView1.text = "hoge" } } もし参照エラーになったら Invalidate caches and restart で解決するかも

Slide 24

Slide 24 text

実装方法 Activityの場合 import com.example.nichiyoshi.viewbindingsample.databinding.MainActi vityBinding class MainActivity : AppCompatActivity() { private lateinit var binding: MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) setContentView(binding.root) } override fun onStart() { super.onStart() binding.textView1.text = "hoge" } } - レイアウトファイルはこれまで通り - 簡単 - Null安全 - bindingインスタンスからViewを取得し ている限り、常に同一のレイアウトファ イルであることを保証 - Type安全

Slide 25

Slide 25 text

実装方法 Fragmentの場合 1 class MainFragment : Fragment() { private var mainFragmentBinding: MainFragmentBinding? = null private val binding get() = mainFragmentBinding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mainFragmentBinding = MainFragmentBinding.inflate(inflater) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.text1.text = "hoge" } override fun onDestroyView() { super.onDestroyView() mainFragmentBinding = null } }

Slide 26

Slide 26 text

実装方法 Fragmentの場合 1 class MainFragment : Fragment() { private var mainFragmentBinding: MainFragmentBinding? = null private val binding get() = mainFragmentBinding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mainFragmentBinding = MainFragmentBinding.inflate(inflater) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.text1.text = "hoge" } override fun onDestroyView() { super.onDestroyView() mainFragmentBinding = null } } Viewのライフサイクルよりも Fragmentのライフサイクルの方が長い => Viewのライフサイクルが終わる時 (onDestroyView)に mainFragmentBinding の参照をリリースして、 ViewTreeのメモリリークを防ぐ

Slide 27

Slide 27 text

実装方法 Fragmentの場合 1 class MainFragment : Fragment() { // private var mainFragmentBinding: MainFragmentBinding? = null // private val binding get() = mainFragmentBinding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val binding = MainFragmentBinding.inflate(inflater) binding.text1.text = "hoge" return binding.root } // override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // super.onViewCreated(view, savedInstanceState) // binding.text1.text = "hoge" // } // override fun onDestroyView() { // super.onDestroyView() // mainFragmentBinding = null // } } 不要なら、bindingインスタンスをフィールドで持たない方が安全!!

Slide 28

Slide 28 text

実装方法 Fragmentの場合 2 (コンストラクタでレイアウト指定) class MainFragment : Fragment(R.layout.main_fragment) { private var mainFragmentBinding: MainFragmentBinding? = null private val binding get() = mainFragmentBinding!! override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mainFragmentBinding = MainFragmentBinding.bind(view) } override fun onStart() { super.onStart() binding.text1.text = "hoge" } override fun onDestroyView() { super.onDestroyView() mainFragmentBinding = null } } inflateメソッドではなくbindメソッドを使う

Slide 29

Slide 29 text

簡略化するために satoshunさんのブログにて言及されています - FragmentでViewの参照を持つとメモリリークする話と実装 - AACサンプルで使っている AutoClearedValueを使う - DataBinding-Ktxを使う

Slide 30

Slide 30 text

リンク - View Binding(Android Developers) - architecture-components-samples/ViewBindingSample - AndroidStudioプレビューリリース - FragmentでViewの参照を持つとメモリリークする話と実装

Slide 31

Slide 31 text

Thanks