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

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

VScodeでWSL上のRailsアプリをデバッグする方法

CentOS, Linux, PC, Ruby, VScode, Windows, WSL2, ツール, 紹介

皆さんはRuby on Railsでアプリを作成した後、そのデバッグをどのように行っているでしょうか?
様々な方法が考えられると思いますが、実はVScodeでもデバッグを行うことが可能です。
今回はWSL2で構築したLinux環境にVScodeから接続し、その中のrailsアプリのデバッグを行う方法について解説しましたので、ぜひ参考にしてみてください!


実行環境

はじめに説明したように、今回はまずWSL2を使ってWindows上にLinux環境を構築してから、railsアプリのデバッグを行います。

今回の実行環境はこちらになります
Windows11
WSL2
CentOS7
Rails 5.2.6.2
ruby 2.7.7
VScode1.74.3


WSL2でLinux環境の構築

まずは、WSL2上でLinuxの環境構築をする必要がありますが、WSL2のインストール方法については以前のブログの途中で紹介していますので、分からない方はそちらで確認してください。

ただ、以前の記事ではUbuntuをインストールしているため、今回のCentoOS7のインストール方法とは異なる点にご注意ください。


Rubyのインストール

続いて、Rubyのインストール方法を紹介します。
今回はrbenvを入れた後に、それを経由してrubyのインストールを行います。

まずは以下のコマンドを実行してrbenvと、ruby-buildをインストールしてください

# rbenvインストール
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv

# ruby-buildインストール
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

続いて環境変数へPath設定を行います

echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile

# 環境変数を反映
source ~/.bash_profile

続いてrubyをインストールします。今回は例として2.7.7をインストールしていますが、使用したいバージョンを指定すれば、それのバージョンのインストールができますので、適宜変更してください。

# rubyインストール
rbenv install 2.7.7

# rbenv の再読み込み
rbenv rehash

# ローカルへ反映設定(ターミナル)
rbenv local 2.7.7

# グローバルへ反映設定(全体)
rbenv global 2.7.7

最後にbundlerとrailsのインストールを行います。以下のコマンドを実行してください。

# bundler のインストール
gem install bundler

# rails のインストール
gem install rails

これでrailsまでのインストールは完了になります。railsアプリの作成については説明しませんので、デバッグに使用するアプリはそれぞれ用意してみてください。


VScodeのインストール

それではVScodeと、必要なプラグインのインストールについて説明します。

VScodeのインストール

まずはVScodeをインストールします。
公式サイトより Download for Windows ボタンを押すと自動でダウンロードが始まります。

公式サイトからダウンロードボタンをクリック

それが終わったら、そのファイルを開いてインストーラーを開きます。特に設定したい内容がなければそのままインストールして問題ありません。
VScodeの起動までできたらインストールは完了です。

プラグインのインストール

まずはVScodeでRubyを扱うためのプラグインをインストールします。

Vscodeを起動して、サイドメニューから拡張機能を選択し「Ruby」と検索してください。
候補がいくつか表示されると思いますが、「Ruby」と今回はrailsも使いますので「Ruby on Rails」をインストールしてください。

Ruby, Ruby on Rails の拡張機能をインストール

また、今回はWSL上のrailsアプリをデバッグするため、そのままではアプリの存在するディレクトリにアクセスすることができません。
そのため、VScodeからWSLにアクセスするためのプラグインもインストールする必要があります。

先ほどと同様に「WSL」と検索し、表示される “WSL” という拡張機能をインストールしてください。こちらがWindowsとLinux環境を繋げるプラグインになります。

WSLと接続するためのプラグインをインストール

VScodeでデバッグを行う

CentOS7と接続

先ほどのプラグインをインストールすると、左下に緑色の表示が出てくるので、そちらをクリックしてください。
するとVscodeの上部に「リモートウィンドウを開くオプションを選択します」という表示が出てくるので、その中の「ディストリビューションを使用した新しい WSL ウィンドウ…」を選択します。
すると、WSL上にあるディストリビューションが表示されますので、接続したいもの (例ではCentOS7) を選んでください。
新しく開いたウィンドウの左下に “WSL : CentOS7” と表示されていれば接続成功です。

上から2番目の選択肢をクリック
左下に接続している表示

必要なgemのインストール

