Chapter 14

おまけ. awesome_notificationでローカル通知をだそう

antman
antman
2021.12.06に更新

utilを担当するディレクトリを作る

  • util
    • notify
      • notify.dart
    • ui
      • alert.dart
        それぞれの役割は以下の通りです
  1. notify.dart
    • 通知を作成する関数を記述します。
  2. alert.dart
    • 設定で通知がオンになっていない場合、通知をオンにするようにalertを出します。

notify.dart

import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:(アプリ名)/model/db/todo_db.dart';
import 'package:flutter/material.dart';
import 'package:(アプリ名)/model/sp/notify_setting_sp.dart';

void initial_notyfy() => AwesomeNotifications().initialize(
      // set the icon to null if you want to use the default app icon
      null,
      [
        NotificationChannel(
            channelGroupKey: 'Todo_channel_group',
            channelKey: 'Todo_channel',
            channelName: 'Todo notifications',
            channelDescription: 'Notification channel for Todo',
            defaultColor: const Color(0xFF9D50DD),
            ledColor: Colors.white),
        // Channel groups are only visual and are not required
      ],
      channelGroups: [
        NotificationChannelGroup(
            channelGroupkey: 'Todo_channel_group',
            channelGroupName: 'Todo group')
      ],
      debug: true,
    );

void make_notify(int id, String title, String body, DateTime date) =>
    AwesomeNotifications().createNotification(
      content: NotificationContent(
          id: id, channelKey: 'Todo_channel', title: title, body: body),
      schedule: NotificationCalendar.fromDate(date: date),
    );

void make_notify_all() async {
  final db = MyDatabase();
  final items = await db.readAllTodoData();
  int decrease = await getNotifySetting();

  for (var item in items) {
    if (item.limitDate != null && item.isNotify) {
      make_notify(item.id, item.title, item.description,
          item.limitDate!.add(Duration(hours: -decrease)));
    }
  }
}

void start_listen(BuildContext context) => AwesomeNotifications()
    .actionStream
    .listen((ReceivedNotification receivedNotification) {});

initial_notyfyでは通知を行うための初期化処理を行います。
make_notify_allではdbからすべてのデータを読み込み、期日がnullでないもののすべてからローカル通知を作成します。
start_listenでは、通知を開いた際に、行う処理を記述できます。今回は何も記述していません。

alert.dart

import 'package:flutter/material.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:awesome_notifications/awesome_notifications.dart';

void showAlert(BuildContext context) {
  AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
    if (!isAllowed) {
      Alert(
        context: context,
        type: AlertType.warning,
        title: "通知の設定",
        desc: "通知を有効にしてください",
        buttons: [
          DialogButton(
            child: const Text(
              "OK",
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
            onPressed: () {
              AwesomeNotifications().requestPermissionToSendNotifications();
              Navigator.pop(context);
            },
            width: 120,
          )
        ],
      ).show();
    }
  });
}

設定を有効にするよう促すアラートです。

main.dartに追記

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:(アプリ名)/view/root.dart';
import 'package:(アプリ名)/util/notify/notify.dart';

void main() {
  initial_notyfy();//追加
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends HookConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    start_listen(context);//追加
    return MaterialApp(
      home: Root(),
    );
  }
}

root.dartに追記

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:(アプリ名)/view/todo/todo_view.dart';
import 'package:(アプリ名)/view/timeline/timeline_view.dart';
import 'package:(アプリ名)/view/settings/settings_view.dart';
import 'package:(アプリ名)/util/ui/alert.dart';//追加

class Root extends HookConsumerWidget {
  const Root({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    showAlert(context);//追加
    List<String> keys = ["Home", "TimeLine", "Settings"];
    List<Icon> icons = [
      Icon(Icons.home),
      Icon(Icons.timeline),
      Icon(Icons.settings),
    ];
    List<Widget> pages = [Todo(), TimeLine(), Settings()];
    final page_state = useState(0);
    List<BottomNavigationBarItem> navi_items = [];
    for (int i = 0; i < keys.length; i++) {
      navi_items.add(BottomNavigationBarItem(
        icon: icons[i],
        label: keys[i],
      ));
    }
    return Scaffold(
      appBar: AppBar(
        title: Text(keys[page_state.value]),
      ),
      body: pages[page_state.value],
      bottomNavigationBar: BottomNavigationBar(
        items: navi_items,
        currentIndex: page_state.value,
        onTap: (index) {
          page_state.value = index;
        },
      ),
    );
  }
}

todo_view.dartに追記

import 'package:(アプリ名)/model/db/todo_db.dart';
import 'package:(アプリ名)/model/freezed/todo_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
import 'package:(アプリ名)/view_model/todo/todo_provider.dart';
import 'package:(アプリ名)/util/notify/notify.dart';

class Todo extends HookConsumerWidget {
  const Todo({Key? key}) : super(key: key);

