Flutter アプリ

【Flutter】通知機能を実装する方法

通知機能は多くのアプリで使われている機能です。本記事では、特定の時刻になるとメッセージを通知する機能の実装方法を説明します。

本記事の想定読者

  • Flutterでアプリを開発している人

実装方法

実装方法は以下の通りです。

パッケージをインストール

flutter_local_notification、timezoneそしてshared_preferencesのパッケージをインストールします。flutter_local_notificationは、ユーザーにメッセージを通知するパッケージですが、timezoneを追加する事で、特定の時刻に通知する事が可能になります。shared_preferencesは通知の有効/無効の設定を保存する為にインストールします。

YAML
# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
# (...)
 flutter_local_notifications: ^16.1.0
 timezone: ^0.9.2
 shared_preferences: ^2.2.2
Bash
flutter pub get

android/app/build.gradleの編集

android/app/build.gradleに以下の行を追記します。

Dart
// android/app/build.gradle
android {
    compileSdkVersion 33
    ndkVersion flutter.ndkVersion
    defaultConfig {
        multiDexEnabled true // added
    }
    compileOptions {
        // Flag to enable support for the new language APIs
        coreLibraryDesugaringEnabled true  // added
        sourceCompatibility JavaVersion.VERSION_1_8  // added
        targetCompatibility JavaVersion.VERSION_1_8  // added
    }
// (...)
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'  // added
}

通知の許可をリクエストする

アプリを初回起動した時に通知の許可をリクエストするよう実装します。

Dart
// lib/main.dart
class _MyAppState extends State<MyApp> {
 
 void initState() { 
    _requestPermissions(); // added 
}
// added below
  Future<void> _requestPermissions() async {
    if (Platform.isIOS || Platform.isMacOS) {
      await flutterLocalNotificationsPlugin
          .resolvePlatformSpecificImplementation<
              IOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(
            alert: true,
            badge: true,
            sound: true,
          );
      await flutterLocalNotificationsPlugin
          .resolvePlatformSpecificImplementation<
              MacOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(
            alert: true,
            badge: true,
            sound: true,
          );
    } else if (Platform.isAndroid) {
      final AndroidFlutterLocalNotificationsPlugin? androidImplementation =
          flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
              AndroidFlutterLocalNotificationsPlugin>();
      final bool? grantedNotificationPermission =
          await androidImplementation?.requestNotificationsPermission();
      setState(() {
        _notificationsEnabled = grantedNotificationPermission ?? false;
      });
    }
  }

通知のクラスを作成する

次に通知するためのクラスを作成します。各メソッドはスタティックメソッドで実装します。クラスのインスタンス化は不要で、直接メソッドにアクセスする事ができます。

Dart
// lib/model/local_notifications.dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

class LocalNotifications {
  static final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  static final onClickNotification = BehaviorSubject<String>();

// on tap on any notification
  static void onNotificationTap(NotificationResponse notificationResponse) {
    onClickNotification.add(notificationResponse.payload!);
  }

// the function to notify the favorite meigen every 8am.  
 static tz.TZDateTime _nextInstanceOf8AM() {
    tz.initializeTimeZones();
    tz.setLocalLocation(
      tz.getLocation('Asia/Tokyo'),  // very important to add the timezone
    );

    final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
    tz.TZDateTime scheduledDate =
        tz.TZDateTime(tz.local, now.year, now.month, now.day, 8, 00);
    if (scheduledDate.isBefore(now)) {
      scheduledDate = scheduledDate.add(const Duration(days: 1));
    }
    return scheduledDate;
  }

  // initialize the local_notification
  static Future init() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    final DarwinInitializationSettings initializationSettingsDarwin =
        DarwinInitializationSettings(
      onDidReceiveLocalNotification: (id, title, body, payload) => null,
    );
    final LinuxInitializationSettings initializationSettingsLinux =
        LinuxInitializationSettings(defaultActionName: 'Open notification');
    final InitializationSettings initializationSettings =
        InitializationSettings(
            android: initializationSettingsAndroid,
            iOS: initializationSettingsDarwin,
            linux: initializationSettingsLinux);
    _flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onDidReceiveNotificationResponse: onNotificationTap,
        onDidReceiveBackgroundNotificationResponse: onNotificationTap);
  }

  // the method to notify at the scheduled date and time
  static Future showScheduleNotification({
    required String title,
    required String body,
    required String payload,
  }) async {
    tz.initializeTimeZones();
    await _flutterLocalNotificationsPlugin.zonedSchedule(
        2,
        title,
        body,
        // tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
        _nextInstanceOf8AM(),
        const NotificationDetails(
            android: AndroidNotificationDetails(
                'channel 3', 'your channel name',
                channelDescription: 'daily scheduled notification',
                importance: Importance.max,
                priority: Priority.high,
                ticker: 'ticker')),
        androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
        matchDateTimeComponents: DateTimeComponents.time,
        uiLocalNotificationDateInterpretation:
            UILocalNotificationDateInterpretation.absoluteTime,
        payload: payload);
  }
  // close all the notifications available
  static Future cancelAll() async {
    await _flutterLocalNotificationsPlugin.cancelAll();
  }
}

タイムゾーンの設定は不可欠です。タイムゾーンの設定がないと通知する事はできません。

Dart
    tz.setLocalLocation(
      tz.getLocation('Asia/Tokyo'),  // very important to add the timezone
    );

クラスを実装する

作成したLocalNotificationsをmain.dartに実装します。Switchウィジェットを使い、trueであればLocalNotifications.showScheduleNotificationを実行し、falseであればLocalNotifications.cancelAll()を実行します。

Dart
 // lib/main.dart
 return Scaffold(
(中略)
      body: Consumer<Favorites>(
        builder: (context, value2, child) {
          return Container(
            padding: EdgeInsets.only(left: 20),
            child: Row(
            children: [
              Text("毎日8時にお気に入りの名言を通知する"),
              Switch(
                value: switchValue,
                onChanged: (value) {
                  setState(() {
                    switchValue = value;
                    saveSwitchState(value);
                    if (value) {
                      LocalNotifications.showScheduleNotification(
                            title: "今日の名言",
                            body: value2.randomFav.message.toString(),
                            payload: "This is schedule data"
                      );
                    } else {
                      LocalNotifications.cancelAll();
                    }
                  }
                );
                },
              ),
            ],),
          );}
      ),
    );

以上が通知機能を実装する方法になります。まだ通知のアイコンなど足りていない箇所ありますが、追々アップデートしようと思います。

開発環境

Bash
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel stable, 3.13.5, on Microsoft Windows [Version 10.0.19045.3448], locale ja-JP)
[] Windows Version (Installed version of Windows is version 10 or higher)
[] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
[] Chrome - develop for the web
[] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.1.4)
[] Android Studio (version 2021.1)
[!] Android Studio (version 4.1)
    X Unable to determine bundled Java version.
[!] Android Studio (version 4.2)
    X Unable to determine bundled Java version.
[] VS Code (version 1.82.3)
[] Connected device (3 available)
[] Network resources

-Flutter, アプリ