接続が完了すると、CentoOS7のディレクトリを開くことができるようになりますので、railsアプリの入ったディレクトリを選択して開いてください。
デバッグの際には以下のgemが必要になりますので、インストールされていない場合はGemfileに以下を記述してインストールしてください。

group :development do
  gem 'ruby-debug-ide'
  gem 'debase'
end

VScode上でデバッグの設定

続いて、VScodeのデバッグ機能を使うための設定を行います。
サイドメニューから”実行とデバッグ”を選択してください。
出てきた表示の中、「実行とデバッグ」の下にある「launch.jsonファイルを作成します。」を押してください。するとVScodeの上部に「デバッガーの選択」の表示が出てくるので、Ruby を選択してください。そうすると、自動でlaunch.jsonというファイルが作成されます。
続いても選択肢が表示されると思いますが、今回はrails アプリのデバッグを行うので、”Rails Server” を選択してください。
そこまで選択すると、launch.jsonファイルの中に自動でデフォルトの内容が記述されます。

launch.json ファイルを作成

launch.json ファイルの編集

先ほど、デバッグのために必要なjsonファイルを作成しましたが、その状態ではデバッグをしようとしてもエラーが表示される場合がありますので、それぞれに合わせた設定をする必要があります。
今回は、下記のようにjsonファイルを編集してください。

{
  "version": "0.2.0",
  "configurations": [
      {
        "name": "Debug Rails server",
        "type": "Ruby",
        "request": "launch",
        "cwd": "${workspaceRoot}",
        "useBundler": true,
        "pathToBundler": "/home/ユーザー名/.rbenv/shims/bundle",
        "pathToRDebugIDE": "/ruby-debug-ideまでのパス/ruby-debug-ide-0.7.3",
        "program": "${workspaceRoot}/bin/rails",
        "env": {
          "RAILS_ENV": "development"
      },
        "args": [
          "server",
          "-b",
          "0.0.0.0",
          "-p",
          "3000"
        ]
      },
  ]
}

まず、今回はBundlerを使って実行するので35行目に “useBundler” のオプションを有効にしています

その次の36,37行目では、それぞれデバッグに使う bundle と ruby-debug-ide までのパスを記述しています。このパスは使用している環境によって異なりますので、適宜変更してください。

42か47行目の args: にはデバッグ実行時に渡す引数を設定することができますので、サーバー起動時のオプションも同時に設定します。
これでjsonファイルの編集も完了です。

デバッグの実行

そこまで編集すると、実行ボタンが表示されますので、デバッグで使用する構成を選んで、実行を行うとデバッグを行うことができます。

実行とデバッグの横からデバッグを開始

もちろんブレイクポイントも設定することができます。
処理を止めたい箇所の左側をクリックすると、その箇所に赤いマークが表示されブレイクポイントが設定されます。その状態でデバッグを実行するとその箇所で止まり、上の画像の欄にどのような値を持っているか等の情報が表示されます。


まとめ

いかがでしたでしょうか。VScodeでのデバッグは一度設定すれば、その後は実行ボタンを押すだけでデバッグを行うことができます。また、今回のようにWSL上のアプリであっても、VScodeから接続することが可能なので、作業効率も上げることができます。
もし、知らなったという人は、今回の記事を参考にデバッグを試してみてください!

【Linux】環境変数の設定方法と注意点

Linux, PC, Ubuntu, Windows, WSL2, ツール, 紹介

PC上で様々なプログラムを実行する時に重要になるのが、環境変数です。環境変数という言葉は聞いたことがあっても、実際にどういったものなのか、どうやって使うのか?については知らないという方もいらっしゃるかもしれません。
しかし、環境変数を理解しておくことは、PCで何か操作をする上で重要な内容になりますので、今回は環境変数について紹介していきたいと思います。


実行環境

今回はLinuxで環境変数の設定を行っていますが、Macでも同様に設定することが可能です。
ディストリビューションは以下の通りです。

Description : Ubuntu 22.04.1 LTS

補足

今回は bash(バッシュ) の記述で説明を行っています。
MacはOSがCatalinaになってから、標準のターミナルが bash から zhs(ズィーシェル) に変更されているため、シェルを bash に切り替えてから読み進めてください。


環境変数とは

まず、環境変数とは何かについて説明していきたいと思います。
環境変数とは変数の一種であり、OSに値を保存して利用者やプログラムから設定・参照できるようにしたものです。

