Home > Mobile >  Creating multiple Flutter nofitications with flutter_local_notification
Creating multiple Flutter nofitications with flutter_local_notification

Time:11-16

Currently I'm using a loading page to avoid having a big lag when the user is being redirected to the home page while the notifications are being created on the same thread (the UI thread which leads to lots of dropped frames). I tried using the compute dart function but the issue is that this function requires using static methods and you can't pass it objects. So I would appreciate some hints on how to use a thread to create the notifications. PS: in the worst scenario the app is creating 7*24 notifications(24 for each day of the week) which is slow even on high end devices.

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter/material.dart';
import '../pages/home_page/home_page.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'data.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:auto_size_text/auto_size_text.dart';

class NotificationLoading extends StatefulWidget {
  const NotificationLoading({Key? key}) : super(key: key);

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

class _NotificationLoadingState extends State<NotificationLoading> {
  @override
  void initState() {
    super.initState();
    manageNotifications();
  }

  Future<void> manageNotifications() async {
    await Future.delayed(
      const Duration(seconds: 1),
    ); // Let time to build the widget
    await Notifications(ctx: context).manageNotifications();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: const [
            CircularProgressIndicator(),
            Padding(
              padding: EdgeInsets.fromLTRB(0, 15, 0, 0),
              child: AutoSizeText(
                "Loading notifs",
                style: TextStyle(fontSize: 30),
              ),
            )
          ],
        ),
      ),
    );
  }
}

class Notifications {
  static const channelId = "coolID";
  static const channelName = "cool";
  Data data = Data();
  int id = 0;
  BuildContext ctx;
  Notifications({required this.ctx});
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  // check if notifications are already setup, if not setup notifications
  // otherwise notifications only need to be changed inside the timer_page
  Future<void> manageNotifications() async {
    final prefs = await SharedPreferences.getInstance();
    bool isNotificationSetup = prefs.getBool('isNotificationSetup') ?? false;
    if (!isNotificationSetup) {
      await _initialization();
      await _scheduleNotifications();
      await prefs.setBool('isNotificationSetup', true);
      Navigator.pop(ctx);
      await Navigator.push(
        ctx,
        MaterialPageRoute<void>(builder: (context) => const HomePage()),
      );
    }
  }

  Future<void> _initialization() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');
    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
    );
    await flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: _selectNotification);
  }

  // Schedule notifications based on user settings
  Future<void> _scheduleNotifications() async {
    // Init the time zone, needed for notification scheduling
    tz.initializeTimeZones();
    final String? timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
    tz.setLocalLocation(tz.getLocation(timeZoneName!));
    await data.getData();
    int delta = (data.endTime.minute   data.endTime.hour * 60) -
        (data.startTime.minute   data.startTime.hour * 60);
    double interval = delta / data.reminderNumber;
    data.checkedDays.forEach((day, values) {
      if (values[1]) {
        double minute = data.startTime.minute   (data.startTime.hour * 60);
        for (int reminder = 0; reminder < data.reminderNumber; reminder  ) {
          int tmpHour = (minute - minute % 60) ~/ 60;
          int tmpMinute = (minute.round()) % 60;
          _createScheduledNotification(
              _nextInstanceOfDayHourMinute(tmpHour, tmpMinute, values[0]), id);
          minute  = interval;
          id  ;
        }
      }
    });
  }

  // Create a scheduled notification
  void _createScheduledNotification(tz.TZDateTime time, int id) async {
    await flutterLocalNotificationsPlugin.zonedSchedule(
        id,
        AppLocalizations.of(ctx)!.notificationTitle,
        AppLocalizations.of(ctx)!.notificationMessage,
        time,
        const NotificationDetails(
          android: AndroidNotificationDetails(
            'weekly notification channel id',
            'New citation message',
            channelDescription:
                'Notifications for new citations configured in the timer page.',
            sound: RawResourceAndroidNotificationSound('notification_sound'),
            groupKey: "meditation invitation",
          ),
        ),
        androidAllowWhileIdle: true,
        uiLocalNotificationDateInterpretation:
            UILocalNotificationDateInterpretation.absoluteTime,
        matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime);
  }

  // Find next instance DateTime object
  tz.TZDateTime _nextInstanceOfHourMinute(int hour, int minute) {
    final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
    tz.TZDateTime scheduledDate =
        tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minute);
    if (scheduledDate.isBefore(now)) {
      scheduledDate = scheduledDate.add(const Duration(days: 1));
    }
    return scheduledDate;
  }

  // Find next instance DateTime object
  tz.TZDateTime _nextInstanceOfDayHourMinute(int hour, int minute, int day) {
    tz.TZDateTime scheduledDate = _nextInstanceOfHourMinute(hour, minute);
    while (scheduledDate.weekday != day) {
      scheduledDate = scheduledDate.add(const Duration(days: 1));
    }
    return scheduledDate;
  }

  // triggered function when the user tap on a notification
  void _selectNotification(String? payload) async {
    if (payload != null) {
      debugPrint('notification payload: $payload');
    }
    await Navigator.push(
      ctx,
      MaterialPageRoute<void>(builder: (context) => const HomePage()),
    );
  }
}

