You are on page 1of 273

電子書籍閲覧に関するご注意

本書では、プログラムリストに専用の等幅フォントを使用していま
す。ビューアによって以下の作業が必要になります。
・Kindle Paperwhiteの場合:フォント設定画面で「出版者のフォン
ト」を選択
・kobo Androidアプリの場合:フォント画面で「オリジナル」を選択
目次
電子書籍閲覧に関するご注意
はじめに
この本を通して学べること
対象読者
サンプルプログラムについて
動作環境
免責事項
表記関係について
底本について
第1章 FlutterとFirebaseを知ろう
1.1 Flutterの概要
1.2 Flutterの特徴
1.3 Firebase
第2章 開発環境を構築しよう
2.1 DartSDKとFlutterSDKの入手方法について
2.2 環境開発構築手順
2.3 Android Studioのインストール
2.4 Flutterプラグインの追加
2.5 flutter doctorの実行
2.6 【参考】fvmを利用した環境開発手順
第3章 Flutterをサクッと動かしてみよう
3.1 Flutterの始め方
3.2 Android Studio 機能解説
3.3 Flutterを実行しよう
第4章 Dartの基礎知識を学ぼう
4.1 Dartについて
4.2 変数
4.3 メソッド
4.4 クラス
4.5 演算子
4.6 非同期処理
第5章 Flutterの基礎知識を学ぼう
5.1 Flutterアプリの画面構造について
5.2 StatelessとStateful
5.3 パッケージ
第6章 アプリの仕様を決めよう
6.1 アプリの仕様
6.2 プロジェクトの作成
第7章 Firebaseの設定をしよう
7.1 アカウント取得
7.2 FlutterアプリとFirebaseの紐付け
7.3 データベースの作成
第8章 一覧画面の実装をしよう
8.1 pubspec.yamlの変更
8.2 リスト作成
8.3 リスト作成の解説
8.4 新規ボタン追加
8.5 新規ボタン追加の解説
8.6 編集ボタン追加
8.7 編集ボタン追加の解説
第9章 入力画面の実装をしよう
9.1 Promiseモデル作成
9.2 Promiseモデル作成の解説
9.3 入力画面作成
9.4 入力画面作成の解説
9.5 一覧画面から入力画面への画面遷移
9.6 一覧画面から入力画面への画面遷移の解説
9.7 ラジオボタン有効化
9.8 ラジオボタン有効化の解説
9.9 日付選択画面作成
9.10 日付選択画面作成の解説
第10章 登録機能の実装をしよう
10.1 登録機能作成
10.2 登録機能作成の解説
10.3 入力チェック機能
10.4 入力チェック機能の解説
第11章 編集機能の実装をしよう
11.1 編集機能作成
11.2 編集機能作成の解説
第12章 削除機能の実装をしよう
12.1 削除機能作成
12.2 削除機能作成の解説
第13章 ログイン機能の実装をしよう
13.1 認証機能の作成
13.2 メールアドレスによる認証機能の作成
13.3 Cloud Firestoreとログイン機能を組み合わせよう
第14章 共有機能を実装しよう
14.1 プラグインを追加する
14.2 共有機能作成
14.3 共有機能作成の解説
第15章 多言語化対応しよう
15.1 多言語化プラグインのインストール
15.2 生成ファイルの確認
15.3 多言語化対応の設定
15.4 言語ファイルの準備
15.5 言語ファイルの反映
15.6 アプリ名の多言語化対応
第16章 アプリのアイコンを設定しよう
16.1 プラグインを追加する
16.2 アプリのアイコンを配置する
16.3 アイコンの生成
第17章 スプラッシュ画面を実装しよう
17.1 プラグインを追加する
17.2 画像の保存
17.3 画像表示設定追加
17.4 画像表示設定追加の解説
第18章 アプリをリリースしよう(Android版)
18.1 jksファイルの作成
18.2 キーストアの登録
18.3 サインイン情報の追記
18.4 リリースAPKの作成
18.5 APKの動作確認
18.6 Google Play Storeへの登録
第19章 【参考】アプリをリリースしよう(iOS版)
19.1 Apple Developer Programへの登録
19.2 iTunes Connectにアプリを登録
19.3 Xcodeプロジェクトの設定を確認
19.4 ビルドアーカイブの作成
おわりに
謝辞
感想
はじめに
本書を手に取っていただき、ありがとうございます。
本書では、Googleによるオープンソースのモバイルアプリケーショ
ンフレームワークであるFlutterと、同じく、Googleが提供する
Firebaseを使用した、Android・iOS上で動作するアプリを作成しま
す。実際に「貸し借りをメモするアプリ」を作成しながら、Flutterに
ついて学びます。
この本を通して学べること
「貸し借りを記録できるメモ帳」のアプリ制作を通し、次のことが
学べます。
・Flutterの開発環境の構築
・Dartの文法
・Flutterの基礎的なWidgetの使い方
・Firebaseを利用したアプリの作成
・アプリのリリース方法
対象読者
Flutterに興味のある方が対象です。Flutter未経験の方や、Firebase
を利用したモバイルアプリを作成してみたい方にオススメです。
サンプルプログラムについて
本書で記載されているコードは、すべて次のGitHubリポジトリーか
らダウンロードできます。タグで章ごとに項目を分けてありますの
で、ぜひ参考にしてください。
https://github.com/chasibu/kasikari_memo_v2
動作環境
本書で想定している動作環境のOSは、次のとおりです。
・Windows...Windows10
・MacOS...Catalina
免責事項
本書に記載された内容は、情報の提供のみを目的としています。し
たがって、本書を用いた開発、製作、運用は、必ずご自身の責任と判
断によって行ってください。これらの情報による開発、製作、運用の
結果について、著者はいかなる責任も負いません。
表記関係について
本書に記載されている会社名、製品名などは、一般に各社の登録商
標または商標、商品名です。会社名、製品名については、本文中では
©、®、™マークなどは表示していません。
底本について
本書籍は、技術系同人誌即売会「技術書典5」で頒布されたものを
底本としています。
第1章 FlutterとFirebaseを知ろう
この章では、FlutterとFirebaseの概要について説明します。
1.1 Flutterの概要
Flutterは、Googleが開発したオープンソースのアプリケーションフ
レームワークです。
アプリケーション開発は「AndroidであればJavaやKotlin」「iOSで
あればObjective-CやSwift」「WebアプリであればPHP,Ruby」などの
ように、動作させるプラットフォーム毎に別の言語を学習する必要が
あり、開発コストが高くなります。
しかし、近年において、クロスプラットフォーム開発フレームワー
クが登場しました。これは、ひとつの言語のみで、複数のプラットフ
ォームに対応したアプリケーションを開発できるツールです。クロス
プラットフォーム開発フレームワークのひとつがFlutterです。他に
は、Facebookが開発したReactNativeやMicrosoftが開発したXamarin
などがあります。
1.2 Flutterの特徴
1.2.1 簡単なUI設計
マテリアルデザインに沿ったUIを簡単に設計できます。「マテリア
ル=物質的な」という意味があるように、マテリアルデザインとは
「物理的な法則に沿ったデザイン」を指します。現実世界のルールで
ある光や影、奥行き、重なりを取り入れたデザインとなっており、
Googleが発表しているWebサイトやアプリに多く用いられています。
マテリアルデザインに沿った、Google製のような美しいアプリケー
ションを簡単に作成できます。
1.2.2 開発言語 Dart
Googleによって開発されたプログラミング言語です。これは、
JavaScriptの替わりとなる言語として作られました。JavaScriptや
Javaと記法が似ており、どちらかの言語を使ったことがある方には、
始めやすい言語です。
1.2.3 ホットリロード対応
Flutterにはホットリロードという、アプリの実行中にコードの変更
をすばやく反映させる機能があります。
アプリの実行中にコードを変更した場合、通常は再ビルドする必要
があり、反映するのに数十秒から数分かかります。しかしホットリロ
ードを使うと、コードを変更してもアプリを停止することなく反映で
きるため、格段にアプリの開発効率が向上します。
次の画像は、「Hello World!」から「Flutter」と文字をプログラム
上で変更する例です。文字を変更してプログラムを保存すると、1秒後
には、画像にあるようにアプリの表示が更新されました。通常であれ
ば数十秒から数分かかるプログラムの反映が数秒でできるので、効率
よくプログラムを作成できます。
図1.1: ホットリロード(文字変更)
FuchsiaとFlutter
Googleが開発しているOSにFuchsia(フクシア)というものがあります。2016年8月に
GitHub上で公開され、その存在が明らかになりました。特徴としては、スマートフォン、
タブレット、PCなど、幅広いデバイス上で起動させることができ、Flutterで作成したアプ
リを動作させることができます。2021年5月に「Nest Hub」向けに配信され、話題になり
ました。今後の動向に注目が集まりそうですね。
図1.2: fuchsiaロゴ

1.2.4 ReactNativeやXamarinとの比較
他のクロスプラットフォーム開発環境と、Flutterを比較してみま
す。
表1.1: 他のマルチクロスプラットフォームとの比較
対応プラットフォーム Flutter/Dart ReactNative/JavaScript Xamarin/.NET
Android ○ ○ ○
iOS ○ ○ ○
デスクトップ(Win) ○ - ○
デスクトップ(Mac) ○ - ○
Web(フロントエンド) ○ - ○
Web(バックエンド) - - -

デスクトップアプリも作成できるため、Xamarinと並んで、クロス
プラットフォーム開発環境の対応数としては最多です。
1.3 Firebase
Firebaseは、Googleが運営するBaaS(Backend as a service)で
す。BaaSとは、サーバーサイドのプログラミングが不要で、データベ
ースや認証機能、プッシュ通知などを行ってくれるサービスです。本
書では「Firestore Database」という、クエリと自動スケーリング機
能を兼ね備えたリアルタイムデータベースを使用します。
第2章 開発環境を構築しよう
Flutterで開発するための環境を構築します。インストールするもの
は次のとおりです。
・DartSDK(version:2.13.1)
・FlutterSDK(version:2.2.2)
・Android Studio(version:4.2.1)
・Xcode(iOSの開発を行う場合)
2.1 DartSDKとFlutterSDKの入手方法について
DartSDKの入手方法は3種類あります。
1.Homebrewなどのパッケージ管理ツールを使用してインストー
ルする。
2.Zip等で配布されているSDKをダウンロードする。
3.Flutterに付属されているDartSDKを利用する(Flutter 1.21以降
のバージョンのみ可能)。
また、FlutterSDKの入手方法は2種類あります。
1.Zip等で配布されているSDKをダウンロードする。
2.fvmなどのFlutterバージョン管理ツールを利用し、ダウンロード
する。
プロジェクト毎にバージョンを変更できるfvmの利用が便利ですが、
少し手順が複雑です。本書では、Zipで配布されているFlutterSDK利用
した手順を紹介します。
fvmを使ってみたい方向けに、この章の最後にfvmを利用した導入手順
を記載しますので、興味のある方はそちらも参考にしてください。
2.2 環境開発構築手順
WindowsとMacのインストール手順が異なるので、順に説明しま
す。
2.2.1 Windows版
公式のFlutterのサイト1にアクセスします。Flutter SDKのダウンロー
ドリンクをクリックし、ダウンロードします。
※ 執 筆 時 点 で は 、 フ ォ ル ダ ー 名 が 「 flutter_windows_v2.2.2-
stable.zip」となっております。
図2.1: ダウンロードリンク

ダウンロードしたZipファイルを展開し、中身のFlutterフォルダーを
「C:\Users\ユーザーフォルダー」の下などに配置します。
図2.2: フォルダー配置
「コントロールパネル > ユーザーアカウント > ユーザーアカウント >
環境変数の変更」をすると、次のような画面が表示されます。
「Path」と書かれた行を選択した上で、編集というボタンを押しま
す。
図2.3: パス設定
先ほど展開したZipフォルダーのパスを設定します。「編集」をクリ
ックします。本書と同じ手順であれば、「C:\Users\ユーザーフォルダ
ー\flutter\bin」となります。設定が完了したらOKをクリックし、画面
を閉じます。
図2.4: パス設定
コマンドプロンプトを実行し、ダウンロードしたFlutterのフォルダ
ーに移動します。本書と同じ手順であれば、次のコマンドで移動できま
す。
> cd C:¥Users¥ユーザーフォルダー¥flutter¥
次のコマンドを実行し、Flutterのバージョンを切り替えます。
> git checkout refs/tags/2.2.2
Windows版のFlutter SDKの準備は完了です。
2.2.2 Mac版
公式のFlutterのサイト2にアクセスします。Flutter SDKのダウンロー
ドリンクをクリックし、ダウンロードします。
※執筆時点ではフォルダー名が「flutter_macos_v2.2.2-stable.zip」と
なっております。
図2.5: ダウンロードリンク

ホーム直下に「development」フォルダーを作成し、ダウンロード
したzipファイルを展開しましょう。
$ mkdir ~/development
$ cd ~/development
$ unzip ~/Downloads/flutter_macos_v2.2.2-stable.zip
「~.bashrc」ファイルにパスを書き込みます。※zshを使用している
方は「~.zshrc」に読み替えてください。
$ vim ~/.bashrc
#ファイルの一番下に書き込む。USERと書かれた部分には実行ユーザ
ー名が入ります。
export PATH="$PATH:/Users/${USER}/development/flutter/bin"
pathを定義したファイルを再読み込みします。※zshを使用している
方は「~/.zshrc」に読み替えてください。
$ source ~/.bashrc
ターミナル上でダウンロードした、Flutterのフォルダーに移動しま
す。本書と同じ手順であれば、次のコマンドで移動できます。
$ cd ~/development/flutter
Flutterのバージョンを切り替えます。
$ git checkout refs/tags/2.2.2
Mac版のFlutter SDKの準備は完了です。
2.3 Android Studioのインストール
公式サイト3よりインストーラーをダウンロードします。インストー
ラー指示にそって、インストールを完了します。
2.4 Flutterプラグインの追加
Android StudioでFlutterを開発するために、Flutterのプラグインを
インストールします。起動画面で「Configure>Plugins」を選択しま
す。
図2.6: プラグインのインストール

もしくは、「File > Preferences」からでも、同様の画面に進むこと


ができます。
図2.7: プラグインのインストール
画面上部のタブ「Marketplace」を選択し、画面上部の検索バーに
「Dart」を入力します。「Dart」を選択し、インストールします。
図2.8: Dartプラグインのインストール

検索欄に「flutter」と入力します。「Flutter」を選択し、インストー
ルします。
図2.9: Flutterプラグインのインストール

インストール後、画面からAndroid Studioの再起動をします。
図2.10: Android Studioの再起動
2.5 flutter doctorの実行
次のコマンドを実行し、Flutterを動作させるのに必要な設定を確認
します。
$ fvm flutter doctor -v
表示された内容に応じて、適宜、必要なソフトウェアをインストー
ルしてください。
これで環境構築は完了です!次の章では、アプリの開発を始める前に
サンプルプログラムを動かして、デバイスへのデプロイを体験しまし
ょう。
2.6 【参考】fvmを利用した環境開発手順
Flutterのバージョン管理ツールである、fvmを利用した環境構築手順
です。興味のある方はこちらを参考に、Flutterを導入してみましょ
う。
2.6.1 fvmについて
fvmはプロジェクト毎に、Flutterのバージョンを管理するツールで
す。fvmという名称で公開されているパッケージは、leoafarias/fvm4
とbefovy/fvm5の2種類が存在しています。本書ではleoafarias/fvmを
利用します。
2.6.2 DartSDK,fvmのインストール
DartSDKとfvmのインストールを行います。WindowsとMacのインス
トール手順が異なるので、順に説明します。
Windows版
DartSDKをダウンロードするために、chocolatey6をインストールし
ます。
管理者権限でWindows PowerShellを実行し、次のコマンドを実行し
ます。
> Set-ExecutionPolicy Bypass -Scope Process -Force; \
[System.Net.ServicePointManager]::SecurityProtocol \
= [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;\
iex ((New-Object System.Net.WebClient).DownloadString(\
'https://chocolatey.org/install.ps1'))
管理者権限でコマンドプロンプトを実行し、次のコマンドを実行
し、DartSDKをインストールします。
> choco install dart-sdk
コマンドプロンプトを一度閉じ、再び、管理者権限でコマンドプロ
ンプトを実行します。次のコマンドを実行し、fvmをインストールしま
す。
> dart pub global activate fvm
インストール完了時にpathを通すように表示されるので、fvmの
pathを追加します。「コントロールパネル > ユーザーアカウント > ユ
ーザーアカウント > 環境変数の変更」を選択すると、次のような画面
が表示されます。「Path」と書かれた行を選択した上で、編集という
ボタンを押します。
図2.11: パス設定
「%USERPROFILE%¥AppData¥Local¥Pub¥Cache¥bin」と入力し、
「OK」を選択して、保存します。
図2.12: パス設定
コマンドプロンプトを一度閉じ、再び、管理者権限でコマンドプロ
ンプトを実行します。次のコマンドを実行し、flutterの2.2.2をインス
トールします。
> fvm install 2.2.2
指定したバージョンがインストールされていることを確認します。
$ fvm list
Cache Directory: C:¥Users¥<ユーザー名>¥fvm¥versions
2.2.2
Windows版のFlutterSDKの準備は完了です。
Mac版
Homebrewをインストールします。
$ /bin/bash -c \
"$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/HEAD/insta
ll.sh)"
DartSDKをインストールします。
$ brew tap dart-lang/dart
$ brew install dart
fvmをインストールします。
$ pub global activate fvm
「~/.bashrc」にfvmのpathを追加します。
※zshを使用している人は「~/.zshrc」に読み替えてください。
export PATH="$PATH":"$HOME/.pub-cache/bin"
pathを定義したファイルを再読み込みします。
※zshを使用している人は「~/.zshrc」に読み替えてください。
$ source ~/.bashrc
FlutterSDKをインストールします。
$ fvm install 2.2.2
指定したバージョンがインストールされていることを確認します。
$ fvm list
Versions path: /Users/<user_name>/fvm/versions
2.2.2
Mac版のFlutterSDKの準備は完了です。

1. https://flutter.dev/docs/get-started/install/windows
2. https://flutter.dev/docs/get-started/install/macos
3. https://developer.android.com/studio/
4. https://github.com/leoafarias/fvm
5. https://github.com/befovy/fvm
6. https://chocolatey.org
第3章 Flutterをサクッと動かしてみよう
この章ではサンプルコードを動かして、Flutterでアプリを開発する
流れを学びます。
3.1 Flutterの始め方
1. Android Studioを開き、「Start a new Flutter project」をクリッ
クします。
図3.1: Flutterの始め方

すでにプロジェクトを開いている場合は、「File > New > New


Flutter Project」をクリックします。
図3.2: Flutterの始め方
2. 作成するプロジェクトの形式とFlutterSDKのパスの設定画面が表
示されます。画面左にある項目は「Flutter App」を選択し、画面右に
ある「...」を選択します。
図3.3: プロジェクト形式とFlutterSDKパスの選択

3. 使用するFlutterSDKを指定します。先ほどインストールしたSDK
を利用します。
図3.4: FlutterSDKの選択
5. 設定できたら「Next」を選択します。
図3.5: FlutterSDKの決定

6.「Project Name」欄に「flutter_firebase」と入力し、「Finish」
をクリックします。
図3.6: Project Name
7. プログラムを書くための環境が起動しました。これで準備は完了
です。
図3.7: プログラム画面全体

次の項目で、Android Studioを使ったことのない方向けに、機能を
簡単に説明します。
3.2 Android Studio 機能解説
簡単にですが、Android Studioの機能を説明します。
図3.8: Android Studio

3.2.1 ファイル選択
FlutterではiOSとAndroidを開発するため、OSごとにフォルダーがあ
ります。libフォルダー内にFlutterの開発に使う、「main.dart」ファイ
ルがあります。変更する際にはパスを指定されるので、ここを変更する
のだと覚えておきましょう。
3.2.2 エディター
Flutterのコードや設定ファイルなどを編集します。
3.2.3 Flutter構成
Flutterの構成を確認できます。
Flutterの特徴として、コードで描画するViewを作成します。このタ
ブをみると、どのWidgetで構成されてるのか、一目でわかります。
3.2.4 プログラム実行
Flutterのコードを実行するときに使用します。デバッグモードにし
たり、デプロイするデバイスを選択します。
3.2.5 エミュレーター
Androidのエミュレーターを作成できます。「Androidエミュレータ
ーの設定」の項目で紹介します。
3.3 Flutterを実行しよう
実機のAndroidでFlutterアプリを実行させる手順を紹介します。実機
がない場合は、「Androidエミュレーターの設定」を確認して、エミュ
レーターで行いましょう。
3.3.1 実機による実行
1. 実機のAndroidを開発者モードに切り替えます。切り替え方法はデ
バイスごとに異なるので、検索して切り替えてください。
2. Androidの設定メニューから「開発者向けオプション > USBデバッ
グ」を有効にします。
図3.9: USBデバッグ有効にする
3. 「2.」までの設定が完了した状態でPCとUSBでつなぐと、このよ
うなダイアログが表示されます。「このコンピューターは常に許可す
る」にチェックを入れ、「OK」を押します。
図3.10: USBデバッグの許可

4. 問題なければ、Android Studioを確認すると、デバイス名が表示
されます。緑色の再生ボタンをクリックするとビルドが開始され、し
ばらくするとプログラムが実行されます。
図3.11: デプロイ
5. 実行するとこのような画面が表示されます。これで動作確認完了
です。
図3.12: 実行例

3.3.2 スマートフォンエミュレーターによる実行
PC上に仮想のスマートフォン端末を用意し、Flutterアプリを実行さ
せる手順です。この項目は、必須項目ではないので、実機がないときに
行ってください。
Androidエミュレーターの設定
Android Studioを開き、Androidエミュレーターの設定をします。
1. メニュータブから「File」→「Project Structure...」を選択しま
す。
2.「Project Settings」→「Project」を選択し、「Project SDK:」の
選択リストを「Android API 30 Platform」にしてOKボタンを選択しま
す。
図3.13: Project SDKの選択

3. 画面上部にある、「AVD Manager」のアイコンをクリックし、表
示された画面にある「+Create Virtual Device...」のボタンをクリック
します。
図3.14: Androidエミュレーターの作成
4. デバイス選択画面が表示されるので「Pixel 4」を選択し、右下の
「Next」ボタンをクリックします。
図3.15: 「Pixel 4」を選択

5. OSのバージョン選択画面が表示されますが、「Android 11.0」を
選択し、右下の「Next」ボタンをクリックします。
図3.16: 「Android 11.0」を選択

6. 設定の確認画面が表示されるので、確認して「Finish」をクリック
します。
7. デバイスを選択して起動させます。
図3.17: Pixel 4を選択

8. Pixel 4が画面に表示されることを確認します。この状態で緑色の
再生ボタンをクリックするとビルドが開始され、しばらくするとプロ
グラムが実行されます。
9. Pixel 4画面の横にある×ボタンを選択すると、エミュレーターを
終了できます。
iPhoneエミュレーターの設定(macOSのみ)
1. Android Studioを開き、iPhoneシミュレータの設定をします。画
面上部にある「<No devices>」を選択し、「Open iOS Simulator」を
クリックします。
図3.18: 「Open iOS Simulator」

2. しばらくすると、iPhoneが画面に表示されます。この状態で緑色
の再生ボタンをクリックするとビルドが開始され、しばらくするとプ
ログラムが実行されます。
3. 「Simulator > Quit Simulator」を選択するとエミュレーターが終
了します。
図3.19: iOSシミュレータの終了方法
第4章 Dartの基礎知識を学ぼう
この章ではFlutterを書き始める前に、Dartの基礎的な文法について
学んでいきます。より詳しく文法について知りたい方は、「A tour of
the Dart language1」を参照ください。
4.1 Dartについて
基本的な文法はJavaやJavaScriptに似ており、これらの言語を触っ
たことある人であれば、比較的取っつきやすい言語です。型推論をも
つ言語ですが、明示的に型の宣言も可能です。また、DartPad2を利用
するとオンライン上で簡単にDartを実行できるので、こちらで実行し
ながら読み進めると、理解が深まりやすいです。
4.2 変数
型の種類やアクセス修飾子について説明します。
4.2.1 型
他の静的言語と同様に、次のように変数の宣言ができます。
リスト4.1: 変数の宣言
1: void main() {
2: String word = 'hello world';
3: print(word);
4: }

Dartの型にはdynamicとvarがあり、どちらも、どのような値でも代
入できます。varは一度値を代入し、型が決まると以降、別の型の代入
は行えません。dynamicは前の型にかかわらず、値が再代入できま
す。
リスト4.2: varを使用
1: void main() {
2: var word = 'hello';
3: print(word);
4: word = 123; // ここでエラーになる。
5: print(word);
6: }

リスト4.3: dynamicを使用
1: void main() {
2: dynamic word = 'hello';
3: print(word);
4: word = 123;
5: print(word);
6: }

これらの型は便利である一方、バグ発生の原因になりやすいので
す。可能な限り使用しないようにしましょう。
4.2.2 null safety
Dartでは、変数宣言時にnullを許容するかどうかを選択できます。
型の後ろに?を追記すると、nullを許容できます。なお、この機能は
Dart2.12から搭載されていた機能です。
リスト4.4: null safety
1: void main() {
2: String? null_ok = null;
3: String not_null = null: // ここでエラーになる。
4: }

4.2.3 アクセス修飾子
Dartにはpublic,protected,privateとったアクセス修飾子がありませ
ん。変数名にアンダースコア(_)を付与することで、プライベート変
数として扱うことができます。
リスト4.5: アクセス修飾子
1: void main() {
2: String _private_variable = hoge;
3: String not_private_variable = fuga:
4: }
4.3 メソッド
メソッドの定義方法や省略記法について説明します。
4.3.1 メソッドの定義
他の言語と同様に、メソッドの定義が行えます。
リスト4.6: メソッドの定義
1: void main() {
2: show_name('taro','yamada');
3: }
4:
5: void show_name(String first_name, String last_name){
6: print(last_name + first_name);
7: }

デフォルト引数を指定する場合は、次のように定義します。
リスト4.7: デフォルト引数の定義
1: void main() {
2: show_name();
3: }
4:
5: void show_name(String first_name = 'jiro', String = 'tanaka'){
6: print(last_name + first_name);
7: }

4.3.2 名前付き引数
次のように、名前付き引数を定義できます。
リスト4.8: 名前付き引数
1: void main() {
2: show_name(first_name: 'taro', last_name: 'yamada');
3: }
4:
5: void show_name({String first_name = 'jiro', String last_name = 'tanaka'}){
6: print(last_name + first_name);
7: }

4.3.3 省略記法
メソッドの戻り値がreturnのみの場合は、次の省略記法を使用でき
ます。
リスト4.9: 省略記法
1: void main() {
2: String full_name = concat_name('taro','yamada');
3: print(full_name);
4: }
5:
6: String concat_name(String first_name, String last_name)
7: => last_name + first_name;
4.4 クラス
クラスの定義方法について説明します。
4.4.1 クラスの定義
Javaと似たような記法で、クラスの定義が行えます。
リスト4.10: クラスの定義
1: class Person {
2: String first_name;
3: String last_name;
4: String getFullName() => first_name + last_name;
5: }
6: }

4.4.2 コンストラクターの定義
クラス名と同じ名称をつけることで定義できます。
リスト4.11: コンストラクターの定義
1: void main() {
2: Person person = new Person(first_name: 'taro', last_name: 'yamada');
3: print(person.getFullName());
4: }
5:
6: class Person {
7: String _first_name = '';
8: String _last_name = '';
9:
10: Person({String first_name = 'jiro', String last_name = 'tanaka'}){
11: this._first_name = first_name;
12: this._last_name = last_name;
13: }
14: String getFullName() => _first_name + _last_name;
15: }
4.5 演算子
null_safety対応でよく使用する演算子を中心に説明します。
4.5.1 条件付きメンバーアクセス(?.)
レシーバーがnullであった場合に、メソッドを呼び出さず、nullを返
すようにします。Rubyなどではnilガードと呼ばれます。
リスト4.12: 条件付きメンバーアクセス
1: void main() {
2: String? name = null;
3: int? length = getLength(name);
4: print(length);
5: }
6:
7: int? getLength(String? s) {
8: return s?.length;
9: }

4.5.2 null許容型のcast(!)
nullを許容しているメンバーを呼び出すと、エラーが発生します。
たとえば、次のようなコードです。
リスト4.13: null許容型のcast
1: class Coffee {
2: String? _temperature;
3:
4: void heat() { _temperature = 'hot'; }
5: void chill() { _temperature = 'iced'; }
6:
7: void checkTemp() {
8: if (_temperature != null) {
9: print('Ready to serve ' + _temperature + '!');
10: }
11: }
12:
13: String serve() => _temperature! + ' coffee';
14: }