また、詳しくは後述しますが環境変数は、ローカルのみ、ログインユーザー毎、全ユーザーの環境変数という様に分けることができ、これによって使える範囲等が異なってきます。


環境変数の確認方法

それでは、まずは実際に設定されている環境変数を確認する方法を紹介します。

環境変数を一覧で表示

設定されている環境変数を一覧で表示したいというときは以下のように “env” コマンドを実行します。

$ env

実行すると環境変数が一覧で表示されます。

$ env
SHELL=/bin/bash
WSL2_GUI_APPS_ENABLED=1
WSL_DISTRO_NAME=Ubuntu
 :
PATH=/usr/local/sbin:/usr/local/bin・・・
HOSTTYPE=x86_64
 :

環境変数を絞り込んで表示

先ほどのように一覧で表示すると、数が多くて分かりにくいということがあると思います。その場合は以下のように “grep” コマンドを使って、表示する変数を絞り込むことができます。

$ env | grep 検索したい文字列

例として、検索したい文字列に「WSL」という文字を入れて実行すると以下のような表示が出てきます。

$ env | grep WSL
WSL2_GUI_APPS_ENABLED=1
WSL_DISTRO_NAME=Ubuntu
WSL_INTEROP=/run/WSL/9_interop
WSLENV=WT_SESSION::WT_PROFILE_ID

もちろん、表示される環境変数は設定されている内容によって変わりますので、色々な文字列で試してみてください。

特定の変数を表示

変数名が分かっていて、その値を確認したいというときは “echo” コマンドを使って確認することができます。変数名は “$変数名” で指定します。

$ echo $変数名

例として、変数名LANGの値を確認すると以下のように表示されます。

$ echo $LANG
C.UTF-8

先ほどまでとは違って変数名を指定しているので、値だけが表示されます。


環境変数の設定方法

使用されている環境変数が確認できたところで、次は実際に環境変数を設定する方法について紹介します。

環境変数の追加

新しく環境変数を追加するには、以下のように “export” コマンドを使用します。

$ export 変数名=値

例として「変数名:hoge, 値:abc」といった環境変数を追加する場合は以下のようにコマンドを実行します。実際に設定されたかどうかは確認しないと分からないので、それも確認します。

$ export hoge=abc
$ env | grep hoge
hoge=abc

先ほど説明したgrepコマンドを使うと、問題なく設定されていることが確認できます。
また、PATHのように値が後ろに追加されるようにしたい場合は以下のようにして追加することができます。

$ export hoge=$hoge:def
$ env | grep hoge
hoge=abc:def

環境変数の削除

環境変数を削除する場合は以下のように “unset” コマンドを実行します。

$ unset 変数名

例として、先ほど追加した環境変数hogeを削除します。

$ unset hoge
$ env | grep hoge
(何も表示されなくなる)

環境変数を設定する際の注意点

これで、環境変数の追加、削除の方法が理解できたと思いますが、この時に注意しなければならないことがあります。それは上記で設定したコマンドは”ターミナルを再起動すると元に戻ってしまう“ということです。
つまり、毎回同じ環境変数を使う場合でも、最初に環境変数の追加をしなければならなくなってしまいます。これが初めの方で少し触れた、ローカルのみの環境変数になります。

なので、永続的に環境変数の設定をしたいという場合には、アカウント毎の環境変数か、システム全体の環境変数を設定する必要があります。その場合は以下のファイルを直接編集することで行うことができます。

【~/.bash_profile】 

ログインのたびに実行されます

【~/.bashrc】

シェル(bash)ログインで毎回読込まれ実行されます

【/etc/profile】

全ユーザーに適用されるデフォルトの設定ファイルです。PC起動時に読み込まれます。

【~/.bash_login】

ログインして~/.bash_profileが存在しない場合にのみ、存在していれば実行されます

【~/.profile】

ログインして~/.bash_profile ・ ~/.bash_loginが存在しない場合にのみ、存在していれば実行されます

ログイン時に読み込まれるものはログインユーザー毎の環境変数であり、別アカウントでログインした際には適用されません。全ユーザーの環境変数を設定する際には【/etc/profile】を編集します。

編集するには、先で紹介したコマンドをそのまま記載すればよいので、編集したいファイルを選んで [export hoge=abc] といった内容を追記してください。


まとめ

