Kotlinカテゴリー記事の一覧です
【Android Kotlin】MVVM+DataBinding+LiveData+ Coroutine+Flow サンプル
Android, Androidstudio, Kotlin, PC, Realm, Windows, 紹介
前回、DataBindingについて解説させていただきましたが、その際にfindViewByIdよりもDataBindingが推奨されているという内容を記載しました。
それと同じように、使っていた技術よりも推奨されるものが出てきたり、新しい考え方であったり、アプリ開発をより効率的に行えるような、便利な機能がどんどん登場しています。
そこで今回は、以前作成したアプリを便利な機能を使って改良し、そこで使ったいくつかの機能について、それぞれ解説していきたいと思います。
アプリの変更点
今回は、前々回のブログで紹介したRealmにデータを保存してそれを表示するアプリに修正を加えたものを使って解説しますので、まずはどのよう変更点があるかコードを見ながら説明します。
修正前のアプリの詳しい内容についてはブログで紹介していますので、まだ見ていないという方はそちらもご覧ください。
機能の追加
アプリの機能自体は修正前とほとんど変わらず、データをrealmに保存して、保存した内容を表示するものですが、新しい機能として「保存」ボタンを押した後に入力したテキストを消去するかどうかの選択肢を表示するようにしました。
MainActivity.kt
まずはMainActivityの変更点から紹介します。
大きく変わった点として、findViewByIdを使用していた箇所をDataBindingに変えてレイアウトと連携させています。
加えて、今回新しく追加した「保存」ボタンを押した後の表示といった各種イベントの登録を行っています。
package com.example.sample_realm import android.os.Bundle import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.example.sample_realm.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val viewModel: SampleViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // レイアウトファイルへ // viewModel(this@MainActivity.viewModel)と // lifecycleOwner(this@MainActivity)を // (binding = it)連携させる ActivityMainBinding.inflate(layoutInflater).apply { viewModel = this@MainActivity.viewModel lifecycleOwner = this@MainActivity }.let { binding = it viewModel.binding = it setContentView(it.root) // アダプターインスタンス作成 viewModel.sampleAdapter = SampleListAdapter( inflater = layoutInflater ) // レイアウトファイルのid/sampleListへ連携させる it.sampleList.adapter = viewModel.sampleAdapter } // Realのイベント設定 viewModel.setRealmEvents() // layoutファイル 各種イベント登録 collectFlow() } // layoutファイル 各種イベント登録 private fun collectFlow() { viewModel.apply { lifecycleScope.launchWhenStarted { clickWriteFlow.collect { AlertDialog.Builder(this@MainActivity) .setCancelable(false) .setMessage("正しく保存できました\n入力欄を初期化しますか?") .setNegativeButton("しない") { _, _ -> // No処理 } .setPositiveButton("する") { _, _ -> // Yes処理 liveDataTextLd.value = "" } .show() } } } } }
SampleListAdapter.kt
ListAdapterで大きな変更点は特にありませんが、以前はこの中にあったViewHolderがなくなって、新しくViewHolderのクラスを作って独立させています。
package com.example.sample_realm import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.example.sample_realm.databinding.ItemSampleBinding class SampleListAdapter( private val inflater: LayoutInflater ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private val sampleList = mutableListOf<String>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SampleViewHolder = SampleViewHolder.create(inflater, parent, false) override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { (holder as SampleViewHolder).bind(sampleList[position]) } override fun getItemCount(): Int = sampleList.size fun updateSampleList(sampleList: List<String>) { // 一度クリアしてから新しいメモに入れ替える this.sampleList.clear() this.sampleList.addAll(sampleList) // データに変更があったことをadapterに通知 notifyDataSetChanged() } }
SampleViewHolder.kt
ViewHolderは先述の通り、ListAdapterの中に含めていたものを新しいクラスとして作成しています。
記述してある処理自体は大きく変わっていませんが、DataBindingに合わせて記述の仕方が変わっています。
package com.example.sample_realm import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.example.sample_realm.databinding.ItemSampleBinding class SampleViewHolder private constructor( private val binding: ItemSampleBinding ) : RecyclerView.ViewHolder(binding.root) { companion object { fun create( inflater: LayoutInflater, parent: ViewGroup, attachToRoot: Boolean ) = SampleViewHolder( ItemSampleBinding.inflate( inflater, parent, attachToRoot ) ) } fun bind( sample: String ) { binding.sampleTextView.text = sample } }
SampleViewModel.kt
修正にあたって、新しく作成されたクラスになります。後述するMVVMの設計モデルに則ってRealm関係の処理やクリックした際の処理を記述しています。また、Coroutineとしての処理を記載し、API通信などを追加できるようにしたり、変数に値をセットしたりしています。
package com.example.sample_realm import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.sample_realm.databinding.ActivityMainBinding import io.realm.Realm import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch class SampleViewModel: ViewModel() { lateinit var binding: ActivityMainBinding lateinit var sampleAdapter: SampleListAdapter private val realm = Realm.getDefaultInstance() // LdプレフィックスでLiveDataと分かるように記載 val liveDataTextLd = MutableLiveData<String>() // FlowプレフィックスでSharedFlowと分かるように記載 val clickWriteFlow = MutableSharedFlow<Unit>() // イニシャライズ init { initInputInfoData() } private fun initInputInfoData() { // コルーチンとして処理開始 viewModelScope.launch { runCatching { // API 通信などここでTry }.onSuccess { // set data liveDataTextLd.value = "" }.onFailure { // エラー }.also { // 後処理 } } } // 書込みボタンクリックイベント fun onClickWrite() { val text = liveDataTextLd.value.toString() //テキストが空の場合には無視をする // if (value.isEmpty()) return if (text.isEmpty()) return viewModelScope.launch { // putData(liveDataTextLd.value.toString()) putData(text) clickWriteFlow.emit(value = Unit) } } private fun putData(value: String) { //テキストが空の場合には無視をする // if (value.isEmpty()) return // Realmのトランザクション realm.executeTransactionAsync { //DataListのオブジェクト作成 val data = it.createObject(DataList::class.java) //nameに先ほど入力されたtextを入れる data.name = value //データの上書きをする it.copyFromRealm(data) } } fun setRealmEvents() { // DBに変更があった時に通知が来る realm.addChangeListener { it -> //変更があった時にリストをアップデートする val list = it.where(DataList::class.java).findAll().map { it.name } //UIスレッドで更新する binding.sampleList.post { sampleAdapter.updateSampleList(list) } } // 初回表示の時にメモ一覧を表示 realm.executeTransactionAsync { val list = it.where(DataList::class.java).findAll().map { it.name } // UIスレッドで更新する binding.sampleList.post { sampleAdapter.updateSampleList(list) } } } }
activity_main.xml
DataBindingをつかってViewModelと結びついています。これによって、EditTextに入力されたデータがViewModelのLiveDataでも使用可能になったり、ViewModelのメソッドにアクセスできるようになっています。
<?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="viewModel" type="com.example.sample_realm.SampleViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/sample_edit_text" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="テキストを入力してください" android:text="@={viewModel.liveDataTextLd}" android:textColor="#000000" 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="保存" android:onClick="@{() -> viewModel.onClickWrite()}" android:textColor="#000000" android:textSize="20sp" app:iconTint="#000000" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/sampleList" 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/sample_edit_text" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
使った機能や設計モデルの紹介
コードを見ながらどのような変更があったのか、というのを簡単に説明しましたので、続いてはそこで使った機能や設計モデルについて詳しく紹介していきたいと思います。
MVVM
ViewModelの説明にあったように、MVVMの設計モデルを使った形に変えています。
MVVMはModel, View, ViewModelの頭文字をとったもので、MVCから派生した設計モデルになります。
MVCと同じ部分もありますが、MVVMのそれぞれについて解説していきます。
Model
Modelに関しては、MVCと同じくシステムの中のビジネスロジックを担当している部分になります。データの処理を行って、データの変更をViewに通知したりします。
View
ViewもMVCと同じ役割で、Modelが扱っているデータを取り出して、UIへの出力などを行っています。
View Model
ViewModelはViewとModel間の伝達や、状態の保持を担当します。
今回のサンプルでは、新しくViewModelのクラスを作成し、それぞれとデータのやり取りを行っています。
DataBinding, LiveData
DataBindingは前回のブログで紹介しましたが、その際は外部からViewの表示を変えることができるという単方向のみの説明でした。しかし、DataBindingには双方向でのデータのやり取りをする機能もあります。
双方向の場合は例にあるように “@={viewModel.liveDataTextLd}” という様に@の次に “=” を入力する必要があります。これによって、Viewからもデータを送るということが可能になります。
また、その際に LiveData というデータを監視するクラスを使用することで、テキスト入力欄に変更があるたびに、自動でデータの更新を行ってくれます。
Coroutine
Coroutineは非同期処理を簡略化することができるデザインパターンのことです。
今回の例では、非同期処理をする必要がほとんどありませんでしたが、コードを紹介していた時にも触れたように、メインの機能と合わせて、API通信をする時等に使用すると非常に役立つものになっています。
Flow
Flowは先ほど紹介した Coroutine の一種であり、サンプルでは MutableSharedFlow として実装されていたものになります。今まで使われていたRxと同じような動きをすることが可能で、複数の値を順次出力できるため、データベースからリアルタイムで更新情報を受け取る際などに便利な機能です。
サンプルではクリックされた際に、MainActivityのイベント登録処理にデータを渡しています。
まとめ
いかがでしたでしょうか。説明を読んだだけでは理解が難しい部分もあると思いますが、自分でそれぞれの機能を使ってみるとより理解が深まりますので、アプリ開発の際にはぜひ活用してみてください。
【Kotlin】
DataBindingを
利用しよう!
Android, Androidstudio, Kotlin, PC, Windows, 紹介
今までKotlinを使ったアプリ開発において、レイアウトとコードを結びつけるときには findViewById が使われてきました。
もちろん、今でもそれを使うことは可能ですが、現在では Data Binding を使うことが推奨されています。機能としても、 Data Binding の方が優れている点が多いため、使い方を理解しておくと、これからのアプリ開発にも必ず役に立つと思います。
そこで今回は、それの使い方について、例を交えながら解説していきたいと思います。
DataBindingとは
まずは、DataBindingが何かについて簡単に説明します。
DataBindingはGoogleが提供しているJetPackのコンポーネントであり、モデルオブジェクトとレイアウトを結びつける仕組みになります。
これによって、Viewに対する操作をView上で記述できるようになります。
これを使うメリットとしては、Nullセーフである点や型安全という点、findViewByIdの記述をより簡潔に書くことができるので、コードが読みやすくなるといった点があります。
DataBindingの使用方法
では実際に簡単なアプリを作成して、DataBindingの使い方を解説していきたいと思います。
サンプルアプリの紹介
今回ア例として作成するアプリがこちらになります。
内容としては、画面に文字列を表示するだけの簡単なアプリです。
サンプルアプリの作成
プロジェクトの作成
まずはプロジェクトを作成します。
今回は画面に文字を表示するだけなので、Empty Activityを選択して、「Sample DataBinding」という名前で作成しました。
Databindingの有効化
まずは、DataBindingを有効化して使えるようにします。
build.gradle(:app)を開いて、以下のようにコードを記述してください。
android { namespace 'com.example.sampledatabinding' compileSdk 32 // 以下のコードを記述 buildFeatures { dataBinding true }
これでDataBindingのセットアップは完了です。
データオブジェクトの準備
続いて、データオブジェクトを用意します。
今回は文字列を表示するだけのアプリなので、textだけを定義したDataクラスを作成しています。
android { namespace 'com.example.sampledatabinding' compileSdk 32 // 以下のコードを記述 buildFeatures { dataBinding true }
レイアウトの変更
続いて、DataBindingを使用するには、レイアウトの構造自体を変える必要があるので、activity_main.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="data" type="jp.co.chrono.sampledatabinding.Data" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context="jp.co.chrono.sampledatabinding.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{data.text}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
Android Studioを使用している方は、上から2行目の<android.constraintlayout…>と書かれているところをクリックすると、DataBindgingに適した形に変更するかの選択肢が出てくるので、それを選択すると、自動でレイアウトの構造を変更してくれます。
ここで、重要なのが<data>タグで囲まれた記述と、<TextView>タグの21行目 android:text=”@{data.text}” と書かれている箇所になります。
まず、<data>タグで囲まれた部分ですが、ここではDataBindingしたい変数の定義を行っています。
「name」には変数名を、「type」には変数の型を指定することができます。
変数名は今回 “data” とし、型は先ほど作成したDataクラスを指定しています。これによって、Viewの中から “data” というメンバーのデータにアクセスできるようになります。
続いて android:text=”@{data.text}” と書かれた箇所ですが、これで、dataの中のtext変数に入っている文字列をTextViewのTextとして表示できるようになります。
MainActivityの実装
モデルとレイアウトの準備ができたので、最後にMainActivityに追加で記述していきます。ファイルを開いて以下のコードを記述してください。
package jp.co.chrono.sampledatabinding import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import com.example.sampledatabinding.R import com.example.sampledatabinding.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) val data = Data("Hello!") binding.data = data } }
MainActivityでDataBindingを使用するには、 setContentView() の代わりにDataBindingUtilのsetContentView を使います。
これによって、ActivityMainBinding というクラスのオブジェクトを取得することができます。
このBindingオブジェクトはレイアウトファイルそのものをオブジェクト化したようなものであり、先ほどレイアウトファイルで定義した変数を使用することができます。
よって、例のように binding.data に文字列を代入することが可能になります。
実行結果
実際にアプリを起動すると、最初に示した例のように、MainActivityで代入した文字列を画面に表示できているのが確認できると思います。
findViewByIdを使っていた場合と違って、レイアウトファイルで表示する文字を指定しなくても、任意の文字列を表示できるということが分かったと思います。
まとめ
いかがでしたでしょうか。今回紹介したDataBindingの使い方はかなり初歩的なものにはなりますが、実際に作成してみることで、DataBindingの考え方の理解が深まると思いますので、ぜひ参考にしてみてください。
【Kotlin】
Realmにデータを
保存する方法を解説!
Android, Androidstudio, Kotlin, PC, Realm, Windows, 紹介
以前の記事で、Realmに入ったデータを取得する方法について解説しました。
その時の説明では、既にデータを用意しているという前提で話を進めていましたが、実際にアプリを開発するとなると、こちらからデータを取得するだけでなく、データを保存できるようにしたいという場合もあると思います。
なので、今回はKotlinでRealmを使う方法と、Realmにデータを保存、Realmに保存したデータを表示する方法について例を交えながら解説していきたいと思います。
今回は例として使っているRealmのバージョンは 10.11.0 ですので、ご注意ください。
サンプルアプリの紹介
今回の解説で使用するサンプルアプリはこちらになります。
入力したテキストをRealmに保存し、保存した内容を画面に表示するという機能を持っています。
今回はこのサンプルをもとに解説をしていきたいと思います。
サンプルアプリの作成
プロジェクトの作成
まずはサンプルのアプリを作るためのプロジェクトを作成します。最初に選ぶプロジェクトは何でも大丈夫ですが、今回は Empty Activity を選択しました。名前は「Sample-realm」としています。
Realmの導入
プロジェクトの作成が完了したら、Realmを導入して使えるようにします。
まずは、”build.gradle(アプリ名)” を開いてください。
既に数行記載がありますが、一番上に以下のコードを記述します。
// build.gradle(Sample-realm) // Top-level build file where you can add configuration options common to all sub-projects/modules. // 以下のbuildscriptを記述 buildscript { repositories { mavenCentral() } dependencies { classpath "io.realm:realm-gradle-plugin:10.11.0" } }
これでRealmのプラグインを追加しています。バージョンはその時によって変わるので、適宜確認してください。
続いては、”build.gradle(:app)” と書かれたファイルを開いてください。その一番上にpluginsと書かれた箇所があると思います。そこに以下のコードを記述します。
// build.gradle(:app) plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' // 以下のコードを記述 id 'org.jetbrains.kotlin.kapt' } //以下のコードを記述 apply plugin: "realm-android"
すると画面の上部にSync nowという表示が出てくると思いますので、そちらを押してください。
Realmの初期化
Realmの導入は完了しましたので、続いてはRealmの初期化を行っていきます。
Applicationクラスの追加
まずは、Applicationクラスを追加したいので、MainActivityと同じ階層に○○Application(サンプルはSampleApplication)というクラスを作成します。作成したら、中身を編集していきます。
まずは、Realmを使えるように以下のコードを記述してください
// SampleApplication.kt package jp.co.chrono.sample_realm import android.app.Application import io.realm.Realm class SampleApplication: Application() { override fun onCreate() { super.onCreate() Realm.init(this) //Realmの初期化 } }
ここではRealmが使えるようにimportと、onCreate()の中でRealmを初期化しています。
Manifestに記述
Applicationの作成が完了したら、それを “AndroidManifest.xml” に追加します。
applicationタグの中に android:name=”Application名” を記述してください
<application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:name="jp.co.chrono.sample_realm.SampleApplication" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Samplerealm" tools:targetApi="31">
この記述でアプリのApplicationクラスがSampleApplicationになります。
これでRealmの初期化も完了です。
レイアウトの設定
続いては、画面に表示するレイアウトの実装をしていきます。ただ、今回の趣旨とは少し離れるので、細かい解説等は省略します。
リストの作り方については、以前のブログでも紹介していますので、気になった方はそちらも参考にしてください。
RecycleViewの追加
今回は、表示する内容がリストになっているので、RecycleViewを使いたいと思います。
まずは、 “build.gradle(:app)” を開いて、dependenciesの中に以下のコードを記述してください。
dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' //この一行を追加 implementation 'androidx.recyclerview:recyclerview:1.2.1' }
ここでSync Nowが表示されたら、そちらも押してください。
画面のレイアウト
つづいては、画面のレイアウトを設定するので、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="jp.co.chrono.sample_realm.MainActivity"> <EditText android:id="@+id/sample_edit_text" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="テキスト入力" android:textColor="#000000" 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="保存" android:textColor="#000000" android:textSize="20sp" app:iconTint="#000000" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/sample_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/sample_edit_text" /> </androidx.constraintlayout.widget.ConstraintLayout>
続いて、リストを表示する新しいファイル(サンプルはitem_sample.xml)を作成し、以下のコードを記述してください。
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/sample_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:textSize="20sp" />
これでレイアウトの実装は完了です。
Adapterの実装
続いて、Adapterの実装をするため、Adapterのクラス(サンプルではSampleListAdapter.kt)を作成してください。
作成できたら、以下のコードを記述します。
package jp.co.chrono.sample_realm import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.example.sample_realm.R class SampleListAdapter: RecyclerView.Adapter<SampleListAdapter.SampleViewHolder>() { private val sampleList = mutableListOf<String>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SampleViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sample, parent, false) return SampleViewHolder(view) } override fun onBindViewHolder(holder: SampleViewHolder, position: Int) { holder.bind(sampleList[position]) } override fun getItemCount(): Int = sampleList.size fun updateSampleList(sampleList: List<String>) { // 一度クリアしてから新しいメモに入れ替える this.sampleList.clear() this.sampleList.addAll(sampleList) // データに変更があったことをadapterに通知 notifyDataSetChanged() } class SampleViewHolder(view: View): RecyclerView.ViewHolder(view) { fun bind(sample: String) { val textView = itemView.findViewById<TextView>(R.id.sample_text_view) textView.text = sample } } }
この中では、onBindViewHolder()の中でSampleViewHolderのbind()を呼び出し、TextViewに文字列を入れています。
Adapterが作成できたので、これをRecyclerViewにセットします。
セットするには “MainActivity.kt” の中に以下のようにコードを記述します。
package jp.co.chrono.sample_realm import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Button import android.widget.EditText import androidx.recyclerview.widget.RecyclerView import com.example.sample_realm.R import io.realm.Realm class MainActivity : AppCompatActivity() { private lateinit var adapter: SampleListAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val recyclerView = findViewById<RecyclerView>(R.id.sample_list) adapter = SampleListAdapter() recyclerView.adapter = adapter
SampleListAdapterのadapterを定義して、それをonCreate()の中で代入しています。
その後、findViewById()で sample_list を取得してadapterに代入しています。
これでレイアウトの設定も完了です。
Realmとの連携
それでは、今回の本題であるRealmとの連携方法について解説していきます。
まずは、テキストを保存するためのデータ形式を定義するための、クラス(サンプルではDataList.kt)を作成してください。
中身は以下のコードを記述して下さい。
package jp.co.chrono.sample_realm import io.realm.RealmObject open class DataList: RealmObject() { var name: String = "" }
今回は、保存する内容がテキストだけなので、nameのみの記述になっています。
そして、ここで重要な点が RealmObjectを継承している点と、open修飾子を付ける点になります。この二つはどちらもRealmライブラリの中で処理をするために必要な記述なので、Realmを使う際は忘れないようにしましょう。
データ形式の定義ができたら、文字列が入力された状態で保存ボタンを押すと画面に表示される機能を実装していきます。
MainActivity.ktをを開き、先ほどの記述の下に新しくコードを記述してください。
val editText = findViewById<EditText>(R.id.sample_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 { //DataListのオブジェクト作成 val data = it.createObject(DataList::class.java) //nameに先ほど入力されたtextを入れる data.name = text //データの上書きをする it.copyFromRealm(data) } //テキスト入力欄を空にする editText.text.clear() }
内容について解説していきます。
まずは1,2行目では findViewById を使って EditText と Button のViewを取得しています。
4行目ではRealmのインスタンスを取得しています。この記述はRealmの操作を行うときには必ず記述しないといけないので、これも忘れないように注意しましょう。
6から24行目はボタンを押したときの処理を記述しています。
空のテキストを保存しないように、最初に文字列が入っているかのチェックをしています。文字列が入力されている時は次の realm.executeTransactionAsync() の処理に進みます。
そこではまず、DataListのオブジェクトを作成します。この時、同時にRealmのDBにも登録がされます。
その後、オブジェクトに入力された文字列を代入し、copyFromRealm() でデータを上書きしています。
これで、文字列をDBに登録することができます。
その後の editText.text.clear は次の内容を入力しやすいように、テキスト入力欄を空にしています。
以上の内容が記述できたら、その下にまた以下のコードを記述します。
// DBに変更があった時に通知が来る realm.addChangeListener { //変更があった時にリストをアップデートする val sampleList = it.where(DataList::class.java).findAll().map { it.name } //UIスレッドで更新する recyclerView.post { adapter.updateSampleList(sampleList) } } // 初回表示の時にメモ一覧を表示 realm.executeTransactionAsync { val sampleList = it.where(DataList::class.java).findAll().map { it.name } // UIスレッドで更新する recyclerView.post { adapter.updateSampleList(sampleList) } } } }
この記述は二つに分かれていますが、どちらもAdapterの updateSampleLIst() を呼び出しています。
一つ目は、DBに変更があった際に表示しているリストを更新しています。
しかし、それだけだとアプリを起動した時に何も表示されないので、初回表示の時にもDBの内容を表示するようにしています。
詳しい内容としては、where().findAll() を使って、DBに登録されているDataListを取得し、それを引数にして updateSampleList() を呼び出しています。
アプリの完成
以上でサンプルアプリの作成は完了です。
実際に触ってみると、入力したテキストを保存できていることが確認できると思います。
まとめ
いかがでしたでしょうか。今回は基本的な内容の解説になりましたが、今後もRealmを使う機会は増えてくると思いますので、入門としてぜひ参考にしてみてください
【Kotlin】
渡っているデータを
確認しよう!
【RxKotlin】
kotlinを使ってアプリ開発を行っていると、RxKotlinを使ってデータそれぞれに処理を行うことがあると思います。
その際、処理を行うデータがちゃんと渡っていなければ上手く実行することができない場合があります。なので、意図しないエラーを減らすためにも、実装時にはデータを確認するのがおすすめです。
今回は、doOnSuccessやdoOnErrorの処理をする時にちゃんとデータが渡っているか確認する方法について紹介しますので、ぜひ参考にしてみてください。
RxKotlinとは
このブログを見ている方はすでにご存じだとは思いますが、RxKotlinについて説明したいと思います。
ただ、説明のためにはまずRxを理解する必要があるので、こちらについても簡単に説明します。
Rxは Reactive Extention を略したもので、Microsoftが開発した概念になります。
データが生成されるたびに送信されるデータに対して、その都度処理を行って目的を達成するといった考え方のことで、リアクティブプログラミングとも呼ばれています。
そして、RxをJavaに導入したRxJavaというライブラリがあり、それをKotlinで使いやすくするための軽量ライブラリがRxKotlinになります。
データの確認方法
では、実際にRxKotlinを使った際のデータの確認方法をお伝えします。
結論からお伝えすると、データを確認したい時は Log.d(“tag”, “出力内容”) を使ってログを出力すればOKです。
かなり簡略化している例ですが、実際に書くとこのような形になります。
dataList() //渡したいデータ .map { item -> Log.d("tag", item.toString()) //処理内容 } .doOnSuccess { res -> Log.d("tag", res.toString()) //処理内容 } .doOnError { e -> Log.d("tag", e.toString()) //処理内容 }
処理した後の結果を出力したい場合は、処理内容の後にログを入れれば大丈夫です。
これで、それぞれの処理が行われる際に、渡っているデータの内容がログに出力されるようになりますので、必要なデータに対して処理が行われているか確認することができます。
まとめ
いかがでしたでしょうか。普段と違う書き方であったり、処理の仕方が違ってくると、動きが分かりにくくなってしまうこともあると思います。そのような時はいつも通りログを出力すると把握がしやすくなると思いますので、ぜひ確認してみてください。
【Android Studio】
デバッグ機能を
活用しよう!
Android, Androidstudio, Kotlin, ツール, 紹介
アプリを実行してみたらエラーが出たり、バグが残っていてうまく動かないというのはアプリ開発をしていく上で必ず起こる現象です。
もちろんそのままではリリースすることができないので、デバッグの作業が必要になります。
Android Studioではデバッグ作業を効率的に行うための機能が備わっていますので、今回はその機能について詳しく解説していきたいと思います。
デバッガー
デバッガーはデバッグのサポート機能であり、Android Studioに限らずIDEには必ずと言っていいほど搭載されています。
こちらを使うとアプリケーションが動いている途中でプログラムを一時停止させて、その内容を詳しく確認することができます。
Android Studioのデバッガーの使い方
実際に例を交えながら説明していきたいと思います。
今回は以前ブログで紹介した、リストを表示するアプリのコードに対してデバッグを行っていきます。
ブレイクポイントの設置
先ほどデバッガーでプログラムを一時停止するということを説明しましたが、まずはどこで一時停止するのかを指定しなければなりません。その一時停止をする箇所を「ブレイクポイント」と呼びます。
デフォルトでは、ブレイクポイントが付いたコードが実行される直前で毎回一時停止するようになります。
ブレイクポイントを設置するには、設置したい行の左端をクリックします。
赤い丸が表示され、ブレイクポイントの設置が完了になります。
デバッグの実行
ブレイクポイントの設置が完了したら、アプリをデバッグで実行します。
Android Studioの右上、虫のようなマークがデバッグになります。
デバッグを実行するとアプリが起動し、ブレイクポイントに来ると一時停止します。
止まっている箇所のコードは背景が青になり、今どこで止まっているかということも確認しやすくなっています。
また、下のタブから「Debug」タブを選択すると現在の変数の状態なども確認することができます。
画像では「employee1~6」にどのような値が入っているかなどを確認することができます。
ステップ実行
ブレイクポイントまでの内容が確認出来たら、ステップボタンでそこから先のコードを実行することができます。ステップボタンはデバッグウィンドウの上部にあるボタンです。
画像で表示されているように、いくつか種類がありますので、左から簡単に説明します。
・Step Over
メソッド内には入らず次の行に進む
・Step Into
呼び出し先のメソッドの一番最初の行に進む
・Force Step Into
Step Intoでスキップしてしまう箇所を実行する
・Step Out
現在のメソッドを抜けて次の行に進む(※現在のメソッドはすべて実行される)
これらを使って一つずつ処理の内容を確認することができますので、状況に合わせて使ってみてください。
デバッグの終了
デバッグが終わったらデバッグウィンドウの左側から「Resume Program」でステップ実行の終了。「Stop app (画面右上のボタンでも可)」でデバッグ自体の終了をすることができます。
まとめ
いかがでしたでしょうか。この機能を使えば、どこにバグがあるのか、どこでエラーが発生しているのかなどを効率的に探すことができます。
もし、うまくアプリが動かないということがありましたら、この機能を使って一つ一つ確認していくと、結果的に早く解決するということもありますので、ぜひ活用してみてください。
【Kotlin】
保存されたデータを
取得する方法!
【realm】
Android, Androidstudio, Kotlin, Realm, 紹介
realmはスマートフォンやタブレットなどのモバイル向けのデータベースです。
軽量かつ高速であり、実装が容易という特徴も持っています。
また、RealmはKotlinにも対応しているため、Androidのアプリ開発をする際に使っているという方も多いと思います。
そんな中で、realmに保存したデータを取得したい、入っている内容を確認したいという場面があると思います。
そこで今回は、kotlinでrealmのデータを取得する方法を解説したいと思います。
データの用意
前提としてこのようなDBモデルと、以下のデータが入っているとします。
open class Sample : RealmObject() { @PrimaryKey var id: Int = 0 @Required var text: String = "" }
id | text |
1 | Sample1:おはようございます! |
2 | Sample2:こんにちは! |
3 | Sample3:こんばんは! |
realmからデータを取得する方法
では実際にrealmに保存されたデータを取得する方法を解説します。
取得する方法
以下の方法でデータを取得することができます。
findAll() を使うとすべてのデータ、findFirst() を使うと最初の一つだけのデータを取り出すことができます。
//Realmのインスタンスを取得する val realm = Realm.getDefaultInstance() //Realmからすべてのデータを取得する val results = realm.where(Sample::class.java).findAll() //Realmから最初のデータを取得する val result = realm.where(Sample::class.java).findFirst() //取得したデータをlogに出力 Log.e("realm", "$results") Log.e("realm", "$result") //Realmオブジェクトを削除する realm.close()
データをログに出力させれば、どういった内容が保存されているのかすぐに確認できますので、状況に合わせて追加してみてください。
//Logcatの出力例 //findAll() Sample = proxy[{id:1},{text:Sample1:おはようございます!}], Sample = proxy[{id:2},{Sample2:こんにちは!}], Sample = proxy[{id:3},{text:Sample3:こんばんは!}] //findFirst Sample = proxy[{id:1},{text:Sample1:おはようございます!}]
検索で使える条件
上記の例では、データを取得する際にfindAll()とfindFirst()を使って取得するデータの数を変えていました。
それに加えて他の条件を追加することで、特定のデータのみを取り出すということもできます。
今回はその条件コードの中でも使う頻度の高いと思ったものを選んで紹介します
・.equalTo(“フィールド名”, 値)
フィールド名が一致したデータを抽出する
・.greaterThan(“フィールド名”, 値)
指定したフィールド名の値が、条件より大きいデータを抽出する
・.lessThan(“フィールド名”, 値)
指定したフィールド名の値が、条件より小さいデータを抽出する
・.contains(“フィールド名”, “値”)
指定した値を含むデータを抽出する
これらを使えばより効率的にデータを取得することができますので、場合に合わせて使ってみてください。
まとめ
いかがでしたでしょうか。今後もrealmを使う機会は多いと思いますので、知らなかった方はこれを機に学んでみてはいかがでしょうか。
もう既に知っているという方も、復習としてぜひ参考にしていただければと思います。
【Kotlin】
API通信のログを
出力する方法
【okhttp】
Android, Androidstudio, Kotlin, PC, Windows
API通信関係の実装する際には、どのようなデータがやり取りされているのか?というログを見ることができると大変便利です。
そして、そのログを出力する方法としてokhttpを使う方法がありますので、今回はそちらについて解説していきたいと思います。
okhttpとは
okhttpとはHTTP通信を行うためのオープンソースライブラリです。
これを使うことによって、Android標準搭載の機能を使うよりも簡単に通信を行えるようになります。
今回の説明ではokhttp3を使用します。
ログを出力する方法
では早速、ログを出力する方法を解説します。
build.gradleへokhttpを追加する
まず、ログ出力をするために必要な内容をbuild.gradleのdependenciesに追加します
dependencies { implementation platform("com.squareup.okhttp3:okhttp-bom:バージョン情報") implementation "com.squareup.okhttp3:okhttp" implementation "com.squareup.okhttp3:logging-interceptor" }
OkHttpClientを設定する
OkHttpClientでランタイムの設定と一緒に、「.addIntercepter…」の箇所を追加します
val okHttpClient = OkHttpClient.Builder() .connectTimeout(1, TimeUnit.MINUTES) .writeTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES) // ログを出力させる設定 .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .build()
以上で完了です。
出力結果
実際に出力してみるとOkhttpからのメッセージが吐き出されるようになっていると思います。
下の画像では、赤い部分にurlなどの情報が出力されています
これによって、通信の様々な内容が出力されますので、知りたい内容についてどのようなメッセージが出ているのか確認してみてください。
Timeoutの設定
ログの出力方法については以上になりますが、補足として Timeout の設定について解説します。
今回の場合 Timeout は先ほどの okHttpClient 中の「.〇〇Timeout()」という箇所になります。
これによって、実行した処理が完了しない場合、途中でその処理を終了することができます。その際、完了までどの程度待つのかを「(数値, 時間の単位)」で設定できます。今回の例ではそれぞれ60秒間と設定しています。
また、Timeoutにはいくつか種類がありますので、それらを紹介します。
connectTimeout
connectTimeout はクライアントとサーバーが接続する時、相手から応答があるまで何ミリ秒待つのか?を設定できます。設定した時間内に応答がない場合は接続が終了します。
writeTimeout
writeTimeout はデータの書き込みをする際に、書き込み処理が完了するまで何ミリ秒待つのか?を設定できます。設定した時間内に完了しなかった場合は、データの書き込みを終了します。
readTimeout
readTimeout はデータを取得する際に、データの取得が完了するまで何ミリ秒待つのか?を設定できます。設定した時間内に完了しなかった場合は、データの取得を終了します。
もしTimeoutをきちんと設定しないでいると、すぐに処理が終了させられてしまったり、処理に膨大な時間がかかってしまう時に延々と待つことになる可能性もあるので、場合に合わせて設定することをお勧めします。
また、今回はクライアント側からの設定になりますが、サーバー側から通信を行う場合は、サーバー側でTimeoutを設定することも可能です。
まとめ
いかがでしたでしょうか。方法としては難しい内容ではありませんが、とても便利な機能になっていますので、API通信周りの実装を行う際などにぜひ参考にしてみてください。
【Kotlin】
データを一覧で表示!
ListViewの使い方
Android, BtoC, Kotlin, PC, Windows, 紹介
アプリの機能として、配列のデータやリストを一覧として画面に表示させたいということがあると思います。Kotlinでは “ListView” というViewを使うことで、データを一覧で表示することができます。
今回はその表示方法について、解説していきたいと思います。
Adapter
ListViewを使って一覧を表示できると初めに説明しましたが、その際にAdapterというものを使う必要があります。どのAdapterを使うかによって表示できる内容が異なるので、そちらについて簡単に説明します。
ArrayAdapter
まず基本的なものがArrayAdapterを使って表示する方法です。
こちらは文字だけのリストといった、簡単な内容を表示させることができるものになっています。
カスタムAdapter
その他にはAdapterを使いたい形に合わせてカスタムする方法があります。こちらはArrayAdapterと違い、複数のテキストや画像をまとめて表示するということが可能です。
実装方法の解説
サンプル紹介
では、実際にリストを表示する方法について解説していこうと思います。今回はより実践で使える内容をお伝えしたいと思いますので、カスタムAdapterを使ったリストを作成します。
作成する内容は下記に示しているような、所属している社員の「アイコン」「名前」「年齢」を表示するリストになります。
実装方法の解説
では早速実装方法について解説していきます。
今回はlist-trainingという名前でプロジェクトを作成し、その中に記述しています。
ListViewの実装
まずはリストを表示するために不可欠なListViewを実装します。
今回は activity_main.xml の中に<ListView/>タグを使用して実装します。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <!--ListViewの実装--> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
これでListViewの準備は完了です。この後、表示する内容をAdapterを使って設定していきます。
dataクラスの用意
続いて、表示するdataのクラスを用意します。最初に説明したように社員の「名前」「年齢」「アイコン」を表示させたいので、それぞれのパラメータを持つEmployeeクラスを作成します。
package jp.co.chrono.list_training data class Employee( val name: String, val age: String, val imageId: Int )
これでリストに表示するデータのクラスも完成です。
カスタムAdapterの作成
続いて、今回使うCustomAdapterの作成をします。
以下のように記述します。
package jp.co.chrono.list_training import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.TextView import com.example.list_training.R class CustomAdapter(context: Context, var employeeList: List<Employee>): ArrayAdapter<Employee>(context, 0, employeeList) { private val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater // getViewメソッドでレイアウトを設定 override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { // Employeeを取得する val employee = employeeList[position] // レイアウトの設定 var view = convertView if (convertView == null) { view = layoutInflater.inflate(R.layout.list_item, parent, false) } // それぞれの項目を表示するviewの設定 val imageView = view?.findViewById<ImageView>(R.id.image) imageView?.setImageResource(employee.imageId) val title = view?.findViewById<TextView>(R.id.name) title?.text = employee.name val age = view?.findViewById<TextView>(R.id.age) age?.text = employee.age return view!! } }
CustomAdapterは、ArrayAdapterを継承しています。コンストラクタでContextとListを受け取って、ArrayAdapterのコンストラクタに渡しています。
ArrayAdapterの第二引数のresourceは0を渡しておいて、getViewメソッド内で実装します。
また、CustomAdapterはgetViewメソッドをオーバーライドしており、このgetViewメソッド内でそれぞれのレイアウトを設定しています。
引数として渡ってくるpositionは、項目の位置が格納されているので、これを使って対象のデータを取得します。
初回の画面表示では convertView が null で渡ってくるので、新たにレイアウトを設定するようになっていますが、再描画する場合は、最初に設定したレイアウトを再利用するという形になっています。
その後、以下の list_item.xml に実装した各Viewの設定をしています。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="12dp"> <ImageView android:id="@+id/image" android:layout_width="60dp" android:layout_height="match_parent" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:layout_marginStart="16dp"> <TextView android:id="@+id/name" android:textColor="#000000" android:textSize="18sp" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" /> <TextView android:id="@+id/age" android:textColor="#000000" android:textSize="18sp" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout>
ここで、それぞれの項目のレイアウトを変えることができます。表示されているコードはあくまでも例としての内容になりますので、テキストの色や大きさなど、自分の好みに合わせて編集してみてください。
ListViewにCustomAdapterを設定
最後に MainActivity.kt で CustomAdapter を生成し、ListViewのAdapterとして設定します。
今回は、リストとして表示するデータもここで設定します。ただし、その場合アイコンに使う画像はあらかじめインポートしておく必要がありますので、注意してください。
package jp.co.chrono.list_training import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import android.widget.ListView import com.example.list_training.R class MainActivity : AppCompatActivity() { lateinit var customAdapter: CustomAdapter lateinit var employeeList: ArrayList<Employee> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val employee1 = Employee("田中", "25歳", R.drawable.icon_107620_48) val employee2 = Employee("山田", "42歳", R.drawable.icon_113120_48) val employee3 = Employee("斎藤", "37歳", R.drawable.icon_113200_48) val employee4 = Employee("吉川", "29歳", R.drawable.icon_160440_48) val employee5 = Employee("渡辺", "50歳", R.drawable.icon_113150_48) val employee6 = Employee("立花", "46歳", R.drawable.icon_107970_48) employeeList = arrayListOf(employee1, employee2, employee3, employee4, employee5, employee6) val listView = findViewById<ListView>(R.id.list_view) customAdapter = CustomAdapter(this, employeeList) listView.adapter = customAdapter } }
CustomAdapter の第一引数には MainActivity にあたる this を指定し、第二引数でリストの情報を渡しています。
これで実装は完了です。
プログラムの実行
記述したプログラムを実行すると、初めに例として挙げた完成予想図と同じものが表示されます。
まとめ
いかがでしたでしょうか。今回のようにデータを一覧で表示したいという機会は多いと思いますので、その時々に合わせたリストをぜひ作成してみてください。
また、解説だけだと分かりにくい部分があると思いますので、解説したサンプルも実際に作って理解を深めていってください。