デジタルの力で仕事効率アップをサポート。お客様の業務を加速させる。

Androidstudioカテゴリー記事の一覧です

【Android Kotlin】MVVM+DataBinding+LiveData+ Coroutine+Flow サンプル

Android, Androidstudio, Kotlin, PC, Realm, Windows, 紹介

前回、DataBindingについて解説させていただきましたが、その際にfindViewByIdよりもDataBindingが推奨されているという内容を記載しました。
それと同じように、使っていた技術よりも推奨されるものが出てきたり、新しい考え方であったり、アプリ開発をより効率的に行えるような、便利な機能がどんどん登場しています。
そこで今回は、以前作成したアプリを便利な機能を使って改良し、そこで使ったいくつかの機能について、それぞれ解説していきたいと思います。


アプリの変更点

今回は、前々回のブログで紹介したRealmにデータを保存してそれを表示するアプリに修正を加えたものを使って解説しますので、まずはどのよう変更点があるかコードを見ながら説明します。

修正前のアプリの詳しい内容についてはブログで紹介していますので、まだ見ていないという方はそちらもご覧ください。

https://ictdoctor.jp/%e3%80%90kotlin%e3%80%91realm%e3%81%ab%e3%83%87%e3%83%bc%e3%82%bf%e3%82%92%e4%bf%9d%e5%ad%98%e3%81%99%e3%82%8b%e6%96%b9%e6%b3%95%e3%82%92%e8%a7%a3%e8%aa%ac%ef%bc%81%e3%80%90realm%e3%80%91/

機能の追加

アプリの機能自体は修正前とほとんど変わらず、データを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の使い方を解説していきたいと思います。

サンプルアプリの紹介

今回ア例として作成するアプリがこちらになります。
内容としては、画面に文字列を表示するだけの簡単なアプリです。

画面にHello!と表示する

サンプルアプリの作成

プロジェクトの作成

まずはプロジェクトを作成します。
今回は画面に文字を表示するだけなので、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() の代わりにDataBindingUtilsetContentView を使います。
これによって、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に保存する

入力したテキストを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の初期化も完了です。

レイアウトの設定

続いては、画面に表示するレイアウトの実装をしていきます。ただ、今回の趣旨とは少し離れるので、細かい解説等は省略します。
リストの作り方については、以前のブログでも紹介していますので、気になった方はそちらも参考にしてください。

https://ictdoctor.jp/%e3%80%90kotlin%e3%80%91listview%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f%e3%83%aa%e3%82%b9%e3%83%88%e3%81%ae%e8%a1%a8%e7%a4%ba%e6%96%b9%e6%b3%95/

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を使う機会は増えてくると思いますので、入門としてぜひ参考にしてみてください

Android Studioを
日本語で使いたい!

Android, Androidstudio, PC, ツール, 紹介

現在、Androidアプリを開発する際にはAndroid Studioを使っている方が多いと思います。
AndroidStudioはGoogleからも推奨されている総合開発環境であり、これからAndroidアプリを開発したいと考えている方にもおすすめのIDEです。
しかし、使用されているのが英語のため、使うのに少し手間取ってしまうということがあるかもしれません。
英語に慣れるためにも、そのまま使った方が良いと思う方もいるとは思いますが、英語がやっぱり苦手で、日本語で使いたいと考えている方も少なからずいると思います。
そこで今回は、日本語で使いたいという方に向けて、Android Studioを日本語化する方法について解説したいと思います。

また、今回はAndroid Studio Dolphinの解説になりますので、ご注意ください。


バージョン確認

では早速、日本語化の手順を紹介していきたいと思いますが、まずは使っているAndroid Studioのバージョンを確認します。

バージョンを確認する場合はAndroid Studioを開き、画面上部の[Help]から[About]を選択します。

画面上部から[Help]を選択
一番下の[About]を選択

すると、画像のようにAndroid Studioのバージョン情報が表示されます。
この時に確認していただきたいのが、Build #AI-と書かれた箇所の次の数字です。今回は213…と続いています。

バージョン情報が表示される。

日本語化のPluginをダウンロードする