いかがでしたでしょうか。環境変数について簡単に設定方法などを説明しましたが、実際にコマンドを実行してみると、より理解が深まると思いますのでご自身のPCでも試してみてください。
また、環境変数の種類を意識しないでいると、上手く反映されないということがありますので、実行する際には注意してみてください。

WSL2を使ってDocker開発環境を構築する方法を紹介!

BtoC, Docker, PC, Ubuntu, Windows, WSL2, ツール, 紹介

現在、アプリケーションの開発環境の構築をDockerで行うということが一般的になってきました。ただし、これまではWindows環境で試そうとすると複雑な手順が多く、気軽に試すということが難しいものでした。
しかし、WSL2 (Windows Subsystem for Linux 2) の登場により、WindowsOS上で互換性の高いLinux環境の構築が可能になり、高速でDockerを動かせるようになりました。
そこで今回はWindows上でWSL2を用いてDocker開発環境を構築する方法を紹介します。
また、今回はUbuntuを使用しますので、そちらもWSL2と合わせてインストールします。


実行環境

今回は以下の環境で実行します。

Windowsバージョン : 11 Pro 21H2
OS ビルド : 22000.1335
Ubuntu : 20.04
Docker Desktop : 4.15.0


WSL2, Ubuntuのインストール

WSL2のインストール

それでは早速、WSL2のインストール方法から紹介します。
WSL2のインストールではPowerShellを使用しますので、Wndows PowerShellの”管理者として実行する”を選択します。

Windows PowerShellを管理者として実行

PowerShellが開けたら以下のコマンドを実行します。

wsl --install

その後以下のような表示がでたら、インストールは完了です。表示に出ているように、このコマンド一つで必要な内容をまとめて実行することができます。

PS C:\WINDOWS\system32> wsl --install
インストール中: 仮想マシン プラットフォーム
仮想マシン プラットフォーム はインストールされました。
インストール中: Linux 用 Windows サブシステム
Linux 用 Windows サブシステム はインストールされました。
ダウンロード中: WSL カーネル
インストール中: WSL カーネル
WSL カーネル はインストールされました。
ダウンロード中: GUI アプリ サポート
インストール中: GUI アプリ サポート
GUI アプリ サポート はインストールされました。
ダウンロード中: Ubuntu
要求された操作は正常に終了しました。変更を有効にするには、システムを再起動する必要があります。
PS C:¥Windows¥system32>

インストールが完了したら、PCを再起動します。
Linuxのディストリビューションを指定する場合は以下のようにコマンドを実行してください。

wsl --install Ubuntu-20.04

Ubuntuの設定

WSL2、Ubuntuのインストールが完了すると、Ubuntuが起動するので、ユーザー名とパスワードの設定をしてください。
設定が終われば、Ubuntuが使用可能な状態になります。
PowerShellでも以下のコマンドを実行することで、以下のようにUbuntuの状態がRunningと表示され、WSL2が有効になっているということが確認できます。

PS C:\WINDOWS\system32> wsl -l -v
  NAME                   STATE           VERSION
  Ubuntu-20.04           Running         2
  Ubuntu                 Running         2
PS C:\WINDOWS\system32>

Dockerを利用する手順

Docker Desktop for WIndowsのダウンロード

Ubuntuの設定まで終わった後は、公式サイトからDocker Desktop for WIndowsをダウンロードします。
Dockerの公式サイトを開くと「Download Docker Desktop Windows」というボタンがあるので、こちらをクリックしてください。

公式サイトからDocker Desktopをダウンロード

その後インストーラを起動し、画面に従ってインストールを行ってください。
途中の [Configuration]の画面では「Install required Windows components for WSL 2」にチェックがついているか確認してください。もし、されていない場合はチェックしてください。

Docker Desktop for WIndowsの設定

インストールが終了したら、最後にDocker Desktopの設定を確認します。
起動していない場合はDocker Desktopを起動して、右上の歯車マークから設定画面を開いてください。

Docker Desktopの画面右上の歯車マークから設定画面が開ける

Settingの画面が開けたら、[General]の “Use the WSL 2 based engine” が有効になっていることを確認します。

Setting画面のGeneralでチェックがついているかの確認

その後、サイドバーから[Resource] > [WSL integration]を選択し、”Enable integration with additional distros:” の下からDockerを使用するものの設定を有効にしてから、右下の「Apply & Restart」ボタンを押します。
今回は例としてUbuntu-20.04を有効にしています。

