0
0

More than 1 year has passed since last update.

【2022年】Androidアプリで必要最小の双方向バインディング実装

Posted at

前置き

2022年09月19日現在の情報です
出来るだけ最新のやり方を調べてますが、古いやり方が混入してるかも

今回作るもの

image.png

・On、Off(めんどくさいのでtrue=On false = Off )の内部情報を表示するTextView
・On、Offの情報を切り替え、自身もOn、Off情報の外部変更によって状態を変えるToggleButton
・On Offの内部情報をフリップフロップする単純なButton

上記仕様を満たすアプリ

環境

OS:Windows10
IDE:AndroidStudio
実機:Galaxy A20
言語:Kotlin

参考

Factoryクラスとかのつくり方はコレと同じ
ViewModelのつくり方とかも、これとほぼ同じ
activity_main.xmlとMainActibity.ktが違うぐらい

ファイル構成は同一なので、各クラス等がどう機能してるかは、上記の記事参照

そもそも「双方向データバインディグ」ってなんぞや

上記記事に説明と、自分が作ったわけでもない例があるが
たぶん実際に作ったほうが理解早いと思います

プロジェクト作成

AndroidStudioでEmpty Anctivityが作れる事前提で話します

image.png

パッケージ名はなんでもいいんですけど、自分はこうしてます

com.bindingtest.twowaybinding

build.gradle(app)

ここに書いてあるのとまったく同じ

MainActivityFactory.kt

ここに書いてあるのとまったく同じ

ActivityMain.ktと同じ階層に作る

ActivityMainFactory.kt
package com.bindingtest.twowaybinding
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class MainActivityViewModelFactory : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel> create( modelClass : Class<T> ) : T
    {
        return MainActivityViewModel() as T
    }
}

MainActivityViewModel.kt

MainActivityViewModel.kt
package com.bindingtest.twowaybinding
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel

class MainActivityViewModel : ViewModel() {

    val toggleButtonSw: MutableLiveData<Boolean> = MutableLiveData<Boolean>()

    init {
        toggleButtonSw.value = false
    }

}

main_activity.xml

下記をコピペして、必要なところを都度修正
(※:これをそのままコピペしても理想通りには動きません
   理由は下記で説明します)

main_acitivity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="vm"
            type="com.bindingtest.twowaybinding.MainActivityViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <LinearLayout
            android:layout_width="409dp"
            android:layout_height="354dp"
            android:orientation="vertical"
            tools:layout_editor_absoluteX="1dp"
            tools:layout_editor_absoluteY="1dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{vm.toggleButtonSw.toString()}" />

            <ToggleButton
                android:id="@+id/toggleButton"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="ToggleButton"
                android:checked="@{vm.toggleButtonSw}"/>

            <Button
                android:id="@+id/button"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Button"
                android:onClick="onClick_ToggleButtonFlipButton"/>

        </LinearLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

・ViewModelのフラグ情報を可視化するTextView
・ViewModeのフラグ情報を切り替え、自身もViewModelのフラグ情報を参照する
 ToggleButton
・ViewModelのフラグ情報を切り替える普通のボタン

の3つで構成

MainActivity.kt

MainActivity.kt
package com.bindingtest.twowaybinding

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import com.bindingtest.twowaybinding.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    lateinit var factory : MainActivityViewModelFactory
    lateinit var viewmodel : MainActivityViewModel
    lateinit var binding : ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        factory = MainActivityViewModelFactory()
        viewmodel = ViewModelProvider(this,factory).get(MainActivityViewModel::class.java)
        binding.vm = viewmodel
        binding.lifecycleOwner = this

    }

    fun onClick_ToggleButtonFlipButton( view: View )
    {
        viewmodel.toggleButtonSw.value = !viewmodel.toggleButtonSw.value!!
    }
}

さあ実行してみよう…しかし

ビルドは通った!問題なく動いてる…
ように見えて、ToggleButtonとButtonを交互に押してみたりすると
ある問題に行き着くと思います

そう、ToogleButtonの入力が正常にViewModel側に通知されてないのです

ToggleButtonはViewModelの入力を受け取るけど
ToggleButton側からの入力はViewModelに反映しませんよと

これが「単方向バインディング」って奴です

どうすれば想定通り(双方向)になるか

答えは簡単です

main_actibity.xml
            <ToggleButton
                android:id="@+id/toggleButton"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="ToggleButton"
                android:checked="@={vm.toggleButtonSw}"/>

現状のソースとの間違い探しみたいですが、簡単です

android:checked="@={vm.toggleButtonSw}の部分を見ればわかる通り
@と{の間に「=」を入れるだけです

これだけで双方向になります

あとは実際にビルドして、ToggleButtonとButtonを交互に押して
おかしな挙動にならず、TextViewに表示してる文字が「True」「False」を交互に表示するになれば成功です

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0