CodePudding user response:

You can't handle too many notifications at time in iOS platform. Instead of that you need again set or schedule notifications after few old notification received otherwise old notifications overlaps & it will not schedule.

You can check this link & my answer I hope it will help you.

Dart/Flutter is single threaded and not possible multi threading. As each isolate has its own memory,space and everything. To make it work like multi threaded you have to use isolates and the communication will be used through ports by sending message to one another. If you not want to use Future you can use isolates.

Read

https://medium.com/flutter-community/flutter-threading-5c3a7b0c065f

https://www.tutorialspoint.com/dart_programming/dart_programming_concurrency.htm

https://pub.dev/packages/threading

So you can use Future Or isolate.

CodePudding user response:

Great François. You already in a good path. I guess i could give you some hints to accomplish what you want.

  1. First of all Flutter require top level functions to run isolates so you have to put it outside of a class.
  2. To transfer data to an isolate it must be serialized. In my example i use jsonEncode to send it as a string and parse it with jsonDecode to retrieve as a dynamic list inside the isolate runner.
  3. When i wrote this code i read about some limitations of the use of plugins inside isolate (I don`t know about the current state). So i found a solution for this using Flutter Isolate plugin (https://pub.dev/packages/isolate_handler)
  4. I use the function killScheduleNotifications as a way to control when the code is running and avoid to create duplications. I always cancel all the scheduled and recreate it every time.

For reference (https://gist.github.com/taciomedeiros/50472cf94c742befba720853e9d598b6)


     final IsolateHandler isolateHandler = IsolateHandler();
     void scheduleNotificationsIsolate(String _reminders) async {
      await new Future.delayed(new Duration(milliseconds: 500));
      // ... (describe settings)
      flutterLocalNotificationsPlugin.initialize(
          settings,
          onSelectNotification: onSelectNotification,
      );
      await flutterLocalNotificationsPlugin.cancelAll();
      List<dynamic> _remindersParsed = jsonDecode(_reminders);
     
      for (// iterate over your entities to show the message) {
        int generatedId = id ?? random.nextInt(1000000000);
    
        await flutterLocalNotificationsPlugin.schedule(
          generatedId,
          title,
          message,
          scheduledNotificationDateTime,
          platformSpecifics,
          payload: payload,
        );
      }
      killCurrentScheduleNotifications();
    }
    
    startScheduleNotifications(String _remindersAsString) {
      killCurrentScheduleNotifications();
      isolateHandler.spawn<String>(
        entryPoint,
        name: "scheduleNotifications",
        onReceive: scheduleNotificationsIsolate,
        onInitialized: () => isolateHandler.send(
          _remindersAsString,
          to: "scheduleNotifications",
        ),
      );
    }
    
    void killCurrentScheduleNotifications() {
      if (isolateHandler.isolates.containsKey('scheduleNotifications'))
        isolateHandler.kill('scheduleNotifications');
    }
    
    void entryPoint(Map<String, dynamic> context) {
      final messenger = HandledIsolate.initialize(context);
      messenger.listen((message) {
        messenger.send(message);
      });
    }
  • Related