Setting画面の[Resources] > [WSL integration]を開き、Dockerを使用するものを有効にする。

これで、WSLからDocker Desktopを利用することができるようになりました。

Dockerの動作確認

最後にDockerの動作確認を行います。Docker Desktopの設定でUbuntuが有効になっているので、Ubuntuから動作確認をします。Ubuntuを起動し、以下のコマンドを実行してください。

docker --version

Dockerのバージョンが以下のように表示されれば、動作しています。

Docker version 20.10.21, build baeda1f

まとめ

いかがでしたでしょうか。WSL2を使用することによって、WindowsでもLinuxを簡単に動かせるようになり、Dockerも手軽に試すことができるようになりました。また、今回はUbuntuをインストールして使用していますが、その他のディストリビューションを利用することもできますので、目的に合わせて色々試してみてください。

GitHubとは?Gitのコマンドと合わせて紹介!

BtoC, Git, PC, Windows, ツール, 紹介

前回、Gitの使い方について解説をしました。それによって、Gitがどういうものか少なからず理解していただけたと思います。しかし、前回のブログの中で説明したように、あの内容はローカルリポジトリという、個々人のマシン上にあるリポジトリの使い方のみの説明であり、Gitを使う上ではそれに加えてリモートリポジトリに関連したGitの使い方や、GitHubについても知る必要があります。
そこで今回は、Gitの使い方とGitHubについて解説していきたいと思います。


GitHubとは

ではまずGitHubについて紹介します。GitHubはGitについて調べると必ずと言っていいほど目にする言葉で、どういったものかある程度理解していたり、名前だけは知っているという方も多いと思います。
ではそのGitHubが何かというと、Gitの仕組みを利用して世界中の人々が自分の作品を保存、公開でき、リモートリポジトリとしての活用や、チーム開発のための機能を提供するWEBサービスの名称です。


リモートリポジトリとは

前回の内容と被ってしまいますが、リモートリポジトリについても簡単に復習します。
そもそも、リポジトリとはバージョン管理によって管理される管理されるファイルと履歴情報を補完する領域のことです。
まずは個々人のマシン上のリポジトリで作業を行い、その後ネットワーク先のサーバー上などにあるリポジトリに集約します。この集約先になっているのがリモートリポジトリになります。
ローカルリポジトリだけでは、一人の作業内容しか保存できないため、複数人で作業するとなると、必ずこのリモートリポジトリが関わってきます。


リモートリポジトリ関連のGitの使い方

前回の使い方では、ローカルリポジトリ内だけでも使えるコマンドについて説明しましたが、最初に説明したように、Gitにおいてはリモートリポジトリも重要な要素であるため、今回はリモートリポジトリに関連した内容を紹介します。

リモートリポジトリの作成

まずは、リモートリポジトリの作成を行います。コマンドを使って作成することも可能ではありますが、今回はGitHubを使った作成方法を紹介します。

まず、GitHubのダッシュボードのページ右上の「+」マークから「New repository」を選択するか、ページ左側「Top Repositories」の「New」を選択します。

右上から「New repository」
左側のTop Repositoriesから「New」

選択後は作成するリポジトリの内容について選択する画面が出てきます。
「Repository name」は作成するリポジトリの名前を入力します。(画像ではsample)
次にリポジトリの種類として「Public」か「Private」のどちらかを選択します。「Public」の場合は、他のユーザーがソースコードなどを閲覧することができ、「Private」の場合は非公開となります。
「Initialize this repository with a README」はリポジトリの説明や使い方を記述するREADMEファイルを事前に作成したい場合にチェックします。
その後の「.gitignore」や「license」は None を選択で問題ありません。

リポジトリ作成画面

すべて入力が終わったら「Create repository」を押して、リモートリポジトリの作成は完了です。

リモートリポジトリが作成できたら、Gitで使うコマンドについても紹介していきます。

git remote

git remote はローカルリポジトリで指定するリモートリポジトリの追加や削除を行うコマンドです。
以下の例のように、GitHubで作成したリモートリポジトリを指定することができます。

$ git remote add origin https://github.com/(自分のアカウント名)/(リポジトリ名).git

git push

