Reputation: 41
My App does the following: It runs a background Task using Flutter Workmanager which checks some values and then it throws a Notification via Flutter Local Notification. In the initialize method from FlutterLocalNotifications Plugin, i can specify a inline fuction, which should navigate to a page. Since i dont have a Builder context, i must use a Navigator Key with OnGenerateRoute to forward the user to a site. However, this doesn`t work and i don´t know why. I know that this code is useful when the app gotkilled.
Example Code
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
String initialRoute = HomePage.routeName;
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
But what to do when the app is still alive? My Project code is listed below.
Main.Dart
void main() {
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
Workmanager().registerPeriodicTask("1", "test",frequency: Duration(minutes: 15));
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: "/",
navigatorKey: NavigationService.navigatorKey,
onGenerateRoute: RouteGenerator.generateRoute
);
}
}
RouteGenerator.dart
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch(settings.name) {
case '/first':
return MaterialPageRoute(builder: (_) => Page1(title: "First"));
case '/second':
return MaterialPageRoute(builder: (_) => Page2(title: "Second"));
case '/third':
return MaterialPageRoute(builder: (_) => Page3(title: "Third"));
case '/fourth':
return MaterialPageRoute(builder: (_) => Page4(title: "Fourth"));
}
return MaterialPageRoute(builder: (_) => Page0(title: "Root!"));
}
}
class NavigationService {
static final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
static Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!.pushNamed(routeName);
}
}
service.dart
class DevHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
}
}
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async{
HttpOverrides.global = new DevHttpOverrides();
var url = 'https://172.16.0.100/handler.php?page=settings';
http.Response response = await http.get(Uri.parse(url));
List<dynamic> list = jsonDecode(response.body);
SharedPreferences prefs = await SharedPreferences.getInstance();
var usage = "Beides";
var checkValue = "temp_out";
var borderValueString = "14.9";
var checktype = "Grenzwert überschreiten";
var borderValueDouble;
var message = "";
if(usage != "Nur Home Widgets" && checkValue != "" && borderValueString != "" && checktype != "")
{
var value = list[0][checkValue];
if (double.tryParse(borderValueString) != null && double.tryParse(value) != null)
{
borderValueDouble = double.parse(borderValueString);
value = double.parse(value);
}
if (checktype == "Grenzwert unterschreiten")
{
if (borderValueDouble is double)
{
if (value <= borderValueDouble)
{
message = "Grenzwert unterschritten";
}
}
}
else if (checktype == "Grenzwert überschreiten")
{
if (borderValueDouble is double)
{
if (value >= borderValueDouble)
{
message = "Grenzwert überschritten";
}
}
}
else if (checktype == "Entspricht Grenzwert")
{
if (borderValueDouble == value)
{
message = "Grenzwert erreicht";
}
}
}
if(message != "")
{
FlutterLocalNotificationsPlugin flip = new FlutterLocalNotificationsPlugin();
var android = new AndroidInitializationSettings('@mipmap/ic_launcher');
var ios = new IOSInitializationSettings();
var settings = new InitializationSettings(android: android, iOS: ios);
flip.initialize(settings, onSelectNotification: (String? payload) async {
await NavigationService.navigatorKey.currentState!.push(MaterialPageRoute(builder: (context) => Page4(title: "Hello")));
});
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
'1',
'weatherstation',
'Notify when values change',
importance: Importance.max,
priority: Priority.high
);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics);
await flip.show(0, message,
'App öffnen für weitere Details',
platformChannelSpecifics, payload: 'Default_Sound'
);
}
return Future.value(true);
});
}
Upvotes: 2
Views: 2326
Reputation: 41
I solved it by reacting to two different events.
If the App starts, i check if the app was launched by a notification. This can be done in createState or initState in main.dart. This code is useful for that.
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
String initialRoute = HomePage.routeName;
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
If the app is in background and a notification launches the app again, you must use Widgets Binding Observer and react to the App Resume event. There is an article at Medium which has example code for this case. Have a look at it here.
There is one drawback when reacting to App Resume Event and using the Flutter Local notifications Plugin. The aforementioned code always delivers true once triggered by an notification, even if the app entered background state again and was resumed manually by an user. This means code for changing a page will always be called, even if you did not click an notification. Therefore, I´m using a boolean variable to trigger the App State Resume code once. Obviously, if you enter the app via notification ,the app gets resumed and you get a second notification, the code for changing a page will not be executed. It´s a workaround, but for my case, it´s good enough.
Upvotes: 2
Reputation: 11
Did you find any solution for it? I'm also working on an Android application that update user data in firestore database (at interval of 15min) and send user its notification (both task happens in background using flutter workmanager_plugin). When user taps on the notification he should be navigated to the route which shows latest data from the database.
First 2 background tasks are happening successfully but when the notification is clicked nothing is happening. I'm also using GlobalKey key to get MaterialApp widget's context, so that routing Route can be pushed.
It seems like onSelectNotification property for FlutterLocalNotificationsPlugin.initialize() method don't work for workmanager plugin. I have also added a print statement inside it, but nothing get displayed in console. I thought maybe my Globalkey has some fault but when I tried it for navigating pages in non background task it was happening succesfully, similarly onSelectNotification was working perfectely for non-workmanager task.
void callbackDispatcher() {
Workmanager().executeTask((task, data) async {
if(task=='showNotification'){
FlutterLocalNotificationsPlugin notifPlugin =
FlutterLocalNotificationsPlugin();
NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
'main_channel',
'Main Channel',
'Main Notification Channel',
importance: Importance.max,
priority: Priority.high,
),
);
await notifPlugin.initialize(
InitializationSettings(
android: AndroidInitializationSettings('ic_launcher')),
onSelectNotification: (String? payload) async {
print('Inside on select Route Navigator. Route= $payload');
switch (payload!) {
case 'Home':
// navigatorKey is GlobalKey
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => Home()));
break;
case 'Auth':
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => Auth()));
break;
case 'Details':
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => UserDetails()));
break;
}
});
await notifPlugin.show(id, title, body, notificationDetails, payload:payload);
}
return Future.value(true);
}
}
When notification is clicked. Following message should print on console: "Inside on select Route Navigator. Route= Details" and he should be navigated on the UserDetails page but nothing seems to be happening.
Upvotes: 1