Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues regarding Actions #1786

Closed
ebelevics opened this issue Nov 14, 2022 · 2 comments
Closed

Issues regarding Actions #1786

ebelevics opened this issue Nov 14, 2022 · 2 comments

Comments

@ebelevics
Copy link
Contributor

Describe the bug
The issue was detected during #1773 issue resolving. Main goal I want to achieve is that when App is terminated on Action press it will do some action. If App is open or hidden it will on Action execute so function but also update UI.

Test results

Platform App is Open App is Hidden App is Terminated
Android Foreground (showsUserInterface: true) Fires immediately onDidReceiveNotificationResponse Fires immediately onDidReceiveNotificationResponse Opens App but doesn't fire anything after
Android Background (showsUserInterface: false) Fires immediately onDidReceiveBackgroundNotificationResponse Fires immediately onDidReceiveBackgroundNotificationResponse Fires onDidReceiveBackgroundNotificationResponse
iOS haven't tested yet

Here are some issues regarding to test results:

  • While showsUserInterface: false does work fine, I can't to find a way how to update UI if App is open or hidden. It makes sense as it's running on separate isolate. And so far I don't how or is it possible to connect background isolate with UI. Maybe with flutter_isolate it is possible, but so far I haven't used isolates in direct way.

  • When showsUserInterface: true, App seems to work fine if App is Open or Hidden, but it's Terminated while it does launch App on click it doesn't seem to do anything after launch. awesome_notifications seems to store events to fired but I don't know if this strategy is applicable here.

  • During Tests I found that after running App for first time on Debug and Terminating App active notification just disappears and doesn't work. I does work if I open App from Terminated state, but seems weird why this is the case. On Profile mode it seems to work all fine. I don't know is that a known issue.

I tried to be as clear as possible. And below dropped the minimal example on closer to my use case where I try to emulate timer.
// chronometerCountDown: true, // not yet merged is my added code to implement https://developer.android.com/reference/android/app/Notification.Builder#setChronometerCountDown(boolean), but it shouldn't differ so much from current plugin version. Implementation is given in #1778

Sample code to reproduce the problem

class NotificationServiceTest {
  static final localNotifications = FlutterLocalNotificationsPlugin();

  late final alarmNotifications = AlarmNotifications(localNotifications);

  Future<void> init() async {
    await _configureLocalTimeZone();
    const initSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
    const initializationSettings = InitializationSettings(android: initSettingsAndroid);

    await localNotifications.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: _onForegroundNotificationTap,
      onDidReceiveBackgroundNotificationResponse: _onBackgroundNotificationTap,
    );

    await alarmNotifications.schedule();
    await alarmNotifications.showTimer();
  }

  Future<void> _configureLocalTimeZone() async {
    tz.initializeTimeZones();
    final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
    tz.setLocalLocation(tz.getLocation(timeZoneName));
  }

  static void _onForegroundNotificationTap(NotificationResponse notificationResponse) async {
    print("FOREGROUND");
    print(notificationResponse.actionId);

    // VibrationUtils.timerVibrate();
  }

  @pragma('vm:entry-point')
  static void _onBackgroundNotificationTap(NotificationResponse notificationResponse) async {
    print("BACKGROUND");
    print(notificationResponse.actionId);

    // VibrationUtils.alarmVibrate();
  }
}

class AlarmNotifications {
  final FlutterLocalNotificationsPlugin localNotifications;
  AlarmNotifications(this.localNotifications);

  Future<void> schedule() async {
    await localNotifications.zonedSchedule(
      0,
      "title",
      "desc",
      tz.TZDateTime.from(DateTime.now().add(Duration(seconds: 10)), tz.local),
      _NotifChannels.alarmChannel(),
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
    );
  }

  Future<void> showTimer() async {
    await localNotifications.show(
      1,
      'Timer',
      'Timer is running',
      _NotifChannels.timerChannel(DateTime.now().add(Duration(seconds: 10))),
    );
  }
}

class _NotifChannels {
  static const _alarmChannelKey = "alarmChannelKey";
  static const _timerChannelKey = "timerChannelKey";

  static NotificationDetails alarmChannel() => const NotificationDetails(
        android: AndroidNotificationDetails(
          _alarmChannelKey,
          'Alarm channel',
          importance: Importance.max,
          priority: Priority.max,
          actions: [AndroidNotificationAction("do_something", 'Do task', showsUserInterface: true)],
          // sound: const RawResourceAndroidNotificationSound('notification'),
          // vibrationPattern: _alarmVibrationPattern,
          fullScreenIntent: true,
          visibility: NotificationVisibility.public,
          audioAttributesUsage: AudioAttributesUsage.alarm,
        ),
        // iOS: const DarwinNotificationDetails(),
      );

  static timerChannel(DateTime endAt) => NotificationDetails(
        android: AndroidNotificationDetails(
          _timerChannelKey,
          'Timer channel',
          priority: Priority.max,
          playSound: false,
          enableVibration: false,
          channelShowBadge: false,
          // actions: [AndroidNotificationAction("do_something", 'Do task', showsUserInterface: true)],
          visibility: NotificationVisibility.public,
          when: endAt.millisecondsSinceEpoch,
          timeoutAfter: endAt.difference(DateTime.now()).inMilliseconds,
          usesChronometer: true,
          // chronometerCountDown: true, // not yet merged
        ),
        // iOS: const DarwinNotificationDetails(),
      );
}
@MaikuB
Copy link
Owner

MaikuB commented Nov 19, 2022

You aren't meant to use the callbacks for when the app is killed. There's a method specific to scenarios when a notification launches an app https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications#getting-details-on-if-the-app-was-launched-via-a-notification-created-by-this-plugin. This would've shown in the example app too so you may need to spend a bit more time cross-referencing it in case there are scenarios that you've missed

@ebelevics
Copy link
Contributor Author

I did manage to get it work with help off onDidReceiveBackgroundNotificationResponse and handling UI with help of isolate, ReceivePort and SendPort.

On app initiation I open up a channel with ReceivePort:

 final receivePort = ReceivePort();
 IsolateNameServer.registerPortWithName(receivePort.sendPort, _backgroundChannelKey);
 backgroundMessagePort = receivePort.asBroadcastStream();

And on onDidReceiveBackgroundNotificationResponse function I send it by using:

final sendPort = IsolateNameServer.lookupPortByName(_backgroundChannelKey);
sendPort?.send(notificationResponse);

And then from UI part I just listen to backgroundMessagePort and react according to pressed notification action.

About final NotificationAppLaunchDetails notificationAppLaunchDetails =await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); have no idea how I missed that part. Most likely because I jumped on Notification Actions # straight away and reread this one section multiple times, but couldn't find any info there.

So basically the last issue is the 3rd point, but it's minor to me, as the functionality will work as expected in release version.

So I'm closing this issue. Thank you @MaikuB for help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants