Flutterで簡単なアプリ開発を解説するよ その3

クロ【喜び】
Flutterのプログラム解説第3弾やっていきますよー
前回の解説が下手に感じたので今回はもっとうまくやります(汗

霊夢【普通】
今回で簡単なアプリの作成は完了になるわ
次回はFlutterでゲームを作ってみようと思っているわ

魔理沙【楽しい】
HTMLやCSSのようなネスト構造のUIを作っていくのは初めてだったので慣れるまでがちょっと大変だったな

妖夢【普通】
ということで、今回もFlutterの解説動画を交えながら要点を解説していこうかなと思います

解説動画もあるよ!

クロ【喜び】
大部分は動画で解説していますので、
こちらを見ていただけると嬉しいです!

霊夢【悲しみ】
今回、いつもより詳しく解説した関係で
内容自体はあまり進んでないわね

魔理沙【楽しい】
動画内でも言ってるけど、
このアプリ制作は前座のつもりだったから1回で終えるはずだったんだけどね

妖夢【普通】
初めて解説する内容が多くて
どうしても時間がかかってしまったというわけですね

Githubにプロジェクトをアップロードしてます!

Githubのプロジェクトリンク:https://github.com/Syungetu/text_editor

クロ【喜び】
解説とかはどうでもいい!
ソースを見せろ!といった方はGithubにプロジェクトファイルを上げてますのでご確認ください~

霊夢【悲しみ】
解説をするために作ったプロジェクトなのであまり難しいことしていないのでロジックに参考にできる物は少ないかもね

魔理沙【喜び】
このプロジェクトを改良して作るのも全然OKだから、気軽にみていってね

妖夢【普通】
このプロジェクトを利用した場合は「すたじお・くろす!」の明記をしていただけるとうれしいです

動画について補足的解説をします

クロ【普通】
今回は通知機能の実装メインなので、通知機能に関しての解説をしていこうと覆います

霊夢【普通】
このローカル通知だけど、Flutterのバージョンや使うパッケージの種類によって設定方法が違うから注意してね

魔理沙【普通】
で、Flutter1時代のローカル通知のパッケージが冬月の環境では動かせなかったので、できる限り解説とおなじパッケージを使うことをおすすめするぞ

妖夢【普通】
冬月さんの環境は「Flutter2.0」でローカル通知のパッケージは「Flutter Local Notifications」をつかいました

ローカル通知機能とは?

クロ【普通】
まずはローカル通知とはなんぞやって話からですね
スマホの通知っていっぱい合ってどれがどの通知なのか分かりづらいですよね

霊夢【普通】
ここで言うローカル通知はツイッターとかでフォロワーが増えた場合にスマホの上からニョキッと生える通知が有るわよね

魔理沙【普通】
あのニョキッと生える通知を今回は実装していく感じになります
今回は外部通信とか行わないのでローカル通知と言っているぞ

妖夢【普通】
この通知の使い方としては、サーバーから何らかの情報をスマホに送信してそれを受信するとアプリの起動の有無関係なく通知する様な感じですね

ローカル通知の実装

クロ【普通】
それでは早速ローカル通知を実装していきましょう!
難しそうな機能ではありますがパッケージが用意されているのでそこまで難しくありません

霊夢【悲しみ】
でも、初期化設定で色々必要なので、初期化設定をサボると動かないのでしっかりやっていくわよ

ローカル通知を行うための権限を追加

魔理沙【普通】
ローカル通知をこのまま実装しても動かないんだよね……
最近はスマホを勝手に動かすプログラムを作るのはNGなんだよね

妖夢【普通】
ローカル通知を行う前に、ローカル通知を行うことをユーザーさんに確認して許可を得る必要があります

クロ【普通】
スマホ待機中でもローカル通知は動かすことが出来るので、不正動作を防ぐためにもユーザーの許可が必要となります
Androidの場合

魔理沙【普通】
Androidの場合はマニフェストファイルに権限周りの設定を書く必要があるぞ
「【ルート】/android/app/src/main/AndroidManifest.xml」
に有るからここに書いていくぞ

 <!– 端末起動時の通知権限 –>
<uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED”/>
<!– 端末のバイブレーションの権限 –>
<uses-permission android:name=”android.permission.VIBRATE” />

妖夢【普通】
書く内容は上に書かれたものになります
これを<application ~~ の上の行(2行目)に追加すれば設定完了です
バイブレーションの権限は今回必要ないので書かなくても大丈夫です

iOSの場合

クロ【喜び】
iOS側も権限の設定は必要なので設定していきます
設定ファイルは「【ルート】/ios/Runner/AppDelegate.swift」になります

// ローカル通知権限
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)

霊夢【普通】
設定ファイルに「)=>boo{}」という記述があるので、{}の中に上のコードを書いていくわ

魔理沙【普通】
これで権限周りの設定は完了だ
次からはローカル通知の追加とソースコードの実装を行っていくぞ

ローカル通知のパッケージを追加

flutter pub add flutter_local_notifications

妖夢【普通】
パッケージの追加はコマンドで行います
上のコマンドをVisual Studio Codeのターミナルに入力します