git push はローカルリポジトリの内容をリモートリポジトリに反映させるコマンドです。
より正確に言うと、ローカルの現在のブランチをリモートの指定したブランチに反映させるコマンドであり、リモートのブランチを指定する必要があります。ただ、例のように 「-u」を付けるとローカルとリモートのブランチを紐づけることができるので、次回からは git push だけで実行可能にすることもできます。
また、コマンドを実行する際にはGitHubのアカウント名とパスワードが求められますので、間違えないように注意しましょう。

$ git push -u origin main
Username for 'https://github.com': アカウント名
Password for 'https://〇〇@github.com': パスワード

git clone

git clone はリモートリポジトリをローカルリポジトリにコピーするためのコマンドです。
前回紹介した git init がリポジトリを新しく作成するのに対して、こちらは既にあるリポジトリを使うという違いがあります。

$ git clone https://github.com/アカウント名/リモートリポジトリ名
Cloning into 'リポジトリ名'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

git fetch

git fetch はリモートリポジトリの情報をローカルリポジトリに持ってくるためのコマンドです。ただし、持ってくるのは更新情報だけであり、これだけではワーキングディレクトリのファイルに変化はありません。後述する git merge を使うことで、ワーキングディレクトリのファイルを更新することができます。

$ git fetch
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 587 bytes | 48.00 KiB/s, done.
From リポジトリ名
 * [new branch]      main       -> origin/main

git merge

git merge は特定のブランチやリビジョンを、現在のブランチに取り込むコマンドです。
これによって、fetchで持ってきた情報を使って、ローカルを更新することができます。
ちなみに、個人開発でもブランチで作業していた内容を統合する際にはmergeコマンドを使うことになります。

$ git merge origin/main
Updating d66e548..e117b19
# 更新内容
Fast-forward
 profile.md | 1 +
 1 file changed, 1 insertion(+)

git rebase

git rebase はmergeと同じように、他のブランチのリビジョンを取り込むコマンドです。
mergeと異なる点としては、取り込んだブランチがベースとなって、自ブランチの差分リビジョンを後ろに付け替えるという点になります。
では、どちらを使うのが良いのか?と疑問に思うかもしれませんが、merge をした場合に生成される「マージリビジョン」がGit管理を複雑にさせるということで、現在は rebase を使う方が一般的となっています。

# リベースで「main」ブランチを取り込む
$ git rebase main
First, rewinding head to replay your work on top of it...
Applying: Add リベース用ファイル

git pull

git pull は先述した git fetch と git merge をまとめて実行できるコマンドです。
わざわざコマンドを分ける必要がないときはこのコマンドを利用するのがおススメです。
また、「–rebase」オプションを使うことで、mergeの代わりにrebaseを実行することができます。

# 通常のコマンド
$ git pull

#rebaseオプション付きのコマンド
$ git pull --rebase

リモート追跡ブランチ

gitのコマンドを一通り紹介させていただきましたが、補足内容として「リモート追跡ブランチ」について解説したいと思います。補足とはいっても、Gitを正確に理解するための重要な内容になりますので、ぜひ覚えていってください。

リモート追跡ブランチとは?

一つのプロジェクトから分岐をさせて、本体に影響を与えず開発をすることができる機能がブランチになりますが、Gitではローカルとリモートリポジトリの同じ名前のブランチが直接連携しているわけではありません。
例えば、先ほど紹介したコマンドの中に “git fetch” がありました。その説明として、リモートリポジトリの情報をローカルリポジトリに持ってきていると言いましたが、その情報(コミット履歴)が更新されるのは、作業中のブランチではありません。
その情報はリモートリポジトリの各ブランチと直接連動しているブランチで更新され、そのブランチのことを「リモート追跡ブランチ」と呼びます。

リモート追跡ブランチはリポジトリをクローンした際にローカルリポジトリ上に作成され、リモートリポジトリの状態を表します。

ブランチ名

リモート追跡ブランチは「remotes/リモートレポジトリ名/リモートブランチ名」という名前がついているブランチになります。
例えば “origin” という名前のリモートリポジトリの “main” ブランチがあるとすると「remotes/origin/main」という名前になります。

ただし、リモート追跡ブランチを指定する時は「remotes/」を省略することが可能なので、「origin/main」だけでも問題ありません。

コマンドとの関係

リモート追跡ブランチがどういったものか理解ができたところで、実際にどのように影響しているのかを、コマンドと合わせて紹介します。

git fetch

リモート追跡ブランチとは何かを説明した際に触れましたが、”git fetch” はリモートリポジトリの内容をリモート追跡ブランチに反映するというコマンドになります。
なので、実際に作業をしているローカルのブランチが変化することはありません。