バージョンの確認ができましたら、日本語化するためのPluginをAndroid Studioに追加していきます。
Android StudioのPlugin MarketplaceにはDolphinに対応したPluginが存在しないため、JetBrains公式の言語パックのjarファイルをPluginとして追加します。

Pluginは以下のサイトから入手できます。
https://plugins.jetbrains.com/plugin/13964-japanese-language-pack——

サイトが表示されたらVersionsタブを選択します。

versionsを選択

バージョン一覧が表示されたら、その中から自分が使っているAndroid Studioのバージョンと同じものを探します。今回は先ほど確認したようにバージョンが213…だったので、213系の中で最新のもの (213.370) を選択します。

Android Studioが213…とあったので213系の中で最新のものを選択

選択すると細かい情報が出てきますので、問題なければダウンロードします。

右上の「Download」ボタンからダウンロード

PluginをAndroid Studioに適用する

日本語化のファイルをダウンロードしたら、Android Studioの最初の画面を開き、左側の「Plugin」を選択します。その後、右上の歯車マークから「Install Plugin from Disk…」を選択します。

Android Studioの最初の画面。ここからPluginをインストールする

インストールするファイルを選択する画面が出てきますので、先ほどダウンロードしたファイルを選択し、OKを押します。

ダウンロードした ja.213.370.zipを選択

選択したPluginが表示されますので、右上の「Restart IDE」を押して、Android Studioを再起動します。

この状態ではまだ日本語化されていないため、Android Studioを再起動する

これで日本語化が完了します。
画像のように、英語の表示が日本語に変わっているのが確認できると思います。

日本語表示に変わる
他の表示も日本語になっている

まとめ

いかがでしたでしょうか。日本語で表示されるとより細かい設定も簡単にできるようになると思います。もし、英語表示で使いづらく感じるといったようなことがあれば、一度日本語表示にしてみて使ってみると良いかもしれません。

【Android Studio】
Layout Inspectorで
デバッグしよう!

Android, Androidstudio, PC, ツール, 紹介

前回の記事でAndroid Studioのデバッガーについて紹介しました。デバッガーによってアプリの中でどの機能が動いているか、どこに問題があるのかを効率的に探すことが可能になりました。

ただ、アプリにおいては機能以外に、レイアウトも重要な要素になってきます。
せっかく思った通りの動きをするようになっても、それが表示されなかったり、レイアウトが崩れていたりしたらリリースすることはできません。

そこで今回はAndroid Studioでレイアウトのデバッグをする方法について紹介します。
使用しているバージョンは “Android Studio Dolphin | 2021.3.1” です。

Layout Inspector

Android Studioにはレイアウトをデバッグする機能として、「Layout Inspector」が備わっています。
Layout Inspectorを使うと、表示しているレイアウトとその構造を確認することができるようになります。エミュレータで実際にアプリを動かしながら確認することができるので、動的なレイアウトのデバッグをする際にとても便利な機能です。

Layout Inspectorの使い方

では実際に使う方法を解説します。

Layout Inspectorを開く

Layout Inspectorを使うには、Android Studioの画面上部 [Tools]から[Layout Inspector]を選択します。

Android Studioの画面上部からToolsを選択
ToolsからLayout Inspectorを選択

エミュレータでアプリを起動していた場合は、エミュレータで表示している画面がLayout Inspectorにも表示されます。
Layout Ispectorのウィンドウが開く際、他のウィンドウと一緒に表示すると画面が狭くなってしまうので、大きく表示したい場合は別ウィンドウで表示すると良いかもしれません。

画面にレイアウトが表示された後、確認したい箇所をクリックすると細かい情報が表示されます。

選択したレイアウトの細かい情報が確認できる。

機能紹介

Layout Inspector でよく使われる機能を紹介します。

Component Tree

ウィンドウの左側に表示されているのが Component Tree です。
今選択しているレイアウトの階層を一目で確認することができます。また、Component Treeから確認したいレイアウトを選択することも可能です。

選択しているレイアウトの構造を表示する

Attributes