このコードは、9行目で_temperatureがnullの可能性があるため、
エラーが発生します。8行目でnullでないことのチェック処理がありま
すが、変数を使用するまでの間に、nullでないことを静的解析が担保
できないためです。そこで、次のコードのように変更し、nullである
ことを許容します。
リスト4.14: null許容型のcast2
1: class Coffee {
2: String? _temperature;
3:
4: void heat() { _temperature = 'hot'; }
5: void chill() { _temperature = 'iced'; }
6:
7: void checkTemp() {
8: if (_temperature != null) {
9: print('Ready to serve ' + _temperature! + '!');
10: }
11: }
12:
13: String serve() => _temperature! + ' coffee';
14: }
4.6 非同期処理
他のプログラミング言語と同様に、Dartでも非同期処理がサポート
されています。よく使用するFutureオブジェクトとStreamオブジェク
トについて解説します。
4.6.1 Future
Dartが標準に用意している非同期処理を扱うためのパッケージで
す。then()を用いる方法と、async/awaitを用いる方法があります。
thenを用いる方法
Futureオブジェクトを返すメソッドに対して、チェインさせて使用
します。特定の処理完了後に実行したい処理をthen()の中に記載し
て、使用します。
リスト4.15: thenを使用
1: void main() {
2: print('getSomethingを実行します。');
3: getSomething().
4: then((value) => print('getSomethingの実行完了しました。$valueを取得しました。'));
5: print('データの取得中です。');
6: }
7:
8: Future<String> getSomething() {
9: final String sampleData = "サンプルデータ";
10: // 非同期的な処理を実行。ここでは、模擬的にFuture.delayedを実行。
11: print('データの取得をします。');
12: Future.delayed(Duration(seconds: 5));
13: return Future<String>.value(sampleData);
14: }

実行結果は次のとおりです。
getSomethingを実行します。
データの取得をします。
データの取得中です。
getSomethingの実行完了しました。サンプルデータを取得しまし
た。
async/awaitを用いる方法
非同期で実行したい処理を、awaitをつけて呼び出します。また、
awaitを使用する場合には、呼び出し元のメソッドにasyncを使用して
あげる必要があります。先ほどの非同期処理を、async/awaitを用いて
同等な処理になるように変更します。
リスト4.16: async/awaitを使用
1: void main() {
2: print('getSomethingを実行します。');
3: completeGetSomething();
4: print('データの取得中です。');
5: }
6:
7: void completeGetSomething() async {
8: String value = await getSomething();
9: print('getSomethingの実行完了しました。$valueを取得しました。');
10: }
11:
12: Future<String> getSomething() {
13: final String sampleData = "サンプルデータ";
14: // 非同期的な処理を実行。ここでは、模擬的にFuture.delayedを実行。
15: print('データの取得をします。');
16: Future.delayed(Duration(seconds: 5));
17: return Future<String>.value(sampleData);
18: }

実行結果は次のとおりです。
getSomethingを実行します。
データの取得をします。
データの取得中です。
getSomethingの実行完了しました。サンプルデータを取得しまし
た。
async/awaitを使用すると、複雑な処理を記載するときにネストが
少なくなるので、thenよりも使用が推奨されています。
4.6.2 Stream
Streamは一連の非同期イベントを取り扱います。非同期で実行され
るStream型を返却するメソッドを別のメソッドが検知し、処理を実行
できます。次のコードでは、randomStringStreamのyieldが実行され
るのをstringShowStreamが検知し、値を画面に表示させます。
リスト4.17: streamを使用
1: import 'dart:math';
2:
3: Future<void> stringShowStream(Stream<int> stream) async {
4: await for (var value in stream) {
5: print(value);
6: }
7: }
8:
9: Stream<int> randomStringStream(int to) async* {
10: for (int i = 1; i <= to; i++) {
11: yield Random.secure().nextInt(10);
12: }
13: }
14:
15: Future<void> main() async {
16: Stream<int> stream = randomStringStream(10);
17: await stringShowStream(stream);
18: }
19:
1. https://dart.dev/guides/language/language-tour
2. https://dartpad.dartlang.org/
第5章 Flutterの基礎知識を学ぼう
この章ではサンプルコードを通して、Flutterの基礎について学んで
いきます。
5.1 Flutterアプリの画面構造について
Flutterでは入力フォームや、ボタン、全体のレイアウトのWidgetを
組み合わせることで、アプリのUIを作成します。
次のコードは、プロジェクト作成時に作成されるサンプルコードか
ら、表示に関わる部分を切り出したものです。どのようにWidgetを組
み合わせ、UIを構成するのかをまとめました。
リスト5.1: main.dart
1: Widget build(BuildContext context) {
2: return Scaffold(
3: appBar: AppBar(
4: title: Text(widget.title),// タイトル
5: ),
6: body: Center(
7: child: Column(
8: mainAxisAlignment: MainAxisAlignment.center,
9: children: <Widget>[
10: Text(
11: 'You have pushed the button this many times:',
12: ),//本文
13: Text(// カウント文字表示
14: '$_counter',// 変数_counterを表示
15: style: Theme.of(context).textTheme.headline4,
16: ),
17: ],
18: ),
19: ),
20: floatingActionButton: FloatingActionButton(// 追加ボタン表示
21: onPressed: _incrementCounter,// ボタンを押したら_incrementCounterを呼び出す
22: tooltip: 'Increment',
23: child: Icon(Icons.add),// Addアイコン表示
24: ),
25: );
26: }
27: }

図5.1: Widgetの組合せ
簡単にですが、いくつかのWidgetについて解説します。
Scaffold
マテリアルデザインに乗っ取った、基本的な画面を構成するのに使
用します。このコードではappBar、body、floatingActionButtonのプ
ロパティーを使用します。
・appBar → 画面上部にあるバーの表示を制御するのに使います。多
くの場合、AppBarWidgetを割り当て、表示している画面の名称な
どを表示させます。
・body → 画面の主要コンテンツの表示を制御するのに使います。こ
の コ ー ド で は 、 CenterWidget を 割 り 当 て 、 そ の 中 で
ColumnWidgetを呼び、カウント数等を表示させています。
・floatingActionButton → body上に重なるように、ボタンを表示さ
せたいときに使います。主に、FloatingActionButtonWidgetを割
り当てます。
AppBar
アプリ画面上にあるバーの表示に使用します。画面の名称の表示だけ
でなく、通知ボタンの表示などに使用します。
Text
テキストを表示するのに使用します。styleプロパティーを設定する
ことで、文字の太さの変更やフォント・サイズの変更ができます。
MainAxisAlignment
ColumnやRowで表示するコンテンツの並びを制御するのに、使用し
ま す 。 MainAxisAlignment.center だ け で な く 、
MainAxisAlignment.startやMainAxisAlignment.endが存在します。
FloatingActionButton
いわゆるアイコンボタンの制御に使用します。onPressedプロパティ
ーにボタンを押下したときの動作などを設定できます。
各Widgetには、ここに記載した以外のプロパティーやメソッドなど
がたくさん存在します。本書を超えて自分オリジナルのアプリを作る場
合には、公式リファレンス1に、豊富なドキュメントやサンプルコード
があるので、ぜひご覧ください。
5.2 StatelessとStateful
Flutterでアプリを作成していると、「Stateless」と「Stateful」とい
うワードが頻繁に出てきます。ステートが変化するかしないかで、どち
らを選ぶのかが変わります。…と説明されても、いまいち理解しにくい
ので、具体例で見てみましょう。
Stateless
・スプラッシュ画面(起動画面)
・規約情報表示など、毎回固定で変化しない画面
Stateful
・リスト表示
・入力画面
・ネットからデータを取ってくるなど、表示が変化する画面
簡単な分類ですが、固く構えずに、イメージをつかんで試していき
ましょう。
5.3 パッケージ
パッケージを使用すると、簡単にアプリを拡張できます。pub.dev2
でパッケージの検索ができます。
Webサイトでアプリ画面のデザインを作れる Flutter Studio
本書では、アプリ画面のデザインを全てコードで行っていますが、Flutter Studio3を利用
すると、アプリ画面のデザインをWebサイトで作成できます。Flutter Studioは次のような
画面となっており、画面左にあるアイコンを真ん中にあるデバイスに対してドラッグ&ドロ
ップすることで、アプリ画面のデザインが可能です。アプリ画面のデザインに悩んだら、使
用してみるとよいでしょう。
図5.2: Flutter Studioの画面

1. https://flutter.dev/docs/development/ui/widgets
2. https://pub.dev/flutter/packages
3. https://flutterstudio.app/
第6章 アプリの仕様を決めよう
この章では、これから作成するアプリの仕様の詳細を説明します。
6.1 アプリの仕様
本書ではサンプルとして、何かを借りたときや貸したときに役立つ
「貸し借りを記録できるメモ帳」を作成します。
次の情報をメモできるものを作成します。
・貸したのか、借りたのか
・誰に
・何を
・返却期限
完成版の画面は次のとおりです。
図6.1: 完成イメージ
その貸し借りの記録を他の端末から確認できるように、ログインと
データをクラウドに保存する機能のために、Firebaseを使います。
今回のアプリに必要な機能は、次のとおりです。
・一覧表示機能
・新規登録機能
・編集機能
・削除機能
・ログイン機能
・共有機能
・多言語対応
さらに発展として、実際にリリースするための対応についてもまと
めました。
・リリース対応
この仕様を叶えられるアプリを、Flutterで作成しながら学んでいき
ます。
6.2 プロジェクトの作成
プロジェクトを作成します。第3章を参考に、プロジェクトを作成し
ます。本書では「kasikari_memo」という名前をプロジェクト名にし
て、開発を進めます。
困ったときは?
これから作成するアプリは、すべてGitHubのリポジトリーにコードがあります。章ごとに
タグを切っているので、つまずいた場合はそのタグのコードと見比べたりして、問題点を探
してみてください。
https://github.com/chasibu/kasikari_memo_v2
たとえば、この章は次のタグになります。
https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter6
本書の進め方
次の章より、次のコメントを含んだコードが登場します。
リスト6.1: サンプルコメント
1: /*-- Add Start --*/
2: /*-- Add End --*/
3:
4: /*-- Edit Start --*/
5: /*-- Edit End --*/

Add StartからAdd Endに囲われた箇所はコードを追加してください。Edit StartからEdit


Endに囲われた箇所は、既存のコードを修正して進めてください。====[/column]
fvmを使用したFlutterプロジェクトの始め方
fvmを使用する場合、プロジェクト毎に使用するFlutterのバージョンを指定する必要があ
ります。次のコマンドをプロジェクトのroot配下で実行し、使用するバージョンを指定しま
す。
fvm use 2.2.2
また、flutterコマンドを使用する場合は、次のように先頭にfvmをつけて、コマンドを実
行してください。
fvm flutter pub get
第7章 Firebaseの設定をしよう
アプリを作成する前に、Firebaseの設定をします。
この章を完了すると、タグ「chapter71」の内容になります。
7.1 アカウント取得
FirebaseのWebサイト2を開き、右上にある「コンソールへ移動」を
選択します。
図7.1: コンソールへ移動

「Firebaseへようこそ」の画面が表示されたら、「プロジェクトを
追加」を選択します。
図7.2: プロジェクトを追加

プロジェクト名に「kasikari-memo」と入力します。Google アナリ
ティクスの設定はオフにして、プロジェクトを作成します。
図7.3: 情報を入力
しばらく待つとプロジェクトが作成されるので、「次へ」ボタンを
選択します。
図7.4: 準備完了
7.2 FlutterアプリとFirebaseの紐付け
AndroidとiOS用に、それぞれ設定する必要があります。
7.2.1 Androidの設定
「Project Overview」を選択し、画面上にある「Android」のマーク
を選択します。
図7.5: Androidの設定

アプリの登録のために、Androidのパッケージ名を入力する必要があ
ります。前の章で作成したAndroid Studioのプロジェクトから
「android/app/src/main/AndroidManifest.xml」を開きます。2行目に
表示されている「package」の値がパッケージ名なので、これをコピー
します。
図7.6: AndroidManifest

パッケージ名を入力し、「アプリを登録」をクリックします。
図7.7: パッケージ名を入力
「google-services.json をダウンロード」をクリックし、JSONファ
イルをダウンロードします。ダウンロードが完了したら「次へ」をクリ
ックします。
図7.8: ダウンロード

ダウンロードしたファイルを「android/app」フォルダー直下にコピ
ーします。実際に配置すると「android/app/google-services.json」と
いった形になります。
図7.9: ファイルを配置
配置できたら、次はふたつのファイルを設定します。
「android/build.gradle」と「android/app/build.gradle」に、次の
内容を追記します。
リスト7.1: android/build.gradle
buildscript {
dependencies {
/*---------- Add Start ----------*/
classpath 'com.google.gms:google-services:4.3.8'
/*---------- Add End ----------*/
}
}

リスト7.2: android/app/build.gradle
dependencies {
/*---------- Add Start ----------*/
implementation platform('com.google.firebase:firebase-bom:28.1.0')
/*---------- Add End ----------*/
}
/*---------- Add Start ----------*/
apply plugin: 'com.google.gms.google-services'
/*---------- Add End ----------*/

図7.10: 追記内容
最後に、「このステップをスキップ」をクリックして、処理を終了
します。
図7.11: このステップをスキップ

これでAndroidの設定は完了です!
7.2.2 iOSの設定
トップページに表示されている「アプリを追加」を選択し、画面上
にある「iOS」のマークを選択します。
図7.12: iOSの選択

アプリの登録のために、iOSバンドルIDを入力する必要があります。
プロジェクトのフォルダーに移動してから次のコマンドを実行し、
Xcodeで開きます。
$ open ios/Runner.xcworkspace
実行後、次の画像のような画面が開きます。
図7.13: Xcodeで開く

サイドバーからRunnerを選択し、横にあるボタンを押します。
「TARGETS」の下にある「Runner」を選択すると、画面中央に
「Bundle Identifier」と記載のある欄が表示されます。これがバンドル
IDなので、コピーします。
図7.14: バンドルIDのコピー
Firebaseのアプリ登録画面に戻り、バンドルIDを入力し、アプリの登
録をします。画面の表示に従い、「GoogleService-info.plist」をダウ
ン ロ ー ド し ま す。 ダ ウ ン ロ ー ド し た フ ァ イ ル を Xcode 上 で、
「Runner」フォルダーの直下に移動させます。
図7.15: ダウンロードファイルの移動

次のコマンドを入力し、cocoapodsをインストールします。
$ brew install cocoapods
これで、iOSの設定は完了です。
7.3 データベースの作成
ここからは、アプリで使うデータベースを「Cloud Firestore」を使
って作成します。
7.3.1 Cloud Firestoreとは
Firebaseが提供しているNoSQL型のデータベースサービスです。似
た機能にRealtime Databaseがあるので、これらの違いを公式サイト3
から引用して、確認しましょう。
Cloud Firestore は、Firebaseのモバイルアプリ開発用の最新デー
タベースです。直感的な新しいデータモデルで、Realtime
Databaseをさらに強化しています。Cloud Firestore は、Realtime
Databaseよりも多彩で高速なクエリと高性能なスケーリングが特
長です。
「Realtime Database」に比べ「Cloud Firestore」の方がモバイルア
プリ開発に向いているため、こちらを使用します。
7.3.2 Cloud Firestoreの設定
左のリストより「構築」→「Firestore Database」を選択します。選
択したら「データベースの作成」を選択します。
このときに間違えてRealtime Databaseで作成しないように注意して
ください。
図7.16: Cloud FireStore

