-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
[flutter_local_notifications] linux support #888
Conversation
Thanks a lot for the PR! I'll take a look and get try to get help from the community to test it out as well |
Hey @akemimadoka are there any specific testing instructions? I used the example in your forked PR repo and tried to run it: |
Hi @daniel-vera-g This error is caused by a low version glib(the reported error suggests that the missing function requires version 2.58), I'll test the minimum required version of glib, thanks for your testing! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice stuff! I can see notifications popping up after creating a .desktop file. 👍
I wonder if one could possibly use a CMake rule to place a .desktop file into ~/.local/share/applications
. It would make the example work out of the box, and serve as a starting point for apps that want to use the package on Linux. The downside is that it kind of pollutes user's .local/share, though.
P.S. There might be something wrong with "Schedule notification to appear in 5 seconds". The notification doesn't show up, and a couple of assertion failures get printed:
(flutter_local_notifications_example:508344): GLib-GIO-CRITICAL **: 23:25:59.245: g_application_send_notification: assertion 'G_IS_NOTIFICATION (notification)' failed
(flutter_local_notifications_example:508344): GLib-GObject-CRITICAL **: 23:25:59.245: g_object_unref: assertion 'G_IS_OBJECT (object)' failed
GValue id = G_VALUE_INIT; | ||
g_object_get_property(G_OBJECT(getApplication()), "application-id", &id); | ||
std::string ret = g_value_get_string(&id); | ||
g_value_unset(&id); | ||
return ret; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could use g_application_get_application_id()
Map<int, Map<String, LinuxNotificationButtonHandler>> _notificationButtonHandlerMap; | ||
Map<int, Map<String, LinuxNotificationButtonHandler>> _periodicalNotificationButtonHandlerMap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these could be final
if they were initialized here instead of initialize()
@@ -660,3 +663,139 @@ class MacOSFlutterLocalNotificationsPlugin | |||
} | |||
} | |||
} | |||
|
|||
class LinuxFlutterLocalNotificationsPlugin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are some analysis warnings here and there in LinuxFlutterLocalNotificationsPlugin
that could be easily solved 😉
export 'src/platform_specifics/linux/enums.dart'; | ||
export 'src/platform_specifics/linux/icon.dart'; | ||
export 'src/platform_specifics/linux/initialization_settings.dart'; | ||
export 'src/platform_specifics/linux/method_channel_mappers.dart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"method_channel_mappers" sounds like an internal helper. is it intentionally exported?
@@ -16,6 +18,8 @@ class InitializationSettings { | |||
/// Settings for iOS. | |||
final IOSInitializationSettings iOS; | |||
|
|||
final LinuxInitializationSettings linux; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add the same one-liner doc as the others around, to avoid affecting pub score?
@@ -16,6 +18,8 @@ class NotificationDetails { | |||
/// Notification details for iOS. | |||
final IOSNotificationDetails iOS; | |||
|
|||
final LinuxNotificationDetails linux; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here :)
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); | ||
} | ||
|
||
FlMethodResponse* show(FlMethodCall* call) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is probably the whole plugin's most important function, but it's a bit hard to follow because it's so long and does many things. would it be better to try to split it up into smaller chunks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops I pressed the wrong button, sorry!
@akemimadoka FYI I asked @jpnurmi to help review since he's being doing a lot of work for the Linux implementations for the Flutter plus plugins and I'm rusty when it comes to C/C++. If you could take a look at his review comments then that would be great :) |
@@ -23,7 +23,8 @@ class LinuxFileIcon implements LinuxIcon { | |||
} | |||
|
|||
/// Represents an icon from binary data | |||
/// The binary data should be in an image file format which supported by glib, such as common formats like jpg and png | |||
/// The binary data should be in an image file format which supported by glib, | |||
/// such as common formats like jpg and png | |||
class BytesLinuxIcon implements LinuxIcon { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reads slight awkward. Could this perhaps be renamed to something else like ByteDataLinuxIcon
? Would also make it similar to https://api.flutter.dev/flutter/dart-typed_data/ByteData-class.html
@akemimadoka there's a |
I'll need to setup a VM to be able to give this a spin but FYI that I'll be merging a PR soon (#907) to provide more info on the guidelines around contribution to make reviews easier going forward. This includes info on API documentation, which follows the guidelines from here. I mention this as some changes in the PR will be needed e.g. to follow this rule. This PR will also turn on the rule around public member API documentation. Will let you know when it's merged, after which you can update your fork so that the analyser can highlight the issues it finds. |
You should be able to update your fork to pull in the latest changes now |
Sorry for the delay, I made some progress on getting a Ubuntu VM via Parallels on my MacBook but ran into issues with getting even the basic counter app running. A window appears but won't show the elements that it should and I'm seeing a lot |
This is just a guess, but perhaps either enabling 3D hardware acceleration for the VM or running Flutter in software rendering mode helps? |
Ok looks like turning off 3D acceleration setting in Parallels helped. Thanks :) |
Sorry another newbie question 😅
Another thing is rather than having the app fail to build due to APIs not being available because of the use of older versions of GLib, is it possible to put a guard in the code around this (e.g. via a macro) to check that a minimum version of GLib is installed? If it's possible, then I would suggest to throw an exception that could be caught on the Flutter side when app calls an API from the plugin that needs a newer version of GLib and include details of the minimum version in the exception. Would be good to also to add info in the docs on how to update glib too as it sounds like this could be a more common issue. |
} | ||
|
||
extension LinuxNotificationDetailsMapper on LinuxNotificationDetails { | ||
Map<String, Object> toMap() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
analysis is picking up that this should use =>
here
|
||
/// Label of this button. | ||
final String label; | ||
/// Identifier of this button, will be passed to callback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you run flutter format .
from the root of the plugin? Regular formatting would add empty lines between properties and running the code does seem to show that some files would be changes as a result. Note that if happen to be using VS Code that you can turn format on save on, which is an option that is off by default
return gtk_window_get_application(GTK_WINDOW(getTopLevel())); | ||
} | ||
|
||
[[maybe_unused]] std::string getApplicationId() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this method be here? Haven't seen any reference to it
Hi @MaikuB It seems on Ubuntu 18 latest version of glib on the official apt source is 2.56, lower than 2.58 which some time zone API requires. I think it could be solved by loading some symbols dynamically, and so we can show a user-friendly prompt. |
@akemimadoka Is it possible to use an |
@MaikuB Yes, it's possible, but if we check the version at runtime, we can guarantee that the product can be run on a different environment from the building one. |
@akemimadoka What I'm proposing is that a runtime check still occurs. The goal is to still allow developers to still use the plugin even if they're using older versions of GLib as they may not care for being schedule a notification, in which case, the code shouldn't fail to compile because the plugin makes use of APIs in newer versions of GLib. This is where the use of something like an #if directive comes in. If they're calling Edit: in terms of code the C++ implementation for
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left some additional comments based on what I can understand from the code whilst I'm still sorting out my Linux dev environment. Main areas are around show
on the C++ side handling multiple ways of scheduling notification and questions on what happens if the app is restarted as it appears that things like cancelling all notifications would fail if the app had restarted
for (final LinuxNotificationButton button in notificationDetails.buttons) | ||
button.buttonId : button.handler, | ||
} : null; | ||
return _channel.invokeMethod('show', <String, Object> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be the only method that specifies show
as the method being called when calling invokeMethod
. This makes it easier to match the code on the Dart side and would make it less confusing for readers of the code as convention within the ecosystem has been that the string matches the name of the method. Currently other methods are also passing show
.
It also prevents the show method on the C++ side from being responsible for triggering notifications in multiple ways as is the case right now. This makes it more risky to change code as a common method is being used for the various plugin APIs. I get why it was done as I did a similar thing in the past but that caused issues with maintenance. Making the change should also resolve the comment @jpnurmi raised on how the implementation of show
in C++ was rather long
if (_notificationButtonHandlerMap.remove(id) != null) { | ||
return _channel.invokeMethod('cancel', id); | ||
} else if (_periodicNotificationButtonHandlerMap.remove(id) != null) { | ||
return _channel.invokeMethod('cancelPeriodicNotification', id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call _channel.invokeMethod('cancel', ...)
for similar reasons I mentioned about how _channel.invokeMethod( 'show', ...)
was called in multiple places. An additional parameter could be passed to indicate the handler should be cancelled on the Dart side.
|
||
@override | ||
Future<void> cancelAll() async { | ||
for (final int id in _notificationButtonHandlerMap.keys) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the user triggers a notification that is displayed, leaves the notification there and restarts the app? If I'm not mistaken, looks as though cancelling notifications and handling buttons would rely on having the same app session active. FYI that cancelling notifications and handling when the user taps on a notification (note: other platforms don't support action buttons at the moment) for all other platforms can be done even if the user restarted the app so if what I described is indeed how it works right now (as I'm still having some trouble with my Linux environment to test) then a solution will need to be found for Linux to behave the same. I imagine, this would potentially need to rely on persisting some information about the notification
@MaikuB If we check the version at build time and a user uses it in an environment where the version of glib doesn't meet the requirement, it will fail at runtime with a not user-friendly prompt like "symbol not defined". Both solutions are not hard to implement. If the situation which I described above is not too serious, I will implement it with macros.😸 |
FYI I still have issues running the app. The furthest I got was to remove code that makes use of timezone APIs and then I was able to run the app but couldn't get a notification, which I did by running the Another thing is with the recent changes I've seen, it looks like information about notification ids only remain in memory and that scheduling notifications only work if the app is still running. This is what I'd expect to be able to do with around cancelling notifications and scheduling notifications
From what I can tell from this PR, neither of these are supported. Please correct me if I'm wrong |
To implement these features, some limitations should be noted:
|
Yep that is I could see from the API docs. This is why I tried to ask about storing the ids. I imagine it should be possible for applications to store data that it needs. The Linux
I'm not that familiar with Linux so I'm not sure what you refer to here around portability. From a user's perspective, they shouldn't have to keep their application and device running to see a scheduled notification. This would also achieve feature parity with how other platforms work as those platforms provide ways for scheduled notifications to work regardless of the application's state. I doubt it'll be of much use if the notification can only be shown when the application is running e.g. using notifications for a periodic reminder |
class NotificationIdPersister extends LinuxNotificationNotifier { | ||
@override | ||
void onNewNotificationCreated(int notificationId) { | ||
SharedPreferences.getInstance().then((SharedPreferences value) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, I wasn't suggesting to use the shared preferences plugin. In fact it should be avoided to minimise dependencies where possible. What I was referring is to look at where they save the info and have the plugin do the same. I would imagine these two lines of Dart code
final processName = path.basenameWithoutExtension(
await File('/proc/self/exe').resolveSymbolicLinks());
final directory = Directory(path.join(xdg.dataHome.path, processName));
have an equivalent in C++
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, so I'm only using it in the example program, users can choose their favorite method to persist the data.
In the plugin, we are only notifying the user by LinuxNotificationNotifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was reviewing on my phone and thought the only changes would've been to the plugin so missed that this was for the example app. Note though what I was also referring to is having the plugin manage all of this. The core reason for this is goes to why a cross-platform library/SDK is typically chosen as it allows feature parity to achieved with minimal effort In other words, allowing users to get their apps and running quicker. Having the plugin manage it removes additional burden from the user. If how data is stored is that much of a concern, then that tends to be when the data needs to be stored securely and would likely require a custom implementation.
Another issue is that the plugin is now relying the on user to provide a collection of notification ids. This makes it a bit awkward as if a user is picking a library to manage notifications, I dare say the vast majority of users would be expecting the plugin to do so on its own. Having to require users to pass this info means the user becomes the source of truth (note: they could pass erroneous values) when it should really be the plugin. I also just noticed that the method for pending notification requests isn't implemented and doing so would most likely require the plugin to save the notification details anyway. If the user needed to persist this data via the LinuxNotificationNotifier
and then feed it back to the plugin for the API to get the pending notification requests to work, this makes it even more awkward.
If some of what I've raised so far including being able to have scheduled notifications occur even when the app isn't running becomes too much an effort to do, feel free to close the PR. Reason being is I imagine you're doing this on your spare time so can understand if you decide to drop working on this. Though in the case of handling scheduled notifications, perhaps one way is to simply mark those methods as not being implemented. Not sure how many applications would find it useful to be able to display a notification immediately though
cc @jpnurmi: if you happen to have time, would appreciate your thoughts on what I've raised here and in the thread so far but can understand if you're not able to do so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @MaikuB, sorry I've been a bit busy lately, but I'll try to catch up with what's been going on here later.
Closing this as changes were requested in a number of places (e.g. how scheduling notification works and having the notification ids saved to storage by the plugin itself) but haven't been actioned for a while. Feel free to re-open for further discussion |
fetch information from notification which activates the application(not applicable due to limitations described in readme)