クロ【普通】
前回も書きましたが、コマンドからパッケージをつかすると競合関係もある程度解決してくれるのでおすすめです

霊夢【普通】
基本的に競合することなく追加できると思うわ
これでパッケージが追加できたので、ローカル通知実装を行っていくわ

ローカル通知の初期化処理

魔理沙【普通】
今回は通知用のクラスを用意してそちらに実装していくぞ
クラス名は「LocalNotifications」としたよ

/// タイムゾーンを設定する
Future<void> initTimeZone() async {
    tz.initializeTimeZones();
    var tokyo = tz.getLocation(‘Asia/Tokyo’);
    tz.setLocalLocation(tokyo);
}

妖夢【普通】
このローカル通知は何秒後に通知を行う仕組みになっているので、時間の設定が必要になります
そのため、必ずタイムゾーンの設定が必要になります

クロ【普通】
本来は端末の設定されているタイムゾーンを取得するべきでは有るのですが、簡単に済ませるために日本の標準時間に決め打ちしています

// ローカル通知管理
static FlutterLocalNotificationsPlugin? flutterLocalNotificationsPlugin = null;

霊夢【普通】
またこのローカル通知を実装したクラスは複数生成すると誤作動の元になるので、ローカル通知の管理クラスとスタティック変数で宣言するわ

魔理沙【普通】
スタティック変数で宣言すると、その変数やクラスは一度宣言した後に宣言し直した後も双方とも同じモノとしてあつかうことができるぞ

妖夢【普通】
変数で言う参照渡しの状態になる感じですね
別の変数として定義しても中に代入されているデータは同じものとして扱えます

クロ【普通】
次にAndroidとiOSで別の処理が必要となるので、
個々の処理を書いていきます

Android側の初期化

// Android
AndroidInitializationSettings androidInitializationSettings =
AndroidInitializationSettings(iconImage);

霊夢【普通】
上のコードがAndroid専用の呼び出しクラスになるわね
必要なのはアイコン画像パス名ね

魔理沙【普通】
通知に表示されるアイコンを変更できるんだけど、
今回はアプリアイコンの「’@mipmap/ic_launcher’」を使うことにしたぞ

iOS側の初期化
// iOS
IOSInitializationSettings iOSInitializationSettings =
IOSInitializationSettings(
// バックグラウンドや終了時にローカル通知のバナーをクリックした際に動く処理
onDidReceiveLocalNotification: onDidReceiveLocalNotification,
);

妖夢【普通】
次にiOS側の設定になります
iOS側はローカル通知のカードをタップした時のに動かしたい関数を設定します

クロ【普通】
クリックしたときにメインページを開く処理を実装すればローカル通知をタップするとアプリを開くことができます

/// アプリがバックグラウンドや終了時に通知バナーがタップされたときに呼ばれる
Future onDidReceiveLocalNotification(
int id, String? title, String? body, String? payload) async {}

霊夢【普通】
今回はタップしても何も動かさないようにしたいので、
設定する関数は空にしているわ

魔理沙【普通】
どのローカル通知をタップしたかどうか関数の引数に入ってくるよ
idが任意に設定したidが入ってくるぞ
各端末の初期化データを纏める
// 端末ごとの通知設定を登録する
InitializationSettings initializationSettings = InitializationSettings(
    fandroid: androidInitializationSettings,
    iOS: iOSInitializationSettings,
);
flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin!.initialize(
    initializationSettings,
    onSelectNotification: onSelectNotification,
);

妖夢【普通】
各端末の設定が終わったらそれをまとめる処理を書きます
InitializationSettings()に先程設定したクラスを登録します

クロ【普通】
次にローカル通知のメインのflutterLocalNotificationsPluginクラスを生成します
生成できたら、先程設定したInitializationSettingsクラスとローカル通知をクリックした場合の処理を書いたクラスを設定します

 /// 通知バナーをクリックした際に呼ばれる
Future<void> onSelectNotification(String? payload) async {}

霊夢【悲しみ】
前もiOS側でロケール通知をクリックした時の処理を登録したけど、ここでも登録するんだよね

魔理沙【普通】
正直違いがわからないからとりあえず別で設定してるけど、このタップした時の処理の違いってなんだろうね

通知処理の作成

妖夢【普通】
それでは実際にローカル通知を呼び出す関数を作っていきます
初期化時に設定した内容もここで行うものもあります