「テストモードで開始」を選択し、「次へ」をクリックします。あと
の章で、セキュリティーの設定をします。
図7.17: テストモードで開始

Cloud Firestoreのロケーションを設定します。デフォルトの設定
(nam5)のまま「有効にする」をクリックします。
続いて、テストデータを作成します。コレクションの作成ダイアログ
が表示されるので、コレクションIDに「promises」と入力し、「次
へ」をクリックします。
図7.18: コレクションの作成ダイアログ

テスト用のデータとして、次のデータを入力します。入力後に「保
存」をクリックします。
表7.1: 作成するコレクション
フィールド タイプ 値
borrowOrLend String lend
stuff String PC
user String てすとユーザー
date timestamp 2021/4/1 00:00:0000

図7.19: 作成するコレクション
正常に作成できると、次のようになります。
図7.20: 成功例
これでFirebaseの設定は完了です!

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter7
2. https://firebase.google.com/
3. https://firebase.google.com/docs/database/rtdb-vs-firestore?hl=ja
第8章 一覧画面の実装をしよう
この章では、先ほどCloud Firestoreに登録した貸し借りデータを表
示する機能を実装します。
この章を完了すると、タグ「chapter81」の内容になります。
8.1 pubspec.yamlの変更
「pubspec.yaml」を開き、次のようにパッケージを追加します。こ
れは、Flutter上でFirestoreを使用するために必要なプラグインです。
リスト8.1: pubspec.yamlの変更
dependencies:
flutter:
sdk: flutter
/*---------- Add Start ----------*/
firebase_core: 1.3.0
cloud_firestore: 2.2.2
/*----------- Add End -----------*/

コンソール画面から次のコマンドを入力します。
flutter packages get
もしくは、Android Studioの画面上からも操作できます。
図8.1: packages get実行
今後、pubspec.yamlにパッケージを追加する際は、この手順と同様
にpackages getを実行してください。
8.2 リスト作成
cloud_firestoreを利用するために、build.gradleの設定を追記しま
す。cloud_firestoreを利用することで、アプリ及びアプリの参照する
ライブラリーが65,536 メソッドを超えてしまいます。この結果、ビル
ドエラーが発生します。このエラーを解消するために、multiDexを有
効化します。
リスト8.2: android/app/build.gradle(multiDexの有効化)
android {
defaultConfig {
/*---------- Add Start ----------*/
multiDexEnabled true
/*----------- Add End -----------*/
}
}

続いて、元々「main.dart」に書かれているものを全て削除し、次の
コードに変更してください。
リスト8.3: main.dart
/*---------- Add Start ----------*/
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'list.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'かしかりメモ',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: List(),
);
}
}
/*----------- Add End -----------*/

「main.dart」と同じ層に「list.dart」というファイルを作成し、次
のコードを記載してください。
リスト8.4: list.dart
/*---------- Add Start ----------*/
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class List extends StatefulWidget {


@override
_MyList createState() => _MyList();
}

class _MyList extends State<List> {


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("かしかりメモ"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('promises').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if (!snapshot.hasData) return
Center(child: CircularProgressIndicator());
if (snapshot.data?.docs.length == 0) return
Center(child: Text("データが登録されていません"));
return ListView.builder(
itemCount: snapshot.data?.docs.length,
padding: const EdgeInsets.only(top: 10.0),
itemBuilder: (context, index) =>
_buildListItem(context, snapshot.data!.docs[index]),
);
}
),
),
);
}

Widget _buildListItem(BuildContext context, DocumentSnapshot document){


String borrowOrLend;
String limitDate = document['date'].toDate().toString().substring(0,10);
if (document['borrowOrLend'] == "lend") {
borrowOrLend = "貸";
} else {
borrowOrLend = "借";
}

return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.android),
title: Text("【$borrowOrLend】:${document['stuff']}"),
subtitle: Text(" 期限:$limitDate\n 相手:${document['user']}"),
),
]
),
);
}
}
/*----------- Add End -----------*/

Cloud Firestoreから読み込んだデータをリスト表示するためのコー
ドを作成しました。
これを実行すると、次のような画面が表示されます。
図8.2: list表示
8.3 リスト作成の解説
Flutterでは、void main()からアプリが開始されます。runAppに表示
データを渡すことで、アプリの画面を作成しています。
どのような順で呼び出されるのかは、次の画像をご覧ください。
図8.3: データ表示までのクラスの呼び出し

Flutterでは、多くは「StatefulWidget」か「StatelessWidget」のど
ちらかを継承して、クラスを作成します。今回は表示されるCloud
Firestoreの内容が変化するので、StatefulWidgetクラスを継承していき
ます。必要によってPaddingを使い、表示するWidgetに余白を指定し
ます。
Cloud Firestoreからデータを読み込み、リストとして表示するまで
の一連の処理をいくつか抜粋しながら解説します。
8.3.1 Listクラスを呼び出すまでの処理
リスト8.5: Listクラスを呼び出すまでの処理
Future<void> main() async {
// Firebase.initializeApp()を実行するために、明示的に呼び出す必要があるため
WidgetsFlutterBinding.ensureInitialized();
// Firebaseのパッケージを使用する場合に必ず、イニシャライズする必要があるため
await Firebase.initializeApp();
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'かしかりメモ',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: List(),
);
}
}

MaterialAppについて
Flutterが標準で用意しているWidgetの中でも、基礎的なWidgetのひ
とつです。マテリアルデザインを利用したアプリケーションを簡単に構
築できます。このWidget内では、アプリのタイトルや全体のテーマを
設定できます。title: 'かしかりメモ'やtheme: ThemeDataが該当箇所で
す。また、このWidgetの内部で別のWidgetを呼び出し、画面を描画し
ます。このコードではFutureBuilderというWidgetを呼び出します。
8.3.2 Firestoreの情報を取得するまでの処理
リスト8.6: Firestoreの情報を取得するまでの処理(list.dart)
class _MyList extends State<List> {

Widget build(BuildContext context) {


return Scaffold(
appBar: AppBar(
title: const Text("かしかりメモ"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.
collection('promises').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemCount: snapshot.data?.docs.length,
padding: const EdgeInsets.only(top: 10.0),
itemBuilder: (context, index) =>
_buildListItem(context, snapshot.data!.docs[index]),
);
}
),
),
);
}

StreamBuilderについて
非同期処理を行うための、Widgetです。streamで非同期的に扱いた
い処理を記載し、builderで表示するWidgetを記載します。似たような
WidgetにFutureBuilderがあります。FutureBuilderは一度実行したら
繰り返し実行されませんが、StreamBuilderは指定したイベントが更新
されるたびに再実行します。
こ の コ ー ド で は 、 stream: に
FirebaseFirestore.instance.collection('promises').snapshots()を指定
し、Cloud Firestoreからデータを取得します。データが受信されるま
で は 「 Loading... 」 と 表 示 し 、 デ ー タ を 取 得 し 始 め た ら
ListView.builderにデータを渡し、表示します。
ListView.builderについて
各プロパティーは次の役割をはたします。
・ itemCount → い く つ 表 示 す る か を 指 定 し ま す 。
snapshot.data.documents.lengthで取得してきたデータ長によっ
て表示数を指定します。
・itemBuilder → 表示するリストデータを受け渡します。
一件ごとのCloud Firestoreのデータを_buildListItem関数に渡し、リ
ストデータを作成しています。
8.3.3 Firestoreの情報を表示するまでの処理
リスト8.7: Firestoreの情報を表示するまでの処理(list.dart)
Widget _buildListItem(BuildContext context, DocumentSnapshot document){
String borrowOrLend;
String limitDate = document['date'].toDate().toString().substring(0,10);
if (document['borrowOrLend'] == "lend") {
borrowOrLend = "貸";
} else {
borrowOrLend = "借";
}

return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.android),
title: Text("【$borrowOrLend】:${document['stuff']}"),
subtitle: Text(" 期限:$limitDate\n 相手:${document['user']}"),
),
]
),
);
}

ListTileで表示したい文字を引数として渡されたdocument["キー名"]
で取得して、表示しています。
8.4 新規ボタン追加
新規ボタンを追加します。データを新規で追加するためのコードは、
第9章で作成します。
リスト8.8: list.dart
class _MyList extends State<List> {

Widget build(BuildContext context) {


return Scaffold(
appBar: AppBar(
title: const Text("かしかりメモ"),
),
body: Padding(
...
),
/*---------- Add Start ----------*/
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
print("新規作成ボタンを押しました");
}
),
/*----------- Add End -----------*/
);
}
}

青色の新規ボタンを表示するためのコードを作成しました。
実行すると、次のような画面が表示されます。
図8.4: 追加ボタン
8.5 新規ボタン追加の解説
ScaffoldにfloatingActionButtonを追加し、新規作成ボタンを追加し
ます。
登録機能の実装は後ほど行うため、ここではボタンを押したときの
処理を記載する、onPressedの中はprint("新規作成ボタンを押しまし
た");とだけ記載します。
この状態でアプリを実行すると、新規登録ボタンが表示されます。
8.6 編集ボタン追加
編集ボタンを追加して、後の章でデータを編集できるように用意し
ます。
リスト8.9: list.dart
class _MyList extends State<List> {
...

Widget _buildListItem(BuildContext context, DocumentSnapshot document){


return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.android),
title: Text("【$borrowOrLend】 : ${document['stuff']}"),
subtitle: Text(" 期限 : $limitDate\n 相手 : ${document['user']}"),
),
/*-- Add Start --*/
ButtonBarTheme(
data: ButtonBarThemeData(buttonTextTheme: ButtonTextTheme.accent),
child: ButtonBar(
children: <Widget>[
TextButton(
child: const Text("へんしゅう"),
onPressed: () {
print("編集ボタンを押しました");
}
),
],
)
),
/*-- Add End --*/
]
),
);
}
}

編集ボタンを表示するためのコードを作成しました。
実行すると、次のような画面が表示されます。
図8.5: 編集ボタン
8.7 編集ボタン追加の解説
Columnの中にButtonTheme.barを追加し、編集画面へのボタンを
設定します。
child: const Text("へんしゅう")で、ボタンに表示したい文字を指定し
ます。
編集機能の実装は後ほど行うため、ここでは、ボタンを押したときの
処理を記載します。
onPressed:の中にはprint("編集ボタンを押しました");とだけ記載しま
す。
この状態で、アプリを実行すると、編集ボタンが表示されます。
これで一覧画面の作成は完了です!
iOSでのbuildについて
iPhone上でbuildすると、実行環境によっては次のエラーが発生します。
Error output from CocoaPods:
[!] Automatically assigning platform `iOS` with version `9.0` on
target `Runner` because no platform was specified.
Please specify a platform for this target in your Podfile.
See `https://guides.cocoapods.org/syntax/podfile.html#platform`.
これは、iOSのbuild対象の最低バージョンが低いために発生します。次のように、コメン
トアウトを外し、対象のバージョンを変更してください。
リスト8.10: ios/Podfile
# Uncomment this line to define a global platform for your project
/*-- Edit Start --*/
platform :ios, '10.6'
/*-- Edit End --*/

また、cocoapodsのキャッシュが原因でbuildに失敗することがあります。そのような場
合には、Android Studioの再起動や「Invalidate Caches / Restart..」を選択し、buildして
ください。

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter8
第9章 入力画面の実装をしよう
この章では、貸し借り情報を入力する画面を作成します。次のデー
タを入力できるようにします。
・貸したのか、借りたのか
・誰から?
・何を?
・いつまで?
この章を完了すると、タグ「chapter91」の内容になります。
9.1 Promiseモデル作成
入力情報を取り扱うモデルを作成します。「promise_model.dart」
ファイルを作成し、次の内容を追記します。
リスト9.1: promise_model.dart
/*---------- Add Start ----------*/
class PromiseModel {
String borrowOrLend;
String user;
String stuff;
DateTime date;

PromiseModel(
this.borrowOrLend,
this.user,
this.stuff,
this.date
);
}
/*----------- Add End -----------*/
9.2 Promiseモデル作成の解説
Promiseモデルはこの後作成する、入力画面で入力された値を管理
するのに使用するクラスです。各変数は次のように対応しています。
・borrowOrLend → 貸したか借りたか
・user → 誰に貸したのか、借りたのか
・stuff → 何を貸したのか、借りたのか
・date → 締め切り日
9.3 入力画面作成
入力画面を作成します。「input_form.dart」ファイルを作成し、次
のコードでInputFormを作成します。
リスト9.2: input_form.dart
/*---------- Add Start ----------*/
import 'package:flutter/material.dart';
import 'promise_model.dart';

class InputForm extends StatefulWidget {


@override
_MyInputFormState createState() => _MyInputFormState();
}

class _MyInputFormState extends State<InputForm> {


final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final PromiseModel _promise = PromiseModel("borrow", "", "", DateTime.now());

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('かしかり入力'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: () {
print("保存ボタンを押しました");
}
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
print("削除ボタンを押しました");
},
),
],
),
body: SafeArea(
child:
Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[

RadioListTile(
value: "borrow",
groupValue: _promise.borrowOrLend,
title: Text("借りた"),
onChanged: (String? value){
print("借りたをタッチしました");
},
),
RadioListTile(
value: "lend",
groupValue: _promise.borrowOrLend,
title: Text("貸した"),
onChanged: (String? value) {
print("貸したをタッチしました");
}
),

TextFormField(
decoration: InputDecoration(
icon: const Icon(Icons.person),
hintText: (_promise.borrowOrLend == "lend" ? "貸した人" : "借りた人"),
labelText: 'Name',
),
onSaved: (String? value) {
_promise.user = value!;
},
),

TextFormField(
decoration: InputDecoration(
icon: const Icon(Icons.business_center),
hintText: (_promise.borrowOrLend == "lend" ? "貸したもの" : "借りたもの"),
labelText: 'loan',
),
onSaved: (String? value) {
_promise.stuff = value!;
},
),

Padding(
padding: const EdgeInsets.only(top:8.0),
child: Text(
"締め切り日:${_promise.date.toString().substring(0,10)}"
),
),

ElevatedButton(
child: const Text("締め切り日変更"),
onPressed: (){
print("締め切り日変更をタッチしました");
},
),
],
),
),
),
);
}
}
/*----------- Add End -----------*/

