【Androidアプリチュートリアル】 メモアプリを作ろう

メモアプリの作成

Android(Kotlin)でメモアプリを作成していくチュートリアルを解説していきます。

特徴としては、Realmというデータベースを利用することです。

Realmはアプリ開発でよく使われるので、ここで使い方を学習していきましょう。

完成形

goal_image

テキストのメモを入力ができ、追加ボタンを押すと一覧に表示されていくアプリです。

この記事で学習できること
  • Realmの使い方
  • ローカルDBの理解
  • ローカルDBを使ったアプリの作成方法
バージョン
  • AndroidStudio 4.1
  • Kotlin 1.3.6

プロジェクトの作成

まずは、プロジェクトを作成していきましょう。

詳細な説明はこちらにありますので、必要があれば参考にしてください。

【入門】Androidアプリプロジェクト作成手順

Choose your projectEmpty Activity を選択してください。

アプリ名はなんでもOKですが、ここではMemoとしました。

input_app_info

Realm導入

早速Realm の導入をしていきましょう。

root配下にある build.gradle を開いてください。

open_root_build_gradle

buildscript の中の dependencies にrealmのpluginを追加してあげます。

buildscript {
    ...
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // この行を追加
        classpath "io.realm:realm-gradle-plugin:6.0.0"
    }
}
...

次に app 配下にある build.gradle を開いてください。

open_app_build_gradle

一番上に apply でまとめられた箇所があります。

そこに2行ほど追加しましょう。

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'
// この2行を追加
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
...

最後に上部に Sync Now というボタンが表示されていると思います。

こちらを押してください。

press_sync_now

しばらくしてこのような表示が出ればOKです。

success_sync

Realm初期化

次にRealmを使うためにRealmの初期化を行なっていきます。

Applicationクラス追加

まずは Application クラスを追加しましょう。

MainActivity と同じ階層に MemoApplication というクラスを追加します。

create_application_class

中身は以下のように記述してください。

アプリ起動時に呼ばれる onCreate() の中で Realm を初期化しています。

import android.app.Application
import io.realm.Realm

class MemoApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        Realm.init(this)
    }
}

Manifestに記述

先ほど追加した MemoApplicationAndroidManifest.xml に追加しましょう。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hiring.memo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name=".MemoApplication"
        ...
    </application>
</manifest>

application タグに android:name=".MemoApplication" というのを追加しましょう。
これでこのアプリの Application クラスが MemoApplication になります。

これでRealmの初期化は完了です。

レイアウト

次はレイアウトを作成していきましょう。

今回はレイアウトにこだわらないので、簡単に実装していきます。

RecyclerViewの追加

今回はメモリストを用意するのでリスト表示のできる RecyclerView を使っていきましょう。

まずはライブラリの追加です。先ほども app 配下にある build.gradle を開きましょう。

dependencies 内に下記のように1行追加して下さい。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.core:core-ktx:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    // この行を追加する
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    ...
}

また先ほどと同様に Sync Now ボタンが表示されるはずなので押しましょう。

これで RecyclerView の準備は完了です。

画面レイアウト

次に画面のレイアウトを実装しましょう。

activity_main を開いて以下のように実装して下さい。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/memo_edit_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="メモを入力"
        app:layout_constraintEnd_toStartOf="@id/add_button"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/add_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="追加"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/memo_edit_text" />

</androidx.constraintlayout.widget.ConstraintLayout>

Design タブを開いてこのようになっていればOKです。

check_design

次はリストのアイテムです。

item_memo.xml というファイルを作成して下さい。
中身の実装は以下のような TextView だけで十分です。

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/memo_text_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp"
    android:textSize="20sp" />

これでレイアウトの実装は完了しました。

RecyclerViewAdapter実装

次はAdapterを実装していきます。

MemoListAdapter というクラスを作成して下さい。

create_adpter

中身は以下のように実装して下さい。 onBindViewHolder() の中で TextView に文字列を入れています。

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class MemoListAdapter: RecyclerView.Adapter<MemoListAdapter.MemoViewHolder>() {
    val memoList = mutableListOf<String>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MemoViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_memo, parent, false)
        return MemoViewHolder(view)
    }

    override fun onBindViewHolder(holder: MemoViewHolder, position: Int) {
        val textView = holder.itemView.findViewById<TextView>(R.id.memo_text_view)
        textView.text = memoList[position]
    }

    override fun getItemCount(): Int = memoList.size

    class MemoViewHolder(view: View): RecyclerView.ViewHolder(view)
}

Adapterの作成が完了したら RecyclerView にセットしていきます。

MainActivity を開いて以下のように実装して下さい。

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    private lateinit var adapter: MemoListAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        adapter = MemoListAdapter()
        val recyclerView = findViewById<RecyclerView>(R.id.memo_list)
        recyclerView.adapter = adapter
    }
}

ここでは MemoListAdapteradapter を定義しておき、 onCreate() で代入しています。

さらに findViewById() メソッドで activity_mainmemo_list を取得し、先ほど作成した adapter を代入しています。

build

ここで一度ビルドしてみましょう。このようになればOKです!

データベースとの連携

それでは次にデータベースと連携していきましょう。

EditText に文字列が入力されている状態で 追加 ボタンを押すと一覧に追加されていくように実装していきます。

それでは MainActivity を開き、以下のように実装してみてください。

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import androidx.recyclerview.widget.RecyclerView
import io.realm.Realm

class MainActivity : AppCompatActivity() {