/// 通知を設定する
Future<bool> SetLocalNotification(
String title,
String body,
DateTime dayTime,
{String channelID = “TextEditorLocalNotification”,
String ChannelName = “TextEditor_SpecifiedNotification”,
String icon = “@mipmap/ic_launcher”}) async {

クロ【普通】
まずはローカル通知を呼びだす関数の引数を解説します
引数に入れるのは主にローカル通知に表示したい文字列と表示したい時刻になります

    String title,  …… ローカル通知のタイトル文字列
    String body,…… ローカル通知の内容文字列
    DateTime dayTime,…… ローカル通知の表示時刻
    {String channelID = “TextEditorLocalNotification”,…… ローカル通知の識別ID
    String ChannelName = “TextEditor_SpecifiedNotification”,…… ローカル通知の識別文字列
    String icon = “@mipmap/ic_launcher”}…… ローカル通知のアイコン画像パス

霊夢【普通】
引数に入るデータは上の用な感じになるわね
ここで肝となるのはchannelID(ローカル通知の識別ID)ChannelName(ローカル通知の識別文字列)かな

魔理沙【普通】
channelID(ローカル通知の識別ID)とはローカル通知ごとに振り分ける識別で、通知することに別の文字列(数字)を指定するぞ

妖夢【普通】
別のIDを指定することで、タップして呼び出したときに、どのローカル通知をタップしたのかをこのIDで判断することができます

クロ【普通】
次にChannelName(ローカル通知の識別文字列)はローカル通知の種類を振り分けるもので、channelIDと似てますが、こちらは設定側の仕分けになります

霊夢【普通】
Twitterでいうと、フォロワーが増えた時、DMが来た時などでグループ分けする感じね

魔理沙【普通】
で、AndroidやiOS側でこの通知種類ごとに出すか出さないかの設定ができるから、その設定でもこのChannelName(ローカル通知の識別文字列)は使うね

// 通知の時刻設定
tz.TZDateTime scheduledDate = tz.TZDateTime(
    tz.local,
    dayTime.year,
    dayTime.month,
    dayTime.day,
    dayTime.hour,
    dayTime.minute,
);

妖夢【普通】
次に時間を設定します
Flutterの基本的な時刻設定はDateTimeなのですが、タイムゾーンが絡んでくるので、「timezone/data/latest」で変換する必要があります

// 通知の詳細な設定
NotificationDetails notificationDetails = NotificationDetails(
    // Android側
    android: AndroidNotificationDetails(
        channelID, // 通知のID
        ChannelName, // 通知のチャンネル名(例:定期通知、不特定通知を分けるなど
        channelDescription: ‘memo_notification_des’, // 通知の詳細
        icon: icon,
    ),
    // iOS側
    iOS: IOSNotificationDetails(),
);

クロ【楽しい】
そして先程受け取った引数の値や、初期化で作成したクラスをNotificationDetailsに入れます

await flutterLocalNotificationsPlugin!.zonedSchedule(
    1, // IDは通知ごとに同じIDを使う
    title, // タイトル
    body, // 内容
    scheduledDate,// 通知の時刻設定
    notificationDetails,// 通知の詳細な設定
    androidAllowWhileIdle: true,
    uiLocalNotificationDateInterpretation:
    UILocalNotificationDateInterpretation.absoluteTime);

霊夢【普通】
これが最後の設定よ
通知作成に時間がかかるのでflutterLocalNotificationsPlugin!.zonedScheduleにはawaitで生成されるまで待つようにするわ

魔理沙【普通】
こちらにローカル通知のタイトル文や内容文を入力するぞ
後は通知時間もこちらに入力するよ

妖夢【普通】
あとは初期化に設定したnotificationDetailsを入れれは設定完了です
androidAllowWhileIdleuiLocalNotificationDateInterpretationは上記のままで大丈夫です

クロ【普通】
というか、これらの引数の内容がよくわかってなかったりします(汗

ローカル通知を呼び出す

// ローカル通知の初期化
LocalNotifications localNotifications = new LocalNotifications();

霊夢【普通】
作ったローカル通知のクラスを実際に使ってみるわね
まずは初期化ですが、コンストラクタに書いたので、newするだけでOKよ

魔理沙【普通】
staticクラスとして作ったので、ローカル通知はスタート画面などアプリの最初の方で生成してね

// 通知設定
bool isCompleted =
    await localNotifications.SetLocalNotification(
        titleTextEditingController.text,
        contentsTextEditingController.text,
        selectDateTime!);
if (isCompleted == true) {
    Fluttertoast.showToast(msg: “通知の設定に成功しました!”);
} else {
    Fluttertoast.showToast(msg: “通知の設定に失敗しました……”);
}

妖夢【普通】
生成したらSetLocalNotification()関数を呼び出してローカル通知を呼び出します

クロ【喜び】
これで、時間が来たら通知が上から現れます
お疲れ様でっした、これで完成です!

霊夢【喜び】
権限設定とかプログラム以外で設定することがあってめんどくさいけど、構造お自体は難しくないから意外と簡単に実装できるわよ

魔理沙【喜び】
あとiOSの場合はAppleDeveloperConsole側でも設定がいるから、リリース時にはもうちょっと設定することが有るんだよねぇ

妖夢【普通】
このあたりの解説はAppleへのリリースすることを解説することがあったら解説したいと思います

クロ【喜び】
今回はこのへんで、
次回はFlutterでゲーム作成に挑戦してみますー

気軽にコメントをどうぞ!

この記事に関すること冬月に聞きたいこと等、小さいことでもコメントしていただける嬉しいです。
冬月に直接連絡したい方は下のお問合せフォームをお使いください。(メール送信されます)

内容に問題なければ、下記の「コメントを送信する」ボタンを押してください。
※メールアドレスは公開されることは有りません。

プログラム解説の関連記事