入力画面を作成しました。ここまでの実装では入力画面への遷移が
できないので、画面を確認できません。次の項目の画面遷移を実装し
てから、確認してみましょう。
画面遷移が完成して、入力画面を表示した場合は、次のような画面が
表示されます。
図9.1: 入力画面の作成
9.4 入力画面作成の解説
画面遷移を作成する前に、この入力画面について解説をします。ユ
ーザーの入力によって表示が変化するので、StatefulWidgetクラスを
継承していきます。
9.4.1 IconButton
保存ボタンと削除ボタンを表示します。機能の実装は後ほど行うた
め、ボタンが押されたときにコンソール画面に「保存ボタンを押しま
した」と表示します。他のIconの種類については、公式のドキュメン
ト2を確認ください。
9.4.2 Form
Formを使って、データの入力画面を作成します。key: _formKeyは
フォーム全体に対する制御をするもので、後ほど実装する入力チェッ
クに利用します。このKeyを使うことで、簡単に複数のWidgetをコン
トロールできます。
9.4.3 フォームの構成
RadioListTileでラジオボタンを作成します。「貸したのか、借りた
のか」の情報を入力します。この段階では、タッチしたときのボタン
の切り替わりは実装しません。
次の情報を記録できるように、TextFormFieldでテキストの入力画
面を作成します。
・貸した、借りた相手の名前
・貸し借りした物の名前
入力画面のみを作成しているため、まだデータの保存等は行えませ
ん。
9.5 一覧画面から入力画面への画面遷移
一覧画面から入力画面への画面遷移を作成します。第8章で作成した
一覧画面の新規作成ボタンに、コードを追加します。
リスト9.3: list.dart
/*---------- Add Start ----------*/
import 'input_form.dart';
/*----------- Add End -----------*/
class _MyList extends State<List> {

Widget build(BuildContext context) {


return Scaffold(
appBar: AppBar(
title: const Text("かしかりメモ"),
),
body: Padding(
...
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.check),
onPressed: () {
print("新規作成ボタンを押しました");
/*---------- Add Start ----------*/
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: "/new"),
builder: (BuildContext context) => InputForm()
),
);
/*----------- Add End -----------*/
}
),
);
}
}

一覧画面から登録画面への画面遷移を作成しました。次の画像の赤
枠で囲われたボタンを押すと、前の項目で作成した入力画面を開くこ
とができるので、試してみましょう。
図9.2: 一覧画面で押すボタン
9.6 一覧画面から入力画面への画面遷移の解説
Navigatorという機能を使って、画面遷移を実装します。Navigator
を使うことで、画面遷移を簡単に行うことができます。基本的には、
次のふたつを使って画面遷移します。
・push → 次のページを指定して移動します。
・pop → 表示されているページを閉じて、前に表示していた画面に
遷移します。
新規登録ボタン選択後、Navigator.push()を使用し、入力画面を表示
します。
9.6.1 MaterialPageRouteについて
ページ遷移に使用します。使用したプロパティーについては、次の
とおりです。
・settings:は、ルーティングの設定を行います。
・builder:は、どこのクラスを呼び出すのか設定します。
9.7 ラジオボタン有効化
ラジオボタンを有効化するために、コードを修正します。
リスト9.4: input_form.dart
class _MyInputFormState extends State<InputForm> {
...

/*---------- Add Start ----------*/


void _setLendOrRent(String value){
setState(() {
_promise.borrowOrLend = value;
});
}
/*----------- Add End -----------*/

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
...
),
body: SafeArea(
child:
Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[

RadioListTile(
value: "borrow",
groupValue: _promise.borrowOrLend,
title: Text("借りた"),
onChanged: (String? value){
print("借りたをタッチしました");
/*---------- Add Start ----------*/
_setLendOrRent(value!);
/*----------- Add End -----------*/
},
),

RadioListTile(
value: "lend",
groupValue: _promise.borrowOrLend,
title: Text("貸した"),
onChanged: (String? value) {
print("貸したをタッチしました");
/*---------- Add Start ----------*/
_setLendOrRent(value!);
/*----------- Add End -----------*/
}
),
...
],
),
),
),
);
}
}

ラジオボタンをタッチすると切り替えができるので、試してみまし
ょう。
図9.3: ラジオボタン
9.8 ラジオボタン有効化の解説
ボ タ ン を 押 し た と き に 、 onChanged が 発 火 し ま す 。
_promise.borrowOrLendにタップされたかどうかの情報を入れること
で、表示を切り替えることができます。
9.9 日付選択画面作成
日付の選択画面を作成します。日付ダイアログでのユーザーから入
力を受け取るために、非同期処理を使います。
リスト9.5: input_form.dart
import 'package:flutter/material.dart';
import 'package:kasikari_memo_v2/promise_model.dart';

class _MyInputFormState extends State<InputForm> {


...

/*---------- Add Start ----------*/


Future <DateTime?> _selectTime(BuildContext context) {
return showDatePicker(
context: context,
initialDate: _promise.date,
firstDate: DateTime(_promise.date.year - 2),
lastDate: DateTime(_promise.date.year + 2)
);
}
/*----------- Add End -----------*/

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
...
),
body: SafeArea(
child:
Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[
...
RaisedButton(
child: const Text("締め切り日変更"),
onPressed: (){
print("締め切り日変更をタッチしました");
/*---------- Add Start ----------*/
_selectTime(context).then((time){
if(time != null && time != _promise.date){
setState(() {
_promise.date = time;
});
}
});
/*----------- Add End -----------*/
},
),
],
),
),
),
);
}
}

「締め切り日変更」と書かれたボタンを押してみましょう。次のよ
うなカレンダーが表示され、日付を選択できます。
図9.4: 日付選択画面
9.10 日付選択画面作成の解説
「締め切り日変更」と書かれたボタンを押すと、時刻を入力するた
めに_selectTime()を呼び出します。Flutterがデフォルトで用意してい
る、日時を入力するためのshowDatePicker()関数を使用します。
showDatePicker()
日時を入力するのに必要なデータを設定します。
・initialDate → 初期値の日付を設定します。
・firstDate → 最小の日付を設定します。実行日から2年前を設定し
ました。
・lastDate → 最大の日付を設定します。実行日から2年後を設定し
ました。

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter9
2. https://api.flutter.dev/flutter/material/Icons-class.html
第10章 登録機能の実装をしよう
この章では、Cloud Firestoreへのデータ登録機能を作成します。
この章を完了すると、タグ「chapter101」の内容になります。
10.1 登録機能作成
Cloud Firestoreにデータを保存するために、次のコードを追加しま
す。この次の項目の「入力チェック機能」までいってから、動作確認
をします。
リスト10.1: input_form.dart
/*---------- Add Start ----------*/
import 'package:cloud_firestore/cloud_firestore.dart';
/*----------- Add End -----------*/

class _MyInputFormState extends State<InputForm> {


...

@override
Widget build(BuildContext context) {
/*---------- Add Start ----------*/
DocumentReference _mainReference =
FirebaseFirestore.instance.collection('promises').doc();
/*----------- Add End -----------*/

return Scaffold(
appBar: AppBar(
title: const Text('かしかり入力'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: () {
print("保存ボタンを押しました");
/*---------- Add Start ----------*/
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
_mainReference.set(
{
'borrowOrLend': _promise.borrowOrLend,
'user': _promise.user,
'stuff': _promise.stuff,
'date': _promise.date
}
);
Navigator.pop(context);
}
/*----------- Add End -----------*/
}
),
IconButton(
...
),
],
),
...
)
}
}
10.2 登録機能作成の解説
Cloud Firestore に デ ー タ を 登 録 す る た め に 、
FirebaseFirestore.instance.collection('コレクション名').doc()を使用
し、インスタンスを生成します。_formKeyは保存ボタン選択後、次の
項目で実装する入力チェック時に使用します。
・_formKey.currentState.validate() → validatorを呼び出します。
・_formKey.currentState.save() → onSavedを呼び出します。
入力チェックを行い、問題なければ_mainReference.set()を使用
し、Cloud Firestoreへデータの登録をします。「"key":"value"」形式
で 、 Cloud Firestore へ デ ー タ 登 録 を し ま す。 登 録 処 理 後 、
Navigator.popを利用して、元の一覧画面に戻ります。
10.3 入力チェック機能
テキストフォームで、入力された文字のチェック機能を作成しま
す。
リスト10.2: input_form.dart

class _MyInputFormState extends State<InputForm> {


...

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
...
),
body: SafeArea(
child:
Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(20.0),
children: <Widget>[
...
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: '相手の名前',
labelText: 'Name',
),
onSaved: (String? value) {
_promise.user = value!;
},
/*---------- Add Start ----------*/
validator: (value) {
if(value!.isEmpty) {
return '名前は必須入力です';
} else {
return null;
}
},
initialValue: _promise.user,
/*----------- Add End -----------*/
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.business_center),
hintText: '借りたもの、貸したもの',
labelText: 'loan',
),
onSaved: (String? value) {
_promise.stuff = value!;
},
/*---------- Add Start ----------*/
validator: (value) {
if(value!.isEmpty) {
return '借りたもの、貸したものは必須入力です';
} else {
return null;
}
},
initialValue: _promise.stuff,
/*----------- Add End -----------*/
),
...
],
),
),
),
);
}
}

空データのときに、保存ボタンを押してみましょう。次のような画
面になります。
図10.1: 入力チェック機能
ここまで実装できたら文字を入力し、画面上部の保存ボタンを押し
ます。入力データをCloud Firestoreに保存し、データ一覧画面で確認
できるので、試してみてください。
10.4 入力チェック機能の解説
TextFormFieldに対して、入力チェック機能を実装していきます。
・validator: → 入力欄が空欄のときに、エラー文を返すように設定
します。
・onSaved: → _promiseの各プロパティーに対して、値の代入をし
ます。
・initialValue: → 初期値を設定します。
これで、登録機能の実装は完了です。

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter10
第11章 編集機能の実装をしよう
この章では、すでに登録したデータの編集機能の実装をします。
この章を完了すると、タグ「chapter111」の内容になります。
11.1 編集機能作成
編集機能実装のため、引数として編集元のデータを渡せるように修
正します。
リスト11.1: list.dart
class _MyList extends State<List> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("かしかりメモ"),
),
body: Padding(
...
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.check),
onPressed: () {
print("新規作成ボタンを押しました");
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: "/new"),
/*---------- Edit Start ----------*/
//新規作成ボタンの修正
builder: (BuildContext context) => InputForm(null)
/*----------- Edit End -----------*/
),
);
}
),
);
}

Widget _buildListItem(BuildContext context, DocumentSnapshot document){


return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
...
ButtonTheme.bar(
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text("へんしゅう"),
onPressed: () {
print("編集ボタンを押しました");
/*---------- Add Start ----------*/
//編集ボタンの処理追加
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: "/edit"),
builder: (BuildContext context) =>
InputForm(document)
),
);
/*----------- Add End -----------*/
}
),
],
)
),
]
),
);
}
}

リスト11.2: input_form.dart
class InputForm extends StatefulWidget {
/*---------- Add Start ----------*/
//引数の追加
final DocumentSnapshot? document;
InputForm(this.document);
/*----------- Add End -----------*/

@override
_MyInputFormState createState() => _MyInputFormState();
}

class _MyInputFormState extends State<InputForm> {


...

@override
Widget build(BuildContext context) {
DocumentReference _mainReference =
FirebaseFirestore.instance.collection('promises').doc();
/*---------- Add Start ----------*/
//編集データの作成
//引数にデータが存在しているか確認
if(widget.document != null) {
// 日付・貸し借り情報更新時に、再buildされるため、値が更新されるのを防ぐ
if(_promise.user == "" && _promise.stuff == "") {
_promise.borrowOrLend = widget.document!['borrowOrLend'];
_promise.user = widget.document!['user'];
_promise.stuff = widget.document!['stuff'];
_promise.date = widget.document!['date'].toDate();
}
_mainReference = FirebaseFirestore.instance.
collection('promises').doc(widget.document!.id);
}
/*----------- Add End -----------*/
return Scaffold(
appBar: AppBar(
title: const Text('かしかり入力'),
...
),
);
}
}

これで、編集ボタンを押したときにデータの編集ができるようにな
りました。実際に動かしてみましょう!次の画像のように、実際に登
録してあるデータを選択し、編集画面を開きます。
図11.1: 編集機能
11.2 編集機能作成の解説
次の4つの部分のコードを修正しました。順に説明していきます。
・引数の追加
・新規作成ボタンの修正
・編集ボタンの処理追加
・編集データの作成
11.2.1 引数の追加
編集機能を作るためには、どのデータが修正対象か、という情報が
必要です。
InputForm(this.document)のように引数を追加することで、データ
によって処理を変更します。
11.2.2 新規作成ボタンの修正
引数を追加したので、クラスの呼び出し元を修正します。新規作成時
には、編集時の遷移と区別したいのでnullを渡します。
変更前 → builder: (BuildContext context) => InputForm()
変更後 → builder: (BuildContext context) => InputForm(null)
11.2.3 編集ボタンの処理追加
編集ボタンを押したときに編集できるように、コードを追加します。
編集ボタンで選択されたデータを引数として渡します。
builder: (BuildContext context) => InputForm(document)
11.2.4 編集データの作成
引数にデータがある場合、それぞれのWidgetにデータをセットし
て、貸し借り情報を表示します。引数がない場合には、デフォルトのデ
ータをセットして、貸し借り情報を表示します。
これで、更新機能の実装は完了です。

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter11
第12章 削除機能の実装をしよう
この章では、登録したデータの削除機能を実装します。
この章を完了すると、タグ「chapter121」の内容になります。
12.1 削除機能作成
_MyInputFormStateクラスに削除機能を追加します。
リスト12.1: input_form.dart
class _MyInputFormState extends State<InputForm> {
...
@override
Widget build(BuildContext context) {
DocumentReference _mainReference =
FirebaseFirestore.instance.collection('promises').doc();

/*---------- Add Start ----------*/


bool isDeleteDocument = false;
/*----------- Add End -----------*/
if(widget.document != null) {
if(_promise.user == "" && _promise.stuff == "") {
_promise.borrowOrLend = widget.document!['borrowOrLend'];
_promise.user = widget.document!['user'];
_promise.stuff = widget.document!['stuff'];
_promise.date = widget.document!['date'].toDate();
}
_mainReference = FirebaseFirestore.instance.
collection('promises').doc(widget.document!.id);
/*---------- Add Start ----------*/
isDeleteDocument = true;
/*----------- Add End -----------*/
}
}
return Scaffold(
appBar: AppBar(
title: const Text('かしかり入力'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: () {
...
}
),
/*---------- Edit Start ----------*/
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
if (isDeleteDocument) {
print("削除ボタンを押しました");
_mainReference.delete();
Navigator.pop(context);
} else {
return null;
}
}
)
/*----------- Edit End -----------*/
],
),
)
}
...
}

