ViewBindingで手軽にView操作

782be973d3c99ed9a805886fa6c2acdf?s=47 nichiyoshi
February 12, 2020

 ViewBindingで手軽にView操作

782be973d3c99ed9a805886fa6c2acdf?s=128

nichiyoshi

February 12, 2020
Tweet

Transcript

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

  2. 自己紹介 - にっちー (@nichi_yd) - Android Engineer @ FiNC Technologies,

    Inc - 元 青年海外協力隊(アフリカ)
  3. もくじ - ViewBindingとは - これまでのView操作 - ViewBindingのメリット - 仕組み -

    実装方法 - 簡略化するために - 参考URL
  4. ViewBindingとは - ビューを操作するコードを簡単に記述できる機能 - Null Safety, Type Safety - Android

    Studio 3.6 Canary 11 以降で使える
  5. これまでのView操作

  6. の前に、(iOSエンジニアの方もいるので ) まず前提 - Androidでは(今の所は、基本的に)xmlファイルでレイアウトを定義 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/text_main_activity" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </FrameLayout>
  7. の前に、(iOSエンジニアの方もいるので ) まず前提 - Androidでは(今の所は、基本的に)xmlファイルでレイアウトを定義 - ActivityやFragmentといったUI提供のコンポーネントでレイアウトファイルのViewを 操作

  8. これまでの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" } }
  9. これまでの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" } } 冗長
  10. これまでの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" } } 他のレイアウトファイ ルも指定できる
  11. これまでの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安全でない
  12. これまでの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" } }
  13. これまでの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" } } スネークケース
  14. これまでの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" } } 他のレイアウトファイ ルも指定できる
  15. これまでの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安全でない 他のレイアウトファイ ルも指定できる
  16. これまでのView操作 Data Binding <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data>

    <variable name="user" type="com.example.nichiyoshi.viewbindingsample.User" /> </data> <LinearLayout android:orientation="vertical" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:text="@{user.name}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/text1" import androidx.databinding.DataBindingUtil import com.example.nichiyoshi.viewbindingsample.databinding.MainActivityBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<MainActivityBinding>(this, R.layout.main_activity) binding.user = User("TARO") binding.text1.text = "hoge" } }
  17. これまでのView操作 Data Binding <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data>

    <variable name="user" type="com.example.nichiyoshi.viewbindingsample.User" /> </data> <LinearLayout android:orientation="vertical" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:text="@{user.name}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/text1" import androidx.databinding.DataBindingUtil import com.example.nichiyoshi.viewbindingsample.databinding.MainActivityBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<MainActivityBinding>(this, R.layout.main_activity) binding.user = User("TARO") binding.text1.text = "hoge" } } ちょっと大変 コンパイルに 時間かかる
  18. ViewBindingのメリット - 簡単 - Null安全 - Type安全

  19. 仕組み - レイアウトファイルを作成すると、自動で ViewDataBindingを継承したクラスが作成さ れる - main_activity.xml => MainActivityBinding -

    生成されたコードは、ビルド後に以下で確認 できる - ~/app/build/generated/data_binding_base_ class_source_out/
  20. 仕組み // 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/
  21. 実装方法 準備 - 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
  22. 実装方法 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" } }
  23. 実装方法 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 で解決するかも
  24. 実装方法 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安全
  25. 実装方法 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 } }
  26. 実装方法 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のメモリリークを防ぐ
  27. 実装方法 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インスタンスをフィールドで持たない方が安全!!
  28. 実装方法 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メソッドを使う
  29. 簡略化するために satoshunさんのブログにて言及されています - FragmentでViewの参照を持つとメモリリークする話と実装 - AACサンプルで使っている AutoClearedValueを使う - DataBinding-Ktxを使う

  30. リンク - View Binding(Android Developers) - architecture-components-samples/ViewBindingSample - AndroidStudioプレビューリリース -

    FragmentでViewの参照を持つとメモリリークする話と実装
  31. Thanks