64
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android MVVM + LiveData + DataBinding(初心者向け)

Last updated at Posted at 2019-04-10

初めに

社内でAndroidの勉強会を開催する機会があり、記事を書こうと思いました。
今回は「AndroidArchitectureComponents」を使ってMVVMを実現する(実装する)内容となっています。別記事で、MVVMの説明した記事を書いてます。
※内容がおろそかだったらごめんなさい:relaxed:

今回作るもの

Studyアプリ.gif

以下を行う双方向データバインディングアプリ

  • 画面を開いた時に、Modelからデータを取得しViewに変更通知
  • 画面から文字を入力し、Viewに変更を通知

設計イメージ

スクリーンショット 2019-04-10 13.38.24.png

実装手順

Model実装 → ViewModel実装 → View実装

さぁ実装!の前に

実装の前に、知っていただきたいことがあります。
今回使用するDataBinding、LiveData、ViewModelをざっくり知っていただこうと思います。
それを知らないと何やっているかわからないと思うからです。ちなみに、別記事で詳しく述べる予定です。

DataBindingとは

Viewとデータ情報を静的または動的に結合する技術
####単方向その1
ViewModelの値を変更する -> Viewに自動で反映される
####単方向その2
ユーザがViewに入力 -> 自動でViewModelに値がセットされる
####双方向
ViewModelの値を変更する -> Viewに自動で反映される
ユーザがViewに入力 -> 自動でViewModelに値がセットされる

LiveDataとは

値の変更をObserveできるデータホルダー

LiveData

外部から変更不可なLiveData

MutableLiveData ← 今回はこれだけ知っていればいい!

外部から変更可能なLiveData

MediatorLiveData

複数のLiveDataを束ねて管理するMutableLiveData

ViewModelとは

Activityの画⾯回転時のデータ保持
Activityの複数Fragment間でのデータ受け渡し
LiveDataと併⽤することが多い ← 今回はこれだけ知っていればいい!
プロセス停⽌後は復旧できない
データの永続化ではない
ViewModelProviders.of()とViewModelProvider.get()を使い、⾃分でnewしない! ← あ、これも知ってて!

実装

Gradleの設定

build.gradle
apply plugin: 'kotlin-kapt'

android {
    dataBinding {
        enabled = true
    }
}

dependencies {
    def archComponents_version = '2.0.0-beta01'
    implementation "androidx.lifecycle:lifecycle-extensions:$archComponents_version"
    kapt "androidx.lifecycle:lifecycle-compiler:$archComponents_version"
}

Model(Repository)

シンプルな初期値のみを取得する

MainRepository.kt
class MainRepository {
    fun fetchText(): String {
        return "初期値!!"
    }
}

ViewModel

変更可能なLiveDataにModelから取得した情報を格納する

MainViewModel.kt
class MainViewModel : ViewModel() {

    var liveDataText: MutableLiveData<String> = MutableLiveData()

    fun fetchText() {
        liveDataText.value = MainRepository().fetchText()
    }
}

レイアウト

xmlファイルのルートをにして、使用するオブジェクトを定義すると、Viewを実装
Viewからバインドしたdataにアクセスするには、@{}でくくる
また、@{}はnullを許容するようになっており、NullPointerExceptionは発生しない

fragment_main_binding.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">

    <data>
        <variable name="view_model" type="com.example.mvvmstudy.viewmodel.MainViewModel"/>
        <import type="java.lang.Integer" />
        <import type="android.view.View"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="20dp">

        <EditText
                android:id="@+id/edit_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="50dp"
                android:text="@={view_model.liveDataText}"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>

        <TextView
                android:id="@+id/count_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="@{`文字数:` + Integer.toString(view_model.liveDataText.length())}"
                app:layout_constraintTop_toBottomOf="@+id/edit_text"
                app:layout_constraintStart_toStartOf="parent"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

View(ActivityとFragment)

ActivityはFragmentを呼ぶだけ

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (savedInstanceState == null) {
            supportFragmentManager
                .beginTransaction()
                .add(R.id.container, MainFragment())
                .commit()
        }
    }
}
  • <layout>でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られます。
  • DataBindingUtil.inflateでBindingしたViewを取得する。
  • binding.viewModel = viewModelでレイアウトファイルに定義したViewModelにViewModelのインスタンスを格納。
  • ViewModelのLiveDataをオブザーブし、データに変更があったらテキストを変更するようにする。
MainFragment.kt
class MainFragment : Fragment() {
    private lateinit var binding: FragmentMainBindingBinding
    private val viewModel: MainViewModel by lazy {
        ViewModelProviders.of(this).get(MainViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel.liveDataText.observe(this, Observer {
            binding.countText.text = "文字数:" + it.length.toString()
        })
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main_binding, container, false)
        binding.viewModel = viewModel
        viewModel.fetchText()
        return binding.root
    }
}

しかし、これでは単方向データバインディング。

  • 双方向データバインディングするために、binding.lifecycleOwner = viewLifecycleOwnerを設定する。
  • オブザーブしていた処理は不要になったため、削除
MainFragment.kt
class MainFragment : Fragment() {
    private lateinit var binding: FragmentMainBindingBinding
    private val viewModel: MainViewModel by lazy {
        ViewModelProviders.of(this).get(MainViewModel::class.java)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main_binding, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        binding.viewModel = viewModel
        viewModel.fetchText()
        return binding.root
    }
}

以上。

最後に

基本的な「MVVM + LiveData + DataBinding」を実装しました。
最終的に双方向にしたので、LiveDataは関係なくなってしまいましたが。。。

また、ViewModelからRepositoryを直接読んでいますが、これでは依存しているため、
RepositoryのInterfaceを作り、ViewModelのコンストラクターでそのRepositoryのIntefaceを渡すようにするとなお良くなる。

###GitHub
勉強会のカンペ用に色々無駄なコードが書かれちゃっているけど
https://github.com/mk2taiga/MVVMStudy

参考にした記事

https://qiita.com/Omoti/items/a83910a990e64f4dbdf1
https://qiita.com/takaaki7/items/91d34e8bf9ad5d71ddd2

64
58
1

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
64
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?