ウィンドウの右側に表示されているが Attributes です。
ここには、選択しているレイアウトの属性が細かく表示されます。

選択しているレイアウトの属性が細かく表示される

Live updates

レイアウト表示の上、デバイスに青いサイクルのマークが表示されているボタンが Live updates になります。この機能がオンになっていると、繋がっているデバイスやエミュレータの画面が変わると同時に、Layout Inspectorでも画面が切り替わるようになります。

デバイスの画面がリアルタイムで反映される

3D Mode

レイアウト表示の右下のボタンから、レイアウトを3Dで確認することができるようになります。
平面だけでは確認が難しいレイアウトの重なりなども、別角度から見ることができるので、視覚的にも構造が分かりやすくなります。
もう一度ボタンを押すと、平面の表示に戻ります。

別の角度から、レイアウトの構造を確認できる

Layout Inspectorを終了する

レイアウトの確認が終わったら、表示しているプロセスの欄を選択し、[Stop Inspector] からLayout Inspectorを終了することができます。

Stop Inspectorで終了

まとめ

いかがでしたでしょうか。画面のレイアウトはアプリの中でも重要な要素の一つです。Layout Inspector はレイアウトの情報を細かく確認することができる便利な機能ですので、レイアウトのデバッグをする際にぜひ活用してみてください。

【Android Studio】
デバッグ機能を
活用しよう!

Android, Androidstudio, Kotlin, ツール, 紹介

アプリを実行してみたらエラーが出たり、バグが残っていてうまく動かないというのはアプリ開発をしていく上で必ず起こる現象です。
もちろんそのままではリリースすることができないので、デバッグの作業が必要になります。

Android Studioではデバッグ作業を効率的に行うための機能が備わっていますので、今回はその機能について詳しく解説していきたいと思います。

デバッガー

デバッガーはデバッグのサポート機能であり、Android Studioに限らずIDEには必ずと言っていいほど搭載されています。
こちらを使うとアプリケーションが動いている途中でプログラムを一時停止させて、その内容を詳しく確認することができます。


Android Studioのデバッガーの使い方

実際に例を交えながら説明していきたいと思います。
今回は以前ブログで紹介した、リストを表示するアプリのコードに対してデバッグを行っていきます。

リストを表示するアプリの一部

ブレイクポイントの設置

先ほどデバッガーでプログラムを一時停止するということを説明しましたが、まずはどこで一時停止するのかを指定しなければなりません。その一時停止をする箇所を「ブレイクポイント」と呼びます。
デフォルトでは、ブレイクポイントが付いたコードが実行される直前で毎回一時停止するようになります。

ブレイクポイントを設置するには、設置したい行の左端をクリックします。
赤い丸が表示され、ブレイクポイントの設置が完了になります。

34行目にブレイクポイントを設置する場合、34の数字の右側をクリック
ブレイクポイントの印である赤い丸が表示される

デバッグの実行

ブレイクポイントの設置が完了したら、アプリをデバッグで実行します。
Android Studioの右上、虫のようなマークがデバッグになります。

画面右上の虫のマークからデバッグで実行

デバッグを実行するとアプリが起動し、ブレイクポイントに来ると一時停止します。
止まっている箇所のコードは背景が青になり、今どこで止まっているかということも確認しやすくなっています。

現在止まっている箇所が青く表示される

また、下のタブから「Debug」タブを選択すると現在の変数の状態なども確認することができます。
画像では「employee1~6」にどのような値が入っているかなどを確認することができます。

画面下の「Debug」を選択
デバッグウィンドウが開き、変数の確認などができる

ステップ実行

ブレイクポイントまでの内容が確認出来たら、ステップボタンでそこから先のコードを実行することができます。ステップボタンはデバッグウィンドウの上部にあるボタンです。

デバッグウィンドウ上部にステップボタンがある

画像で表示されているように、いくつか種類がありますので、左から簡単に説明します。

・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 = ""
}
idtext
1Sample1:おはようございます!
2Sample2:こんにちは!
3Sample3:こんばんは!
保存されているデータ

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通信周りの実装を行う際などにぜひ参考にしてみてください。