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

ViewBindingで手軽にView操作

nichiyoshi
February 12, 2020

 ViewBindingで手軽にView操作

nichiyoshi

February 12, 2020
Tweet

More Decks by nichiyoshi

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. これまでのView操作

    View Slide

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

    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    android:id="@+id/text_main_activity"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

    View Slide

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

    View Slide

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

    View Slide

  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"
    }
    }
    冗長

    View Slide

  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"
    }
    }
    他のレイアウトファイ
    ルも指定できる

    View Slide

  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安全でない

    View Slide

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

    View Slide

  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"
    }
    }
    スネークケース

    View Slide

  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"
    }
    }
    他のレイアウトファイ
    ルも指定できる

    View Slide

  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安全でない
    他のレイアウトファイ
    ルも指定できる

    View Slide

  16. これまでのView操作
    Data Binding

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    type="com.example.nichiyoshi.viewbindingsample.User" />

    android:orientation="vertical"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    android:text="@{user.name}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
    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(this,
    R.layout.main_activity)
    binding.user = User("TARO")
    binding.text1.text = "hoge"
    }
    }

    View Slide

  17. これまでのView操作
    Data Binding

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    type="com.example.nichiyoshi.viewbindingsample.User" />

    android:orientation="vertical"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    android:text="@{user.name}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
    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(this,
    R.layout.main_activity)
    binding.user = User("TARO")
    binding.text1.text = "hoge"
    }
    }
    ちょっと大変
    コンパイルに
    時間かかる

    View Slide

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

    View Slide

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

    View Slide

  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/

    View Slide

  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

    View Slide

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

    View Slide

  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 で解決するかも

    View Slide

  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安全

    View Slide

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

    View Slide

  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のメモリリークを防ぐ

    View Slide

  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インスタンスをフィールドで持たない方が安全!!

    View Slide

  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メソッドを使う

    View Slide

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

    View Slide

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

    View Slide

  31. Thanks

    View Slide