次の画像のように、登録してあるデータを削除してみましょう。
図12.1: 登録データの削除
12.2 削除機能作成の解説
if (isDeleteDocument) の部分で、削除ボタンの有効化・無効化を判
定しています。画面上部にある削除ボタンを編集のときは有効化、新規
作成のときには無効化します。
編集時には_mainReference.delete()を利用して、データを削除しま
す。
これで、削除機能の実装は完了です。

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter12
第13章 ログイン機能の実装をしよう
現在の設定では、Cloud Firestoreに入力された貸し借りデータは誰
でも確認して、編集できます。このままでは、秘密にしたいことも公
開されてしまい、困ってしまいます。そこでログイン機能を実装し、
自分が入力したデータを自分だけが見られるようにします。
この章を完了すると、タグ「chapter131」の内容になります。
13.1 認証機能の作成
ログイン機能の実装は、Firebaseが提供している「Firebase
Authentication」を利用します。
13.1.1 Firebase Authenticationとは
自前で作ると大変な認証機能を、少しのコードを追加するだけで、
ユーザー認証機能が実装できるサービスです。使用可能なログインプ
ロバイダは、次のとおりです。
・メール/パスワード
・電話番号(SMS)
・Google
・Playゲーム
・Facebook
・Game Center
・Facebook
・Twitter
・GitHub
・Yahoo!
・Microsoft
・Apple
・匿名ログイン
Firebase Authenticationを使うと、簡単な設定で、アプリにさまざ
まなアカウントでログインができるようになります。今回のアプリで
は、メール/パスワードログインと匿名ログインを使用します。
13.1.2 Firebase Authenticationの設定
コードを書く前に、FirebaseのWebコンソール上から設定します。
FirebaseのサイドメニューにあるAuthenticationをクリックします。
図13.1: Authenticationの開き方

「始める」ボタンをクリックします。
図13.2: Authenticationの開き方

ログインプロバイダからメール/パスワードを選択し、有効に変更し
て保存をクリックします。
図13.3: メール/パスワードの有効化

ログインプロバイダから匿名を選択し、有効に変更して保存をクリ
ックします。
図13.4: 匿名の有効化

メール/パスワード、匿名が有効になっていることを確認できれば、
準備完了です。
図13.5: メール/パスワード、匿名有効化の確認
これで、Firebase側の準備は整いました。それでは、実際にコード
を書いていきましょう。
13.1.3 ライブラリーの追加
ログイン機能を有効化するため、pubspec.yamlにライブラリーを追
加します。
リスト13.1: pubspec.yaml
name: kasikari_memo
description: kasikari memo Flutter application.
...
dependencies:
flutter:
sdk: flutter
firebase_core: 1.3.0
cloud_firestore: 2.2.2
/*---------- Add Start ----------*/
firebase_auth: 1.4.1
fluttertoast: 8.0.7
/*---------- Add End ----------*/

それぞれのライブラリーの役割は、次のとおりです。
・firebase_auth : Firebase Authenticationを利用するためのプラグ
インです。
・fluttertoast : ログインできなかったときにトースト表示をするの
に使用します。
iOSでのbuildエラー対応
ライブラリーを追加し、iOSでbuildすると、次のメッセージが表示されることがありま
す。
warning: The iOS Simulator deployment target 'IPHONEOS_DEPLOY
MENT_TARGET'
is set to 8.0, but the range of supported deployment
target versions is 9.0 to 14.0.99
iOSのdeploymentターゲットがサポート対象外となっているため、発生します。Podfile
に次の設定を追記します。
リスト13.2: Podfile
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
/*---------- Add Start ----------*/
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
end
/*----------- Add End -----------*/
end
end

13.1.4 ルーティング追加
ログイン機能実装前にルーティングを追加します。機能実装を進め
る上で、表示する画面が増えるためです。
リスト13.3: main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'list.dart';
/*---------- Add Start ----------*/
import 'initialize.dart';
import 'splash.dart';
/*----------- Add End -----------*/
...

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'かしかりメモ',
theme: ThemeData(
primarySwatch: Colors.blue,
),
/*---------- Edit Start ----------*/
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => Initialize(),
'/list': (BuildContext context) => List(),
'/splash': (BuildContext context) => Splash()
}
/*---------- Edit End ----------*/
);
}
}

13.1.5 ルーティング追加の解説
アプリのルーティングを設定します。アプリ起動直後、Initializeク
ラスを呼び出すように変更し、このクラスの中でログインの確認処理
を行います。Listクラス・Splashクラスについても、同様に設定をし
ておきます。
13.1.6 ログイン確認追加
「initialize.dart」を「main.dart」と同じ階層に作成し、次のコー
ドを記載します。
リスト13.4: initialize.dart
/*---------- Add Start ----------*/
import 'package:flutter/material.dart';

import 'list.dart';
import 'splash.dart';
import 'user_auth.dart';

class Initialize extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _mailLoginCheck(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text("接続に失敗しました");
}
else if (snapshot.connectionState == ConnectionState.done) {
return List();
}
else {
return Splash();
}
}
)
);
}

Future _mailLoginCheck() async {


// メール/パスワードによるログイン状態でない場合は匿名ユーザーでログインする
if (userAuth.currentUser == null) {
await userAuth.signInAnonymously();
}
}
}
/*---------- Add End ----------*/
「user_auth.dart」を「main.dart」と同じ階層に作成し、次のコー
ドを記載します。
リスト13.5: user_auth.dart
/*---------- Add Start ----------*/
import 'package:firebase_auth/firebase_auth.dart';

final userAuth = FirebaseAuth.instance;


/*---------- Add End ----------*/

FirebaseAuth.instanceをuserAuthに代入して、この変数を取り回し
て使用します。
13.1.7 ログイン確認の解説
Initializeクラスでは、ユーザーがメール/パスワードによるログイン
状態かどうかを確かめています。ログイン状態でない場合は、匿名ユ
ーザーとしてログインし、一覧画面に遷移させます。ログイン状態の
判断は非同期処理で行い、結果がわかるまでの間はスプラッシュ画面
を表示させます。
FutureBuilder
非同期処理を行うためのFlutterに用意されているWidgetです。
futureで実行したい非同期処理を指定します。builderで非同期処理の
実行結果を参照して、実行したい処理を記載します。futureの実行結
果は、AsyncSnapshot snapshotに代入されます。
signInAnonymously()は非同期処理として扱う必要があるため、
futureに指定します。非同期処理の結果が代入されているsnapshotの
値によって、次のように画面を切り替えます。
・snapshotにエラーが含まれている → 「接続できません」と表示
・snapshotのconnectionStateがdoneになっている → 一覧画面に
遷移
・上記以外の状態 → スプラッシュ画面に遷移
この時点では、スプラッシュ画面の実装が足りていないため、エラー
が発生し、アプリの起動が行えません。スプラッシュ画面を作成し、
エラーを解消します。
13.1.8 スプラッシュ画面作成
アプリを起動するとき、Firebaseの初期化処理の完了を待っている
間に表示する、簡易スプラッシュ画面の実装をします。
「splash.dart」を「main.dart」と同じ階層に作成し、次のコード
を記載します。
リスト13.6: splash.dart
/*---------- Add Start ----------*/
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class Splash extends StatelessWidget{


@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: const Text("スプラッシュ画面"),
),
);
}
}
/*---------- Add End ----------*/

Scaffold内でスプラッシュ画面に表示する内容を記載しており、画
面の中央に「スプラッシュ画面」と表示しています。
13.2 メールアドレスによる認証機能の作成
ここから、メール/パスワードを利用した認証機能を作成していきま
す。
13.2.1 ログインボタン作成
ログインボタンは一覧画面の上部に表示します。
リスト13.7: list.dart
class _MyList extends State<List> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("かしかりメモ"),
/*---------- Add Start ----------*/
actions: <Widget>[
IconButton(
icon: Icon(Icons.exit_to_app),
onPressed: () {
print("ログインボタンを押しました。");
showBasicDialog(context);
},
)
]
/*---------- Add End ----------*/
),
...
);
}
}

ログインボタンは一覧画面のappBarに追記します。次に実装する
showBasicDialog()が呼び出され、ログイン画面を表示します。
13.2.2 ログイン画面作成
ダイアログを用いて、ログイン画面を実装します。
リスト13.8: list.dart
/*---------- Add Start ----------*/
import 'package:fluttertoast/fluttertoast.dart';
import 'user_auth.dart';
/*---------- Add End ----------*/

class _MyList extends State<List> {


...
/*---------- Add Start ----------*/
void showBasicDialog(BuildContext context) {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String email = "";
String password = "";
if(userAuth.currentUser!.isAnonymous) {
showDialog(
context: context,
builder: (BuildContext context) =>
AlertDialog(
title: Text("ログイン/登録ダイアログ"),
content: Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.mail),
labelText: 'Email',
),
onSaved: (String? value) {
email = value!;
},
validator: (value) {
if (value!.isEmpty) {
return 'Emailは必須入力項目です';
} else {
return null;
}
},
),
TextFormField(
obscureText: true,
decoration: const InputDecoration(
icon: const Icon(Icons.vpn_key),
labelText: 'Password',
),
onSaved: (String? value) {
password = value!;
},
validator: (value) {
if (value!.isEmpty) {
return 'Passwordは必須入力項目です';
} else if (value.length<6) {
return 'Passwordは6桁以上です';
} else {
return null;
}
},
),
],
),
),
// ボタンの配置
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.pop(context);
}
),
TextButton(
child: Text('登録'),
onPressed: () async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
await _createUser(context, email, password);
}
Navigator.
pushNamedAndRemoveUntil(context, "/list", (_) => false);
}
),
TextButton(
child: Text('ログイン'),
onPressed: () async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
await _signIn(context, email, password);
}
Navigator.
pushNamedAndRemoveUntil(context, "/list", (_) => false);
}
),
],
),
);
} else {
showDialog(
context: context,
builder: (BuildContext context) =>
AlertDialog(
title: Text("確認ダイアログ"),
content:
Text(userAuth.currentUser!.email! + " でログインしています。"),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.pop(context);
}
),
TextButton(
child: Text('ログアウト'),
onPressed: () async {
await _signOut();
Navigator.
pushNamedAndRemoveUntil(context, "/list", (_) => false);
}
),
],
),
);
}
}

Future<void> _signOut() async {


try {
await userAuth.signOut();
await userAuth.signInAnonymously();
} catch(e) {
print(e);
Fluttertoast.showToast(msg: "Firebaseのログインに失敗しました。");
}
}

Future<void> _signIn(BuildContext context,String email, String password) async {


try {
await userAuth.signInWithEmailAndPassword(email: email, password: password);
} catch(e) {
print(e);
Fluttertoast.showToast(msg: "Firebaseのログインに失敗しました。");
}
}

Future<void> _createUser(BuildContext context,String email, String password) async {


try {
await userAuth.
createUserWithEmailAndPassword(email: email, password: password);
} catch(e) {
print(e);
Fluttertoast.showToast(msg: "Firebaseの登録に失敗しました。");
}
}
}
/*---------- Add End ----------*/

firebaseUser.isAnonymousを使用し、ユーザーが匿名アカウントか
メール/パスワードログインによるアカウントかどうかで処理を分けま
す。アプリ起動時にユーザーがログインを行っていない場合、匿名ア
カウントでアプリを使用します。ログインボタンを押したときに、ロ
グイン状態によって表示を切り替えます。
・匿名アカウントの場合 : ログインボタンを押すと、ログイン/登録
画面が表示します。
・匿名アカウント以外の場合 : ログアウトボタンを表示します。
_signInでは、_auth.signInWithEmailAndPasswordを使用し、メー
ル / パ ス ワ ー ド で ロ グ イ ン し ま す 。 _createUser で は 、
_auth.createUserWithEmailAndPasswordを使用し、メール/パスワー
ドで新規ユーザーを作成します。
13.3 Cloud Firestoreとログイン機能を組み合わせ
よう
ログイン機能を実装したことで、Cloud Firestore側の変更する必要
があります。データの保存構成やアクセスルールを変更していきま
す。
13.3.1 データ保存構成
今までの機能では、次のような構成になっていました。
図13.6: 今までの構成

それを次のような構成に変更します。
users → [userID] → promises → [貸し借りデータ]
・userID : ログインしたユーザーID
・貸し借りデータ : アプリから入力したデータ
図13.7: 今後の構成
Cloud Firestoreのデータ保存の仕組み
Cloud Firestoreでは、NoSQLドキュメント指向データベースです。NoSQLはSQLとは異
なり、キーと値の組み合わせ(key-value型)でデータを保存します。
データの集合をドキュメントという名称で呼び、ドキュメントの集合をコレクションと
いう名称で呼びます。
図13.8: データ、ドキュメント、コレクションの関係

また、ドキュメントの下にはデータだけなく、コレクションを追加できます。次の画像
のように「コレクション→ドキュメント→コレクション…」という入れ子構造を作れま
す。
※ドキュメントの下にドキュメント、コレクションの下にコレクションを作ることはで
きません。
図13.9: ドキュメント、コレクションの入れ子構造
13.3.2 入力機能の変更
ログイン機能を追加したため、データの保存構成が変更になりまし
た。これに伴い、データの入力機能についても変更を加えます。
リスト13.9: input_form.dart
/*----------- Add Start -----------*/
import 'user_auth.dart';
/*----------- Add End -----------*/
class _MyInputFormState extends State<InputForm> {
...

@override
Widget build(BuildContext context) {
/*----------- Edit Start -----------*/
DocumentReference _mainReference =
FirebaseFirestore.instance.
collection('users').doc(userAuth.currentUser!.uid).
collection('promises').doc();
/*----------- Edit End -----------*/

bool isDeleteDocument = false;

if(widget.document != null) {
if(_promise.user == "" && _promise.stuff == "") {
_promise.borrowOrLend = widget.document!['borrowOrLend'];
_promise.user = widget.document!['user'];
_promise.stuff = widget.document!['stuff'];
_promise.date = widget.document!['date'].toDate();
}
/*----------- Edit Start -----------*/
_mainReference = FirebaseFirestore.instance.
collection('users').doc(userAuth.currentUser!.uid).
collection('promises').doc(widget.document!.id);
/*----------- Edit End -----------*/
isDeleteDocument = true;
}
}
}

貸し借りデータを保持するための、Firestore.instanceの生成方法を
変更します。先ほど決めた「データ保存構成」に合わせて修正しま
す。
collection('users') → doc(userAuth.currentUser!.uid) →
collection("promises")
この順に設定することで、準備は完了です。
13.3.3 一覧表示画面の変更
データ保存構成が変更になったため、一覧画面にも変更を加えま
す。
リスト13.10: list.dart
class _MyList extends State<List> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
...
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: StreamBuilder<QuerySnapshot>(
/*----------- Edit Start -----------*/
stream: FirebaseFirestore.instance.collection('users').
doc(userAuth.currentUser!.uid).
collection("promises").snapshots(),
/*----------- Edit End -----------*/
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
...
);
}
),
),
)
}
}
13.3.4 Cloud Firestoreのルール設定
Cloud Firestoreには、アクセス制御のルールを設定できます。この
ルールを決めることで、不正なアクセスを防ぎ、安全なデータベース
を構築できます。自分が作成したデータのみを見ることができる設定
とします。
Firebaseにログインし、「Database > ルール」を開きます。
図13.10: ルール設定画面

