Android(Kotlin)でメモアプリを作成していくチュートリアルを解説していきます。
特徴としては、Realmというデータベースを利用することです。
Realmはアプリ開発でよく使われるので、ここで使い方を学習していきましょう。
完成形
テキストのメモを入力ができ、追加ボタンを押すと一覧に表示されていくアプリです。
- Realmの使い方
- ローカルDBの理解
- ローカルDBを使ったアプリの作成方法
- Android Studio Chipmunk | 2021.2.1 Patch 1
- Kotlin 1.6.21
プロジェクトの作成
まずは、プロジェクトを作成していきましょう。
詳細な説明はこちらにありますので、必要があれば参考にしてください。
Choose your project
は Empty Activity
を選択してください。
アプリ名はなんでもOKですが、ここではMemo
としました。
ここでビルドをした時に Android Gradle plugin requires Java 11 to run
のエラーが出た時にはこの記事を参考にしてください。
Realm導入
早速Realm
の導入をしていきましょう。
root配下にある build.gradle
を開いてください。
一番上に以下の buildscript
を追加してあげます。
中にはrealmのpluginが書かれています。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// ↓これを追加する
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:10.11.0"
}
}
...
次に app
配下にある build.gradle
を開いてください。
一番上に plugins
でまとめられた箇所があります。
そこに2行ほど追加しましょう。
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
// この行を追加
id 'org.jetbrains.kotlin.kapt'
}
// この行を追加
apply plugin: "realm-android"
...
最後に上部に Sync Now
というボタンが表示されていると思います。
こちらを押してください。
しばらくしてこのような表示が出ればOKです。
Realm初期化
次にRealmを使うためにRealmの初期化を行なっていきます。
Applicationクラス追加
まずは Application
クラスを追加しましょう。
MainActivity
と同じ階層に MemoApplication
というクラスを追加します。
中身は以下のように記述してください。
アプリ起動時に呼ばれる onCreate()
の中で Realm
を初期化しています。
import android.app.Application
import io.realm.Realm
class MemoApplication: Application() {
override fun onCreate() {
super.onCreate()
Realm.init(this)
}
}
Manifestに記述
先ほど追加した MemoApplication
を AndroidManifest.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"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
</application>
</manifest>
application
タグに android:name=".MemoApplication"
というのを追加しましょう。
これでこのアプリの Application
クラスが MemoApplication
になります。
これでRealmの初期化は完了です。
レイアウト
次はレイアウトを作成していきましょう。
今回はレイアウトにこだわらないので、簡単に実装していきます。
RecyclerViewの追加
今回はメモリストを用意するのでリスト表示のできる RecyclerView
を使っていきましょう。
まずはライブラリの追加です。先ほども app
配下にある build.gradle
を開きましょう。
dependencies
内に下記のように1行追加して下さい。
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// この行を追加する
implementation 'androidx.recyclerview:recyclerview:1.2.1'
...
}
また先ほどと同様に 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:id="@+id/memo_list"
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です。
次はリストのアイテムです。
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
というクラスを作成して下さい。
中身は以下のように実装して下さい。 onBindViewHolder()
の中で MemoViewHolder
の bind()
を呼んでいます。これで 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>() {
private 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) {
holder.bind(memoList[position])
}
override fun getItemCount(): Int = memoList.size
fun updateMemoList(memoList: List<String>) {
// 一度クリアしてから新しいメモに入れ替える
this.memoList.clear()
this.memoList.addAll(memoList)
// データに変更があったことをadapterに通知
notifyDataSetChanged()
}
class MemoViewHolder(view: View): RecyclerView.ViewHolder(view) {
fun bind(memo: String) {
val textView = itemView.findViewById<TextView>(R.id.memo_text_view)
textView.text = memo
}
}
}
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
}
}
ここでは MemoListAdapter
の adapter
を定義しておき、 onCreate()
で代入しています。
さらに findViewById()
メソッドで activity_main
の memo_list
を取得し、先ほど作成した adapter
を代入しています。
ここで一度ビルドしてみましょう。このようになればOKです!
データベースとの連携
それでは次にデータベースと連携していきましょう。
まずは保存するデータ形式を定義していきます。
Memo
というクラスを作成してください。
中身は以下のように実装してください。
import io.realm.RealmObject
open class Memo(
open var name: String = ""
) : RealmObject()
このMemo
というクラスがDBへ書き込むための形式となります。
今回は入力されるメモだけを保存すれば良いのでname
だけになっています。
気をつけることが2点あり1つ目は RealmObject
を継承していることです。Realmライブラリの中で処理をするために必要となります。
2つ目はopen
修飾子を付けることです。これもRealmライブラリの中でこのMemo
クラスを継承するために必要となります。
理由までを理解することは難しいと思うので必要な操作として押さえておくだけでいいと思います。
次は 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.executeTransactionAsync {
// Memoのオブジェクトを作成
val memo = it.createObject(Memo::class.java)
// nameに入力してあったtextを代入
memo.name = text
// 上書きする
it.copyFromRealm(memo)
}
// テキストを空にする
editText.text.clear()
}
// DBに変更があった時に通知がくる
realm.addChangeListener {
// 変更があった時にリストをアップデートする
val memoList = it.where(Memo::class.java).findAll().map { it.name }
// UIスレッドで更新する
recyclerView.post {
adapter.updateMemoList(memoList)
}
}
// 初回表示時にリストを表示
realm.executeTransactionAsync {
val memoList = it.where(Memo::class.java).findAll().map { it.name }
// UIスレッドで更新する
recyclerView.post {
adapter.updateMemoList(memoList)
}
}
}
}
それでは1つ1つ説明してきます。
val editText = findViewById<EditText>(R.id.memo_edit_text)
val addButton = findViewById<Button>(R.id.add_button)
まずは View
の取得です。 findViewById()
メソッドによって EditText
と Button
を取得しています。
val realm = Realm.getDefaultInstance()
次は Realm
のインスタンス取得です。これはRealmに対して操作を行う場合には必ず行う操作です。
取得できた Realm
インスタンスに対して処理をしていきます。
addButton.setOnClickListener {
val text = editText.text.toString()
if (text.isEmpty()) {
// テキストが空の場合には無視
return@setOnClickListener
}
// Realmのトランザクション
realm.executeTransactionAsync {
// 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.executeTransactionAsync()
を呼んでいる箇所です。 executeTransactionAsync()
の引数はリスナーですが、Kotlinだと {}
で書けるようになっています。
ここの中での it
は Realm
ですので、この it
に対して処理を書いていけばOKです。
処理の内容を以下に抜き出すと、
// Memoのオブジェクトを作成
val memo = it.createObject(Memo::class.java)
// nameに入力してあったtextを代入
memo.name = text
// 上書きする
it.copyFromRealm(memo)
となっています。
createObject()
で Memo
のオブジェクトを作成しています。
それと同時に Realm
のDBにも登録されます。
その下の行で memo
の name
に入力された文字列を代入させています。
memo
自体はすでにDBに登録されているので最後の copyFromRealm()
メソッドで上書きしています。
最後の editText.text.clear()
は editText
の中身を空にさせておき、次のメモの入力をしやすくさせるためにしています。
// MemoListAdapter
private fun updateList(memoList: List<String>) {
// 一度クリアしてから新しいメモに入れ替える
adapter.memoList.clear()
adapter.memoList.addAll(memoList)
// データに変更があったことをadapterに通知
adapter.notifyDataSetChanged()
}
次は場所が変わりますが MemoListAdapter
の updateList()
です。
これは引数の memoList
を元に RecyclerView
の中身を更新しています。
1度 adapter
の memoList
を空にしてから新しいリストを追加しています。
最後の行では更新したことを自身の Adapter
に伝えるために notifyDataSetChanged()
メソッドを呼んでいます。
// DBに変更があった時に通知がくる
realm.addChangeListener {
// 変更があった時にリストをアップデートする
val memoList = it.where(Memo::class.java).findAll().map { it.name }
// UIスレッドで更新する
recyclerView.post {
adapter.updateMemoList(memoList)
}
}
// 初回表示時にリストを表示
realm.executeTransactionAsync {
val memoList = it.where(Memo::class.java).findAll().map { it.name }
// UIスレッドで更新する
recyclerView.post {
adapter.updateMemoList(memoList)
}
}
最後に updateList()
を呼んでいる箇所です。2つありますが、中でやっていることは同じことです。
まず1つ目の addChangeListener()
は名前の通り、変更があった時にだけ呼ばれます。今回の場合だと文字が入力された状態で addButton
を押した時に呼ばれます。
しかしこれだけだと Memo
を追加した時にしか表示されません。つまりアプリを起動した直後には何も表示されないことになります。これでは困るので2つ目としては executeTransactionAsync()
を使って表示されるようにしています。
それでは中身の説明をすると where()
メソッドで、どのclassを対象にするかを設定します。今回は Memo
クラスなので Memo::class.java
としました。
次に findAll()
メソッドを呼んでおりDBに登録されている全ての Memo
を取得してきています。あとはそれを引数にして updateList()
を呼ぶだけですね。
1つ目はDBに変更があった時で2つ目はアプリ起動時のためです。
それではここでビルドしてみましょう。このような動きになれば完成です!
まとめ
お疲れ様でした!
メモアプリを作成していきました。データベースはほとんどのアプリで利用されています。
アプリを作りながら使い方を覚えていきましょう!