  List<Widget> _buildTodoList(
      List<TodoItemData> todoItemList, TodoDatabaseNotifier db) {
    List<Widget> list = [];
    for (TodoItemData item in todoItemList) {
      Widget tile = Slidable(
        child: ListTile(
          title: Text(item.title),
          subtitle:
              Text(item.limitDate == null ? "" : item.limitDate.toString()),
        ),
        endActionPane: ActionPane(
          //スライドしたときに表示されるボタン
          motion: DrawerMotion(),
          //スライドしたときのアニメーション
          children: [
            SlidableAction(
              flex: 1,
              //長さ
              onPressed: (_) {
                //押された時の処理
                db.deleteData(item);
              },
              icon: Icons.delete,
              //アイコン
            ),
            SlidableAction(
              flex: 1,
              onPressed: (_) {
                TodoItemData data = TodoItemData(
                  id: item.id,
                  title: item.title,
                  description: item.description,
                  limitDate: item.limitDate,
                  isNotify: !item.isNotify,
                );
                db.updateData(data);
              },
              icon: item.isNotify
                  ? Icons.notifications_off
                  : Icons.notifications_active,
            ),
          ],
        ),
      );
      list.add(tile);
      //listにtileを追加
    }
    return list;
  }

  
  Widget build(BuildContext context, WidgetRef ref) {
    final todoState = ref.watch(todoDatabaseProvider);
    //Providerの状態が変化したさいに再ビルドします
    final todoProvider = ref.watch(todoDatabaseProvider.notifier);
    //Providerのメソッドや値を取得します
    //bottomsheetが閉じた際に再ビルドするために使用します。
    List<TodoItemData> todoItems = todoProvider.state.todoItems;
    //Providerが保持しているtodoItemsを取得します。
    TempTodoItemData temp = TempTodoItemData();

    useEffect(() {
      make_notify_all();
    }, [todoItems]);//追加

    List<Widget> tiles = _buildTodoList(todoItems, todoProvider);
    return Scaffold(
      body: ListView(children: tiles),
      //ListViewでtilesを表示します。
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () async {
          await showModalBottomSheet<void>(
            context: context,
            useRootNavigator: true,
            isScrollControlled: true,
            builder: (context2) {
              return HookConsumer(
                builder: (context3, ref, _) {
                  final limit = useState<DateTime?>(null);
                  //DatePickerが閉じた際に再ビルドするために使用します。
                  return Padding(
                    padding: MediaQuery.of(context3).viewInsets,
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[
                        TextField(
                          decoration: InputDecoration(
                            labelText: 'タスク名',
                          ),
                          onChanged: (value) {
                            temp = temp.copyWith(title: value);
                          },
                          onSubmitted: (value) {
                            temp = temp.copyWith(title: value);
                          },
                        ),
                        TextField(
                          decoration: InputDecoration(
                            labelText: '説明',
                          ),
                          onChanged: (value) {
                            temp = temp.copyWith(description: value);
                          },
                          onSubmitted: (value) {
                            temp = temp.copyWith(description: value);
                          },
                        ),
                        Table(
                          defaultVerticalAlignment:
                              TableCellVerticalAlignment.values[0],
                          children: [
                            TableRow(
                              children: [
                                ElevatedButton(
                                  onPressed: () {
                                    DatePicker.showDateTimePicker(
                                      context,
                                      showTitleActions: true,
                                      minTime: DateTime.now(),
                                      onConfirm: (date) {
                                        limit.value = date;
                                        temp = temp.copyWith(limit: date);
                                      },
                                      currentTime: DateTime.now(),
                                      locale: LocaleType.jp,
                                    );
                                  },
                                  child: Row(
                                    children: [
                                      Icon(Icons.calendar_today),
                                      Text(limit.value == null
                                          ? ""
                                          : limit.value
                                              .toString()
                                              .substring(0, 10)),
                                    ],
                                  ),
                                ),
                                Container(
                                  width: 10,
                                ),
                                ElevatedButton(
                                  onPressed: () {
                                    todoProvider.writeData(temp);
                                    Navigator.pop(context3);
                                  },
                                  child: Text('OK'),
                                ),
                              ],
                            ),
                          ],
                        ),
                      ],
                    ),
                  );
                },
              );
            },
          );
          temp = TempTodoItemData();
        },
      ),
    );
  }
}

まとめ

以上ですべての作業は終わりです。
お疲れ様でした。