次のコードに置き換えて、「公開」と書かれたボタンをクリックし
ます。
リスト13.11: Cloud Firestore ルール
1: rules_version = '2';
2: service cloud.firestore {
3: match /databases/{database}/documents {
4: match /users/{userId}/promises/{file} {
5: allow read,create,delete,update: if request.auth.uid == userId;
6: }
7: }
8: }
図13.11: ルール設定画面

これで設定は完了です。
13.3.5 Cloud Firestoreのルール設定解説
「/users/{userId}/promise/{file}」に一致し、「if request.auth.uid
== userId」の条件にあてはまる場合に、読み込み/新規作成/削除/アッ
プデートの許可をするコードになります。
・request.auth.uid : リクエストがあったユーザーのIDを指します。
・userId : ファイルのフォルダーとして使用しているユーザーのID
を指します。
フォルダーのユーザーIDとリクエストのあったユーザーIDが一致し
たときに、読込、作成、更新、削除の権限を許可するようになってい
ます。これで他のファイルの読み書きはできないので、バグなどで間
違えて他人のデータが見えてしまうということはなくなりました。こ
れで、Cloud Firestoreルール設定は完了です!
ルールのテスト
Cloud Firestoreルール設定をするときには、問題がないか確認しながら開発できます。
シミュレータがあり、認証した状態やパスの設定、読み書きの状態など、さまざまなこと
を確認できるので、開発時には活用しましょう!
図13.12: ルール設定画面

13.3.6 アプリを実行してみよう
アプリを実行して、次の画像のように実際にログインフォームを入
力し、アカウントを作成してログインしてみましょう。
図13.13: ログイン入力画面

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter13
第14章 共有機能を実装しよう
この章では、記録した貸し借り情報を他のアプリで共有する機能を
実装します。
この章を完了すると、タグ「chapter141」の内容になります。
14.1 プラグインを追加する
共有機能を追加するために、Shareプラグイン2を追加します。
リスト14.1: pubspec.yaml
dependencies:
flutter:
sdk: flutter
...
cloud_firestore: 2.2.2
firebase_auth: 1.4.1
fluttertoast: 8.0.7
/*---------- Add Start ----------*/
share: 2.0.4
/*----------- Add End -----------*/
14.2 共有機能作成
入力画面を開いたときに、表示される内容を共有できるようにしま
す。_MyInputFormStateクラスに共有機能を追加します。
リスト14.2: input_form.dart
import 'package:cloud_firestore/cloud_firestore.dart';
...
/*---------- Add Start ----------*/
import 'package:share/share.dart';
/*----------- Add End -----------*/
import 'dart:async';
import 'user_auth.dart';
...

class _MyInputFormState extends State<InputForm> {


...
return Scaffold(
appBar: AppBar(
title: const Text('かしかり入力'),
actions: <Widget>[
...
/*---------- Add Start ----------*/
IconButton(
icon: Icon(Icons.share),
onPressed: () {
if (_formKey.currentState!.validate()) {
String _borrowOrLend =
_promise.borrowOrLend == "lend" ? "貸" : "借";

Share.share(
"【 $_borrowOrLend 】${_promise.stuff}\n"
"期限:${_promise.date.toString().substring(0,10)}\n"
"相手:${_promise.user}\n"
"#かしかりメモ"
);
}
},
)
/*----------- Add End -----------*/
]
)
...
)
}
14.3 共有機能作成の解説
Shareというプラグインをインポートし、共有機能を実装します。共
有ボタンは入力・編集画面の上部に表示させます。そのため、appBar
の下にIconButtonを追加します。保存機能の実装と同様にonPressed:
を使用し、ボタン選択後の処理を実装します。Share.shareを使用し、
共有画面を表示します。共有する内容は引数で指定できます。
それでは、アプリを実行してみましょう。次の画像のように、実際
に登録してあるデータの共有が可能になっています。
図14.1: 共有機能

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter14
2. https://pub.dev/packages/share
第15章 多言語化対応しよう
この章では、多言語化対応に必要な実装をします。スマートフォンの
設定言語に合わせて、日本語と英語に対応できるように変更します。今
回の方法以外にも多言語対応させる方法はありますが、IDEのプラグイ
ンを用いた方法が簡単なので、その方法で進めていきます。
この章を完了すると、タグ「chapter151」の内容になります。
15.1 多言語化プラグインのインストール
Android Studioの設定画面からプラグインをダウンロードします。
Android Studioの画面上部から「Android Studio > Preference」を選
択します。
図15.1: Preferenceの選択

サイドメニューから「Plugins」を選択し、画面上部のタブ
「Marketplace」を選択します。画面上部の検索バーに「Flutter Intl」
を入力します。「Flutter Intl」を選択し、インストールします。
図15.2: Flutter Intlの選択
画面の指示に従い、Android Studioを再起動します。
図15.3: 再起動

「pubspec.yaml」にパッケージを追加します。
リスト15.1: pubspec.yaml
dependencies:
flutter:
sdk: flutter

/*-- Add Start --*/


flutter_localizations:
sdk: flutter
/*-- Add End --*/
15.2 生成ファイルの確認
プ ロ ジ ェ ク ト フ ォ ル ダ ー に 「 lib/generated 」 フ ォ ル ダ ー 、
「lib/l10n」フォルダー,「lib/generated/intl」フォルダーが自動生成
されることを確認してください。
※フォルダーがない場合には、Android Studioのツールバーより
「Tools」→「Flutter Intl」→ 「Initialize for the Project」を実行して
ください。
図15.4: 生成ファイル
15.3 多言語化対応の設定
多言語化対応に必要な設定をします。
リスト15.2: main.dart

/*-- Add Start --*/


import 'package:flutter_localizations/flutter_localizations.dart';
import 'generated/l10n.dart';
/*-- Add End --*/
...

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
/*-- Add Start --*/
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('ja', ''),
const Locale('en', ''),
],
/*-- Add End --*/
title: 'かしかりメモ',
theme: ThemeData(
primarySwatch: Colors.blue,
),
...
},
);
}
}
...

コード内にあるSは、l10n.dartに定義されたインスタンスです。
・localizationsDelegates → 対応するクラスを設定します。
・supportedLocales → サポートする言語を設定します。
15.4 言語ファイルの準備
まずは、プロジェクトフォルダーにある「lib/l10n/intl_en.arb」を
開いて、次のコードを追記してみましょう。
リスト15.3: lib/l10n/intl_en.arb
{
"@@locale": "en",
"title":"kasikari-memo"
}

同様に、日本語変換用のファイルも作成します。
1.「l10n」フォルダーの上で右クリックし、「New」→「Arb
File」を選択します。
2.ファイル作成画面で「ja」と入力し、「OK」を選択します。
3.「lib/l10n/intl_ja.arb」に次の内容を追記します。
リスト15.4: lib/l10n/intl_ja.arb
{
"@@locale": "ja",
"title":"かしかりメモ"
}

これで、英語と日本語のタイトルを切り替える準備ができました。
15.5 言語ファイルの反映
先ほど作成した言語ファイルをアプリへ反映させるために、
「list.dart」を修正します。
リスト15.5: list.dart

/*-- Add Start --*/


import 'generated/l10n.dart';
/*-- Add End --*/

class _MyList extends State<List> {


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
/*-- Edit Start --*/
title: Text(S.of(context).title),
/*-- Edit End --*/
actions: <Widget>[
IconButton(
icon: Icon(Icons.exit_to_app),
onPressed: () {
print("login");
showBasicDialog(context);
},
)
],
),
...
)
}
}

S.of(context).titleこのように呼び出すことで、設定された言語でタ
イトルを表示できます。
他の箇所も同様に日本語、英語の言語ファイルに追記しますが、修
正範囲が大きいため、次のタグを参照してください。
https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapte
r15
変数を含む多言語対応
英語では「Welcome [ユーザー名]!」、日本語では「[ユーザー名]さん、ようこそ!」と、
言語によって表示する文言を変えたい場合、これから紹介する方法で簡単に対応できます。
1. 言語ファイルを変数を入れたい場所に$をつけて宣言します。
リスト15.6: strings_ja.arb
{
"welcome_user":"$user さん、ようこそ!",
}

リスト15.7: strings_en.arb
{
"welcome_user":"Welcome $user !",
}

2. 使用したい部分で次のコードを呼び出します。
リスト15.8: main.dart
S.of(context).welcome_user("Flutter"),

これで英語のときは「Welcome Flutter!」、日本語のときは「Flutterさん、ようこそ!」
と表示されます。
15.6 アプリ名の多言語化対応
アイコンに表示されるアプリ名について、多言語化対応します。
Android,iOSでそれぞれ設定ファイルを変更します。
15.6.1 Androidの対応
アンドロイド上でアプリ名を多言語対応させるために、
AndroidManifestを変更します。
android/app/src/main/AndroidManifest.xmlを開き、次の変更を加
えます。
リスト15.9: AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.kasikarimemo">
<application
android:name="io.flutter.app.FlutterApplication"
/*-- Edit Start --*/
android:label="@string/appTitle"
/*-- Edit End --*/
android:icon="@mipmap/ic_launcher">
...
</application>
</manifest>
android:labelは、アプリの名前として表示されます。デバイスの言
語設定に合わせてアプリの表示名を変更させるために、
android:label="@string/appTitle"とします。
android/app/src/main/res/values直下にstrings.xmlを作成し、次の
内容を記載します。
リスト15.10: strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="appTitle">Kasikari memo</string>
</resources>

android/app/src/main/res/直下にvalues-jaというフォルダーを作成
します。values-ja直下にstrings.xmlというファイルを作成し、次の内
容を記述します。
リスト15.11: strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="appTitle">かしかりメモ</string>
</resources>

これで準備は完了です。この状態でアプリをインストールし、OSの
設定で言語切り替えを試すと、次のようにアプリのタイトルが変更さ
れます。
図15.5: アプリタイトル変更
15.6.2 iOSの対応
プロジェクトのフォルダーに移動して次のコマンドを実行し、Xcode
で開きます。
$ open ios/Runner.xcworkspace
画面左からRunnerを選択し、Project配下にあるRunnerを選択。
Localizationsの「+」ボタンをクリックします。
図15.6: Localizationsの追加
「Japanese(ja)」を選択します。
図15.7: Japanese(ja)の選択

「Finish」をクリックします。
図15.8: 言語の設定
次の画像のように「Japanese」が追加されていることを確認しま
す。
図15.9: 追加されてることの確認

翻訳ファイルを追加します。Runner配下にファイルを追加します。
図15.10: 翻訳ファイルの追加1
「Strings File」をクリックし、「Next」をクリックします。
図15.11: 翻訳ファイルの追加2
ファイル名を「InfoPlist.strings」にして、「Create」をクリックし
ます。
図15.12: 翻訳ファイルの追加3

InfoPlist.stringsを選択肢、画面右にある、「Localize...」ボタンをク
リックします。
図15.13: 翻訳ファイルの追加4

「English」を選択して、「Localize」をクリックします。
図15.14: 翻訳ファイルの追加5

「Localization」の項目にある、「Japanese」にチェックを入れま
す。
図15.15: 翻訳ファイルの追加6

「InfoPlist.strings」配下に翻訳用のファイルが作成されます。
図15.16: 翻訳ファイルの追加7

それぞれのファイルに、次のように記載します。
リスト15.12: InfoPlist.strings(English)
CFBundleDisplayName = "kasikari-memo";
リスト15.13: InfoPlist.strings(Japanese)
CFBundleDisplayName = "かしかりメモ";

この状態でアプリをインストールし、OSの設定で言語を切り替える
と、Androidと同様にアプリ名が切り替わるので、試してみましょう!

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter15
第16章 アプリのアイコンを設定しよう
この章では、アプリのアイコンをオリジナルの画像に設定します。
通常、複数の解像度のアイコン画像を用意する必要があるのですが、
自動的に複数解像度の画像を作成してくれるプラグインを追加し、対
応します。
この章を完了すると、タグ「chapter161」の内容になります。
16.1 プラグインを追加する
アイコンを加工するFlutterLauncherIconsプラグイン2を追加しま
す。AndroidやiOSによって異なる画像を使用したり、背景色を指定で
きます。詳細は、プラグインのURLを参照してください。
リスト16.1: pubspec.yaml
dev_dependencies:
flutter_test:
sdk: flutter
/*---------- Add Start ----------*/
flutter_launcher_icons: 0.9.0

flutter_icons:
android: true
ios: true
remove_alpha_ios: true
image_path: "assets/icon.png"
/*----------- Add End -----------*/

image_pathで、アイコンの保存場所を指定します。また、今回は
Android、iOSの両方のアイコン画像を変更するため、android:trueと
ios:trueを指定します。
16.2 アプリのアイコンを配置する
先ほど設定したimage_pathに画像を配置します。具体的には、次の
画像を参考に画像を配置してください。
図16.1: アイコンの保存場所
16.3 アイコンの生成
画像を配置したら、次のコマンドを実行して複数の解像度のアイコ
ンを生成します。
flutter pub run flutter_launcher_icons:main
生成されたアイコンが正しく反映されているか確認します。
Androidの場合、android/app/src/main/resにあるフォルダーを選
択し、次のような画像を確認します。
図16.2: Androidのアイコン

iOSの場合、xcodeでkasikari_memo/iosのフォルダーを開きます。
Runner/Runner/Assets.xcassets/Applconを選択し、次の画像のよう
にアイコンが保存されていることを確認します。
図16.3: iOSのアイコン

アプリを実行し、アイコンが変更されていることを確認してみまし
ょう。

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter16
2. https://pub.dev/packages/flutter_launcher_icons
第17章 スプラッシュ画面を実装しよう
この章では、アプリ起動時に表示する、スプラッシュ画面を実装し
ます。
この章を完了すると、タグ「chapter171」の内容になります。
17.1 プラグインを追加する
スプラッシュ画面を実装するために、「pubspec.yaml」に設定を追
記します。Flutterでは画像ファイルや動画などのアセットファイル
は、「pubspec.yaml」であらかじめ定義する必要があります。詳しい
説明は、公式のドキュメント2を参照ください。
リスト17.1: pubspec.yaml
flutter:
uses-material-design: true
/*-- Add Start --*/
assets:
- assets/note.png
/*-- Add End --*/