    private lateinit var adapter: MemoListAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val recyclerView = findViewById<RecyclerView>(R.id.memo_list)
        adapter = MemoListAdapter()
        recyclerView.adapter = adapter

        val editText = findViewById<EditText>(R.id.memo_edit_text)
        val addButton = findViewById<Button>(R.id.add_button)

        val realm = Realm.getDefaultInstance()

        addButton.setOnClickListener {
            val text = editText.text.toString()
            if (text.isEmpty()) {
                // テキストが空の場合には無視
                return@setOnClickListener
            }
            // Realmのトランザクション
            realm.executeTransaction {
                // Memoのオブジェクトを作成
                val memo = it.createObject(Memo::class.java)
                // nameに入力してあったtextを代入
                memo.name = text
                // 上書きする
                it.copyFromRealm(memo)
            }
            // テキストを空にする
            editText.text.clear()
        }
        // DBに変更があった時に通知がくる
        realm.addChangeListener {
            // 変更があった時にリストをアップデートする
            updateList(it.where(Memo::class.java).findAll())
        }
        // 初回表示時にリストを表示
        realm.executeTransaction {
            updateList(it.where(Memo::class.java).findAll())
        }
    }

    private fun updateList(memoList: List<Memo>) {
        val items = memoList.map { it.name }
        // 一度クリアしてから新しいメモに入れ替える
        adapter.memoList.clear()
        adapter.memoList.addAll(items)
        // データに変更があったことをadapterに通知
        adapter.notifyDataSetChanged()
    }
}

それでは1つ1つ説明してきます。

val editText = findViewById<EditText>(R.id.memo_edit_text)
val addButton = findViewById<Button>(R.id.add_button)

まずは View の取得です。 findViewById() メソッドによって EditTextButton を取得しています。

val realm = Realm.getDefaultInstance()

次は Realm のインスタンス取得です。これはRealmに対して操作を行う場合には必ず行う操作です。

取得できた Realm インスタンスに対して処理をしていきます。

addButton.setOnClickListener {
    val text = editText.text.toString()
    if (text.isEmpty()) {
        // テキストが空の場合には無視
        return@setOnClickListener
    }
    // Realmのトランザクション
    realm.executeTransaction {
        // Memoのオブジェクトを作成
        val memo = it.createObject(Memo::class.java)
        // nameに入力してあったtextを代入
        memo.name = text
        // 上書きする
        it.copyFromRealm(memo)
    }
    // テキストを空にする
    editText.text.clear()
}

次に addButton を押した時の処理です。

addButton を押した時は editText に入力されている文字列を Realm に保存させます。

まず最初に行なっているのは入力された文字列が空かどうかをチェックしています。空の時には Realm に保存させたくないので無視しているわけですね。

次に realm.executeTransaction() を呼んでいる箇所です。 executeTransaction() の引数はリスナーですが、Kotlinだと {} で書けるようになっています。
ここの中での itRealm ですので、この it に対して処理を書いていけばOKです。

処理の内容を以下に抜き出すと、

// Memoのオブジェクトを作成
val memo = it.createObject(Memo::class.java)
// nameに入力してあったtextを代入
memo.name = text
// 上書きする
it.copyFromRealm(memo)

となっています。

createObject()Memo のオブジェクトを作成しています。

それと同時に Realm のDBにも登録されます。

その下の行で memoname に入力された文字列を代入させています。

memo 自体はすでにDBに登録されているので最後の copyFromRealm() メソッドで上書きしています。

最後の editText.text.clear()editText の中身を空にさせておき、次のメモの入力をしやすくさせるためにしています。

private fun updateList(memoList: List<Memo>) {
    val items = memoList.map { it.name }
    // 一度クリアしてから新しいメモに入れ替える
    adapter.memoList.clear()
    adapter.memoList.addAll(items)
    // データに変更があったことをadapterに通知
    adapter.notifyDataSetChanged()
}

次は少し飛ばして updateList() です。

これは引数の memoList を元に RecyclerView の中身を更新しています。

1行目で String のリストを作り、1度 adaptermemoList を空にしてから新しいリストを追加しています。

最後の行では更新したことを adapter に伝えるために notifyDataSetChanged() メソッドを呼んでいます。

// DBに変更があった時に通知がくる
realm.addChangeListener {
    // 変更があった時にリストをアップデートする
    updateList(it.where(Memo::class.java).findAll())
}
// 初回表示時にリストを表示
realm.executeTransaction {
    updateList(it.where(Memo::class.java).findAll())
}

最後に updateList() を呼んでいる箇所です。2つありますが、中でやっていることは同じことです。

まず1つ目の addChangeListener() は名前の通り、変更があった時にだけ呼ばれます。今回の場合だと文字が入力された状態で addButton を押した時に呼ばれます。

しかしこれだけだと Memo を追加した時にしか表示されません。つまりアプリを起動した直後には何も表示されないことになります。これでは困るので2つ目としては executeTransaction() を使って表示されるようにしています。

それでは中身の説明をすると where() メソッドで、どのclassを対象にするかを設定します。今回は Memo クラスなので Memo::class.java としました。

次に findAll() メソッドを呼んでおりDBに登録されている全ての Memo を取得してきています。あとはそれを引数にして updateList() を呼ぶだけですね。

1つ目はDBに変更があった時で2つ目はアプリ起動時のためです。

goal

それではここでビルドしてみましょう。このような動きになれば完成です!

まとめ

お疲れ様でした!

メモアプリを作成していきました。データベースはほとんどのアプリで利用されています。

アプリを作りながら使い方を覚えていきましょう!