git merge

“git merge” はfetchで更新されたリモート追跡ブランチの内容をローカルのブランチに反映するコマンドになります。
コマンド紹介の例でも「$ git merge origin/main」のように、冒頭を省略した形でリモート追跡ブランチを指定しています。

git pull

“git pull” は fetch と merge をまとめて行っているものなので、処理の途中でリモート追跡ブランチを経由しているという形になります。

git push

“git push” では、ローカルブランチの内容をリモートブランチだけでなく、リモート追跡ブランチにも反映します。変更した内容が、「origin/main」にも影響を与えるので注意してください。


まとめ

いかがでしたでしょうか。チームで開発を行うとなった時には必ず、このリモートリポジトリやGitHubが関わってきますので、使い方はもちろん、Gitがどういう仕組みなのかを理解しておくと大変役に立つと思いますので、今回の内容はぜひ覚えていってください!

Gitとは?基本的な使い方の紹介

Git, PC, Windows, ツール, 紹介

今やエンジニアにとって必須になったGitですが、今まで使ってこなかった人や、エンジニアとしての勉強を始めたばかりの人にとっては、「どういったものか分からない」、「なんとなく理解はできたけど、使い方がよく分かっていない」ということもあると思います。
そこで今回は、Gitの使い方に関して詳しく解説していきたいと思います。


Gitとは

まずGitとは何かについて説明する前に、「バージョン管理」について説明します。
バージョン管理とはソースコードを始めとしたファイルの変更履歴(バージョン)を管理することです。変更された情報を管理することで、過去の変更箇所の確認、特定時点の内容に戻すといったことが可能になります。

そしてそのバージョン管理を行うためのシステムの一つがGitになります。
バージョン管理システムは他にもありますが、Gitは「分散型」のバージョン管理システムであり、個々人のマシン上にリポジトリを作成して開発を行うことができるという特徴があります。


リポジトリとは

バージョン管理によって管理されるファイルと履歴情報を補完する領域を、リポジトリと呼びます。Gitでは、まず個々人のマシン上にあるリポジトリ(ローカルリポジトリ)上で作業を行い、その後に作業内容をサーバー上などのリポジトリ(リモートリポジトリ)に集約して開発を進めていきます。
今回の内容ではローカルリポジトリ上での操作を紹介しています。
Gitを使った開発ではこの区別が重要になるので、こちらもぜひ覚えておいてください。


Gitの使い方

現在ではGitをGUIツールを使って管理することもできますが、GUIツールがない場合やサーバーに入って作業する際など、そういったツールが使えない場合もあります。
そこで、今回は標準で使えるgitコマンドの使い方を紹介します。また、いきなりたくさんのコマンドを説明しても分かりづらくなってしまうので、基本的なコマンドをピックアップして紹介します。

Gitの初期設定

まずはGitをインストールし、その後以下のコマンドでGitの初期設定を行います。

$ git config --global user.name ユーザー名
$ git config --global user.email メールアドレス

git init

プロジェクトをGitで管理する場合は、まずそのプロジェクトのディレクトリに移動してgit initコマンドで初期化をします。

$ mkdir git-tutorial && cd git-tutorial
$ git init

今回は例として git-tutorial というフォルダを作成し、そこでgit initを使いローカルリポジトリの作成をしています。

git add

初期化しただけの状態では、どのファイルもGitの管理下にないので、まずはバージョン管理をしたいファイルをgit addを使ってインデックスに登録します。

$ git add Hello.txt

git commit

git addで登録した後は、git commitコマンドで変更内容をコミットします。
コミットとは、ファイルの変更内容をローカルリポジトリに残すことで、変更内容などを記述した「コミットメッセージ」を添えて実行する必要があります。

git commit -m "コミットメッセージ"

git commitのコマンドでは、その後に-m “文字列”という様にコミットメッセージを記述する必要があります。

git status

git statusコマンドではディレクトリやインデックスの状態を確認することができます。
実際の動きを確認するために、先ほどのHello.txt”を編集した状態でgit statusコマンドを打ちます。

$ echo "good morning" > Hello.txt

すると以下のようなメッセージが表示されます。

$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   Hello.txt

Changes not staged for commitと表示されていますが、これはGitの管理下にあるファイルで、差分があるが、git addされておらず、次回コミットの対象になっていないものがあると表示されます。