スプラッシュ画面に使用する画像の保存場所を指定します。
17.2 画像の保存
先ほど設定したpathに画像を配置します。具体的には、次の画像を
参考に画像を配置してください。
図17.1: 画像保存場所
17.3 画像表示設定追加
スプラッシュ画面に先ほど保存した画像を表示させるために、コー
ドに変更を加えます。
リスト17.2: splash.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class Splash extends StatelessWidget{


@override
Widget build(BuildContext context) {
return Scaffold(
/*-- Add Start --*/
backgroundColor: Colors.blue,
/*-- Add End --*/
body: Center(
/*-- Edit Start --*/
child:
FractionallySizedBox(
child: Image.asset('assets/note.png'),
heightFactor: 0.4,
widthFactor: 0.4,
),
/*-- Edit End --*/
),
);
}
}
17.4 画像表示設定追加の解説
各プロパティーの設定は次のとおりです。
・backgroundColor → 画像の背景色を設定します。Colors.blueと
記載し、青色を設定します。
・FractionallySizedBox → childに設定したWidgetの大きさを動的
に変更できます。
・heightFactor → 高さを指定します。
・widthFactor: → 横幅を指定します。
それでは、アプリを実行してみましょう。次の画像のように、スプ
ラッシュ画面に設定した画像が表示されます。
図17.2: splash画面
スプラッシュ画面の画像が表示されないときは
実行している端末によっては、_mailLoginCheck()が早く終了します。それによ
り、画像が表示されません。次のように、Future.delayed()を使い、処理を遅延させ
てみましょう。
リスト17.3:
Future _mailLoginCheck() async {
if (userAuth.currentUser == null) {
await userAuth.signInAnonymously();
}
/*-- Add Start --*/
await Future.delayed(Duration(seconds: 1));
/*-- Add End --*/
}

1. https://github.com/chasibu/kasikari_memo_v2/releases/tag/chapter17
2. https://flutter.dev/docs/development/ui/assets-and-images
第18章 アプリをリリースしよう(Android
版)
この章では、アプリをリリースするための手順を解説します。
18.1 jksファイルの作成
キーストアを作成する方法は、GUIとCUIの2種類の方法がありま
す。どちらの方法も解説しますので、好きな方法で作成してみてくだ
さい。
18.1.1 GUIで署名する
1. 一度、プロジェクトのAndroidフォルダーを開き直す必要がある
ので、Android Studioの画面上部より「File > Open...」を選択しま
す。
2. 「kasikari_memo」フォルダーの直下にある「android」を選択
します。
図18.1: Androidフォルダーを選択
3. プロジェクトを新しく開いたら、「Build > Generate Signed
APK...」を選択します。
4. 「Create new...」を選択します。
5. 次のリストを参考に必要な情報を入力し、「OK」を選択します。
OKを押した段階でファイルが生成されます。
・Key store path: このPathにjksファイルが生成されます
・Password:, Confirm: KeyStoreのパスワードを設定
・[Key]
─Alias: 名前を設定
─Password:, Confirm: Keyのパスワードを設定
─Validity (years): 有効期限を設定
─[Certificate]
・First and Last Name: 名前
・Organizational Unit:グループ名、部署名
・Organization: 団体名、会社名
・City or Locality: 市町村名
・State or Province: 都道府県
・Country Code (XX): 国を
図18.2: キーストアの作成
6. Cancelを押し、閉じます。
図18.3: APKの作成
7. 指定したパスにjksファイルが生成されていることを確認します。
18.1.2 CUIで署名する
1. 次のコマンドを入力し、キーストアを作成します。
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -
validity 10000 \
-alias key
2. 次の質問項目が表示されるので、各項目を適宜入力してくださ
い。各項目は入力した後、エンターを押すと次の項目に進みます。
・パスワード
・パスワード再入力
・姓名
・組織単位
・都市名または地域名
・都道府県名または州名
図18.4: ユーザー情報の入力
3. 確認画面が表示されたら、“はい”と入力し、エンターを押しま
す。
4. jksファイルが生成されることを確認します。
18.2 キーストアの登録
1. android/key.propertiesというファイルを作成し、次の内容を記
載します。
リスト18.1: key.properties
storePassword=<password from previous step>
keyPassword=<password from previous step>
keyAlias=key
storeFile=<location of the key store file, e.g. /Users/<user name>/key.jks>

次の表を参照し、各項目に値を入力します。
表18.1: 各値の意味
フィールド 意味
storePassword キーストアのパスワード。登録したパスワードを入力してください。
keyPassword キーのパスワード。登録したパスワードを入力してください。
keyAlias エイリアスの値を入力します。
storeFile jksファイルの格納場所を記載します。
18.3 サインイン情報の追記
1. android/app/build.gradleを開き、次の内容を追記します。
リスト18.2: build.gradle
...

/*-- Add Start --*/


def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
/*-- Add End --*/

android {
compileSdkVersion 27

lintOptions {
disable 'InvalidPackage'
}

defaultConfig {
...
}
/*-- Add Start --*/
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
/*-- Add End --*/

buildTypes {
release {
/*-- Edit Start --*/
signingConfig signingConfigs.release
/*-- Edit End --*/
}
}
}
...
18.4 リリースAPKの作成
1. プロジェクトのパスに移動します。
2. 次のコマンドを入力します。
flutter build apk
3. 次の場所にAPKファイルが生成されていることを確認します。
build/app/outputs/apk/app-release.apk
18.5 APKの動作確認
1. 作成したAPKの動作確認のために、お手持ちのAndroidデバイス
にアプリをインストールします。パソコンにデバイスを接続します。
2. 次のコマンドを入力し、インストールをします。
flutter install
3. デバイス上で正しく、動作することを確認します。
18.6 Google Play Storeへの登録
本書では取り扱いません。公式のリファレンス1を参考に行ってく
ださい。

1. https://developer.android.com/distribute/best-practices/launch/
第19章 【参考】アプリをリリースしよう
(iOS版)
この章では、iOSアプリをリリースするための手順を解説します。こ
の章は、Apple Developer Program(年間11,800円)に登録をしない
と行うことができません。
Flutter公式サイト1の情報を和訳した内容をもとに、参考までに掲載
します。
19.1 Apple Developer Programへの登録
1.Apple Developer Program2にアクセスします。
2.「Enroll」と書かれたボタンを押し、画面の指示に従い登録しま
す。
19.2 iTunes Connectにアプリを登録
1.Developerページ3から「App IDs」ページを開きます。
2.「 + 」をクリックすると、新しいバンドルIDが作成されます。
3.アプリケーション名を入力し、IDを入力します。
4.アプリケーションで使用するサービスを選択し、「続行」をク
リックします。
5.「登録」をクリックしてバンドルIDを登録します。
6.iTunes Connect4を開き、「マイアプリ」をクリックします。
7.ページの左上にある「 + 」をクリックし、「新しいアプリ」を
選択します 。
8.表示されるフォームでアプリの詳細を入力します。「プラットフ
ォーム」セクションで、iOSがオンになっていることを確認しま
す。Flutterは現在tvOSをサポートしていないので、チェックボ
ックスをオフのままにし、「作成」をクリックします。
9.アプリのアプリケーションの詳細に移動し、サイドバーから
「アプリ情報」を選択します。
10.「一般情報」セクションで、先ほどのバンドルIDを選択しま
す。
19.3 Xcodeプロジェクトの設定を確認
1. Xcodeでアプリのiosフォルダーを開きます。
図19.1: プロジェクトの選択

2. 画面上部からXcodeプロジェクトナビゲーターでRunnerプロジェ
クトを選択します。
3. サイドバーでRunnerターゲットを選択します。
4. [General]タブを選択します。
5. 次の設定について確認します。
・IDセクション
─Display Name: ホーム画面や他の場所に表示されるアプリの名
前。
─Bundle Identifier: iTunes Connectに登録したApp ID。
・署名セクション
─Automatically manage signing: Xcodeがアプリの署名とプロビ
ジョニングを自動的に管理するかどうかの設定になります。
─Team: 登録したApple Developerアカウントに関連付けられてい
るチームを選択します。
19.4 ビルドアーカイブの作成
1. 次のコマンドを実行してリリースビルドを作成します。
flutter build ios
※ Xcodeがリリースモードの設定を更新するようにするには、Xcode
ワークスペースを閉じて再度開きます。Xcode 8.3以降では、この手順
は不要です。
2. Xcodeでアプリのiosフォルダーを開きます。
3. 「Product > Scheme > Runner」を選択します。チェックマークが
ついていることを確認してください。
図19.2: Runner選択

4. 「Product > Destination > Generic iOS Device」を選択します。


図19.3: iOS Device選択

5. 左のタブから「Runner」を選択し、TARGETSの「Runner」を選
択します。
6. 「Identity」セクションで、次の項目を確認します。
・バージョン:公開するユーザー向きのバージョン番号に更新しま
す。環境変数が入力されていることを確認してください。
・ビルド識別子:iTunes Connectでこのビルドを追跡するために使用
する一意のビルド番号に更新します。環境変数が入力されているこ
とを確認してください。各アップロードには固有のビルド番号が
必要です。
図19.4: Identityの確認
7. ビルドアーカイブを作成するために、「Product > Archive」を選
択します。
図19.5: Archiveの選択

8. 「Xcode Organizer」ウィンドウのサイドバーで、iOSアプリを選
択して、作成したビルドアーカイブを選択します。
9. 「Validate...」ボタンをクリックします。問題が報告された場合
は、その問題に対処し、別のビルドを作成します。アーカイブをアップ
ロードするまで、同じビルドIDを再利用できます。
10. アーカイブの検証が完了したら、「App Storeにアップロー
ド...」をクリックします。
以上でiOSのリリースは完了です。

1. https://flutter.dev/docs/deployment/ios
2. https://developer.apple.com/programs/
3. https://developer.apple.com/
4. https://itunesconnect.apple.com
おわりに
本書を通し、FlutterとFirebaseを触ってみて、いかがでしたでしょ
うか?ひととおりアプリを作成して、特性などがより深く理解できた
のではないでしょうか?
今後の拡張やより品質を高める項目として、次の項目があります。
興味があればぜひ試してみてください。
・GoogleやFacebookなどの他の認証でのログイン機能を実装する
・期限になったらPush通知する
・グループ編集機能を作成する
・エラーハンドリングを実装する
・テストを追加する
本書を読み進めていただき、ありがとうございました。
謝辞
本書のレビューを引き受けてくれた@wonda-tea-coffeeさん、あり
がとうございます。レビューのおかげでクオリティーが格段に上がり
ました。インプレスR&D様と山城さんのおかげで商業誌としてだけで
なく、第二版も出版させていただきました。大変感謝しております。
感想
最後まで、ご愛読いただきありがとうございました。
本書が、FlutterやFirebaseの理解の一助になりましたら幸いです。
このふたつの技術を取り扱った書物はまだ少なく、敷居が高くなって
いるのが現状です。本書をきっかけに、FlutterやFirebaseを使い始め
る人が一人でも増えると、とても嬉しいです。
下畑、わみ共に本業を行う中、時間を見つけ書き上げるのは大変で
した。なかなかうまく進捗しないこともありましたが、共同で執筆し
たことにより、多くの壁を乗り越えることができました。一人では乗
り越えられない壁も、協力することで乗り越えることができるものだ
と、改めて気づかせてもらいました。
最後になりますが、執筆にあたりご協力いただいた知人・友人に、
この場を借りてお礼申し上げます。
著者紹介
下畑 翔(しもはた しょう)
普段はIT系企業にてサーバ、ネットワークを中心としたインフラ系の基盤構築の仕事をしています。趣味でプ
ログラミング等を行なっており、その流れから、今回Flutterを勉強し、本を出版させていただきました。趣
味は海外でバックパッカー。
わみ
NefryというフリスクサイズのIoTデバイスのハードウエア&ソフトウエアの開発者です。普段はロボットの
アプリケーションやファームウェアの作成をしています。AndroidとC++をメインで書いています。今回の本
をきっかけにFlutterをはじめました。
◎本書スタッフ
アートディレクター/装丁:岡田章志+GY
編集協力:飯嶋玲子
デジタル編集:栗原 翔
〈表紙イラスト〉
高野 佑里(たかの ゆり)
嵐のごとくやって来た爆裂カンフーガール。本業はGraphicとWebのデザイナー。クライアントと一緒に作っ
ていくイラスト、デザインが得意。FirebaseやNetlifyなど人様のwebサービスを勝手に擬人化しがち。
Twitter:@mazenda_mojya
技術の泉シリーズ・刊行によせて
技術者の知見のアウトプットである技術同人誌は、急速に認知度を高めています。インプレスR&Dは国内最大級の即売会「技術書典」
(https://techbookfest.org/)で頒布された技術同人誌を底本とした商業書籍を2016年より刊行し、これらを中心とした『技術書典シリーズ』を
展開してきました。2019年4月、より幅広い技術同人誌を対象とし、最新の知見を発信するために『技術の泉シリーズ』へリニューアルしまし
た。今後は「技術書典」をはじめとした各種即売会や、勉強会・LT会などで頒布された技術同人誌を底本とした商業書籍を刊行し、技術同人誌の
普及と発展に貢献することを目指します。エンジニアの“知の結晶”である技術同人誌の世界に、より多くの方が触れていただくきっかけになれば
幸いです。
株式会社インプレスR&D
技術の泉シリーズ 編集長 山城 敬
●お断り
掲載したURLは2021年7月1日現在のものです。サイトの都合で変更されることがあります。また、電子版ではURLにハイパーリンクを設定してい
ますが、端末やビューアー、リンク先のファイルタイプによっては表示されないことがあります。あらかじめご了承ください。
●本書の内容についてのお問い合わせ先
株式会社インプレスR&D メール窓口
np-info@impress.co.jp
件名に「『本書名』問い合わせ係」と明記してお送りください。
電話やFAX、郵便でのご質問にはお答えできません。返信までには、しばらくお時間をいただく場合があります。
なお、本書の範囲を超えるご質問にはお答えしかねますので、あらかじめご了承ください。
また、本書の内容についてはNextPublishingオフィシャルWebサイトにて情報を公開しております。
https://nextpublishing.jp/
技術の泉シリーズ
Flutter×Firebaseで始める
モバイルアプリ開発
最新改訂版
2021年8月6日 初版発行Ver.1.0(リフロー版)
著 者 下畑 翔,わみ
編集人 山城 敬
企画・編集 合同会社技術の泉出版
発行人 井芹 昌信
発 行 株式会社インプレスR&D
〒101-0051
東京都千代田区神田神保町一丁目105番地
https://nextpublishing.jp/
◉本書は著作権法上の保護を受けています。本書の一部あるいは全部について株式会社インプレスR&Dから文書による許
諾を得ずに、いかなる方法においても無断で複写、複製することは禁じられています。
©2021 Sho Shimohata,Wami. All rights reserved.
ISBN978-4-8443-7998-0

You might also like