これをgit addすると、以下のように表示が変わります。

$ git add Hello.txt

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   Hello.txt

Changes to be committed と表示され、変更後にaddされ、次回コミットの対象に含まれているファイルを表示してくれます。

このように、git status で現在の状態を確認することができます。

git diff

git status はディレクトリやインデックスの状態を確認することができますが、実際に変更などがあった場合に、どこが変更されたのかという差分を確認するには git diffコマンドを使用します。
先ほどの状態でgit diffを使うと以下のように表示されます

$ git diff --cached Hello.txt
diff --git a/Hello.txt b/Hello.txt
index b016251..b1eb873 100644
--- a/Hello.txt
+++ b/Hello.txt
@@ -1 +1 @@
-こんにちは
+good morning

Hello.txtの中の「こんにちは」という文字列が削除 (-) され、「good morning」という文字列が追加 (+) されたということが分かると思います。
ちなみに例のような git add 済みのファイルは git diff の後に –cached オプションが必要になります。

git log

コミットを行うとそのたびに、バージョンが更新されます。その時点までの記録したコミットログ(バージョン履歴)は git log で以下のように確認することができます。

$ git log
commit 9c4ae3ff92faa7e8818a04535d6ca97189495148 (HEAD -> main)
Author: ユーザー名 <メールアドレス>
Date:   Wed Dec 14 13:14:09 2022 +0900

    commitします

commit cf9f0b9fa21dd4d6c047d85a8c0b80624a6cd231
Author: ユーザー名 <メールアドレス>
Date:   Wed Dec 14 11:54:33 2022 +0900

    文字列の追加

commit f51f8800cd3c7d81de7a8e21858ba9e1670bc5d1
Author: ユーザー名 <メールアドレス>
Date:   Wed Dec 14 11:41:53 2022 +0900

    first commit

コミットを行った日時と、コミットメッセージが表示されます。
commit の後に続いて表示されているのが、コミットハッシュと呼ばれるもので、コミットを一意に識別するためのものになります。

checkout

履歴をさかのぼって、特定のバージョン時点の状態にするには、チェックアウト機能を使います。
先ほどの git log で出てきた表示に(HEAD -> main)と記載されているものがありましたが、HEADというのが、現在参照しているバージョンで、mainとあるのがブランチ名になります。

ここから特定のバージョンに移動する際には git checkout コマンドを使います。checkeout の後には該当のコミットハッシュを入力します。

$ git checkout f51f8800cd3c7d81de7a8e21858ba9e1670bc5d1

$ git log
commit f51f8800cd3c7d81de7a8e21858ba9e1670bc5d1 (HEAD)
Author: ユーザー名 <メールアドレス>
Date:   Wed Dec 14 11:41:53 2022 +0900

    first commit

checkout をした後にlogを確認すると該当のバージョンにHEADの表示が移っているのが分かると思います。

git reset

もし、Git のコマンドを間違えて実行してしまったという場合には、git resetコマンドでその作業をやり直すことができます。
例えば、間違えてgit addしてしまったファイルを取り消す際には “git reset HEAD ファイル名” または “git reset” で一括取り消しをすることができます。
間違えて行ったコミットを取り消す場合は “git reset HEAD” を使って行うことができます。

# addしたファイルの取り消し
$ git reset HEAD ファイル名

# addしたファイルの一括取り消し
$ git reset

# commitの取り消し
$ git reset HEAD

まとめ

いかがでしたでしょうか。Gitの使い方を覚えておくと、ソースコードの管理ができたり、チームでの開発も効率的に行うことができるようになります。
Gitの内容に限りませんが、知識として学ぶだけでなく実際に使ってみることで、より理解が深まると思いますので、今回の内容を参考にして皆さんも使ってみてください。

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

【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 を指定し、第二引数でリストの情報を渡しています。

これで実装は完了です。

プログラムの実行

記述したプログラムを実行すると、初めに例として挙げた完成予想図と同じものが表示されます。

設定したレイアウトに合わせて表示

まとめ

いかがでしたでしょうか。今回のようにデータを一覧で表示したいという機会は多いと思いますので、その時々に合わせたリストをぜひ作成してみてください。
また、解説だけだと分かりにくい部分があると思いますので、解説したサンプルも実際に作って理解を深めていってください。

古い投稿ページへ