Search code examples
flutterflutter-local-notification

Flutter flutter_local_notifications when clicked on notification, open specific page


So in my flutter app, when the user clicks a notification, it should take them to a specific page. However, it currently doesn't and with Navigator.push wont work since its outside of the Widget build.

This is my main.dart file:

import 'dart:async';
import 'dart:io';

// import 'package:fetch_client/fetch_client.dart'
// import 'package:fetch_client/fetch_client.dart';
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
// import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
// import 'package:ticket_wallet/pages/login_page.dart';

import 'package:pocketbase/pocketbase.dart';
// import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
import 'dart:developer';
// import 'dart:io' show Platform;
// import 'package:flutter/foundation.dart' show TargetPlatform;
import 'package:flutter/foundation.dart' show kIsWeb, kReleaseMode;
import 'package:ticket_wallet/data/ticket.dart';
import 'package:ticket_wallet/extensions/locale_ext.dart';
import 'package:ticket_wallet/l10n/en_NL_intl.dart';
import 'package:ticket_wallet/logger/tw_logger.dart';
import 'package:ticket_wallet/pages/login_page.dart';
// import 'package:url_launcher/url_launcher.dart';
import 'package:ticket_wallet/fetchClient/custom_platform.dart';
import 'package:ticket_wallet/pages/ticket_view_page.dart';

import 'pages/ticket_wallet_app.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'package:flutter_timezone/flutter_timezone.dart';

late PocketBase pb;
bool online = false;
TwLogger logger = TwLogger(level: Level.trace);
Locale? currentLanguage;
Ticket? notifTicket = null;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await _configureLocalTimeZone();
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  final NotificationAppLaunchDetails? notificationAppLaunchDetails =
      await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();

  log('notification app launch details: $notificationAppLaunchDetails');
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('tw_logo');
  final DarwinInitializationSettings initializationSettingsDarwin =
      DarwinInitializationSettings(
          onDidReceiveLocalNotification: onDidReceiveLocalNotification());
  final LinuxInitializationSettings initializationSettingsLinux =
      LinuxInitializationSettings(defaultActionName: 'Open notification');
  final InitializationSettings initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsDarwin,
      macOS: initializationSettingsDarwin,
      linux: initializationSettingsLinux);
  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onDidReceiveNotificationResponse:
          (NotificationResponse notificationResponse) async {
    log('notification response: ${notificationResponse.payload}');
    String ticketID = notificationResponse.payload!;
    var tempPb = await _notificationPb();
    log('userid: ${tempPb.authStore.model.id}');
    RecordModel ticketRM = await tempPb.collection("ticket").getOne(ticketID);
    Ticket ticket = Ticket.fromRecord(ticketRM);

    // Push the TicketWalletApp onto the navigation stack and remove all previous routes
    notifTicket = ticket;
    log('notifTicket: $notifTicket');
  });
  runApp(const App());
}

Future onDidReceiveNotificationResponse(
    NotificationResponse notificationResponse) async {
  log('notification response: ${notificationResponse.payload}');
  String ticketID = notificationResponse.payload!;
  var tempPb = await _notificationPb();
  log('userid: ${tempPb.authStore.model.id}');
  RecordModel ticketRM = await tempPb.collection("ticket").getOne(ticketID);
  Ticket ticket = Ticket.fromRecord(ticketRM);

  // Push the TicketWalletApp onto the navigation stack and remove all previous routes
  notifTicket = ticket;
  log('notifTicket: $notifTicket');
}

onDidReceiveLocalNotification() {}

Future<void> _configureLocalTimeZone() async {
  if (kIsWeb || Platform.isLinux) {
    return;
  }
  tz.initializeTimeZones();
  final String? timeZoneName = await FlutterTimezone.getLocalTimezone();
  tz.setLocalLocation(tz.getLocation(timeZoneName!));
}

Future<PocketBase> _notificationPb() async {
  final prefs = await SharedPreferences.getInstance();
  final connectivityResult = await (Connectivity().checkConnectivity());

  log('connectivity: $connectivityResult');

  final store = AsyncAuthStore(
    save: (String data) async => prefs.setString('auth', data),
    initial: prefs.getString('auth'),
  );
  var url = 'https://api.ticketwallet.eu';

  var fetchClientFactory = FetchClientFactory();
  log("http client factory: $fetchClientFactory");
  log("create: ${fetchClientFactory.create()}");
  var tempPb = PocketBase(
    url,
    authStore: store,
    httpClientFactory: kIsWeb ? () => fetchClientFactory.create() : null,
  );

  return tempPb;
}

class App extends StatefulWidget {
  const App({super.key});

  @override
  State<App> createState() => _AppState();

  static _AppState of(BuildContext context) {
    return context.findAncestorStateOfType<_AppState>()!;
  }
}

class _AppState extends State<App> {
  final ValueNotifier<Locale> localeNotifier =
      ValueNotifier(const Locale('en', 'US'));

  // final router = GoRouter(
  //   routes: [
  //     GoRoute(
  //       path: '/add_ticket_link',
  //       builder: (context, state) => Scaffold(
  //         appBar: AppBar(
  //           title: const Text("test"),
  //         ),
  //       ),
  //     ),
  //   ],
  // );

  void setLocale(Locale locale) {
    logger.i('New Language: ${locale.toLanguageTag()}');
    currentLanguage = locale;
    localeNotifier.value = locale;
  }

  @override
  void initState() {
    super.initState();
  }

  Future<PocketBase> _pocketbaseSetup(BuildContext context) async {
    // final test = await ZxinQrScanner().startQrScanner();
    // log('test native code: $test');
    final prefs = await SharedPreferences.getInstance();
    final connectivityResult = await (Connectivity().checkConnectivity());

    log('connectivity: $connectivityResult');

    final store = AsyncAuthStore(
      save: (String data) async => prefs.setString('auth', data),
      initial: prefs.getString('auth'),
    );
    var url = 'https://api.ticketwallet.eu';
    // ignore: use_build_context_synchronously
    // var platform = Theme.of(context).platform;
    // if (platform == TargetPlatform.android) {
    //   log('android');
    //   url = 'http://10.0.2.2:8090';
    // } else {
    //   log('other');
    //   url = 'http://127.0.0.1:8090';
    // }
    var fetchClientFactory = FetchClientFactory();
    log("http client factory: $fetchClientFactory");
    log("create: ${fetchClientFactory.create()}");
    var tempPb = PocketBase(
      url,
      authStore: store,
      httpClientFactory: kIsWeb ? () => fetchClientFactory.create() : null,
    );

    log("tempP b: $tempPb");
    try {
      await tempPb.health.check().catchError((e) {
        logger.e("Health check failed: $e");
        throw e;
      });
      online = true;
    } catch (e) {
      if (connectivityResult == ConnectivityResult.none) {
        online = false;
        WidgetsBinding.instance.addPostFrameCallback(
          (timeStamp) {
            if (!online) {
              _showPopup(context, 'No Internet Connection',
                  'You can still view your tickets, but you need an internet connection to add new tickets, view archived tickets or update your account information.\n\nIf you are reconnected, please restart the app to enable these features.');
            }
          },
        );
      } else {
        online = false;
        WidgetsBinding.instance.addPostFrameCallback(
          (timeStamp) {
            if (!online) {
              _showPopup(context, 'Server Not Reachable',
                  'The server is not reachable. You can still view your tickets, but you need to wait until the server is reachable again to add new tickets, view archived tickets or update your account information.\n\nIf the server is reachable again, please restart the app to enable these features.');
            }
          },
        );
      }
      logger.e(e.toString());
      online = false;
    }

    try {
      logger.d('getting user');
      final user =
          await tempPb.collection("users").getOne(tempPb.authStore.model.id);
      logger.d('user: $user');
    } catch (e) {
      logger.e("ClientException: $e");
      logger.e('User not found');
      tempPb.authStore.clear();
      WidgetsBinding.instance.addPostFrameCallback(
        (timeStamp) {
          Navigator.pushReplacement(
            context,
            MaterialPageRoute(
              builder: (context) => const LoginPage(),
            ),
          );
        },
      );
    }
    // _getLocale(tempPb).then((value) {
    //   setLocale(value);
    // });
    await initializeDateFormatting();

    logger.d('tempPb: $tempPb');
    return tempPb;
  }

  void _showPopup(BuildContext context, String title, String message) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text(title),
          content: Text(message),
          actions: <Widget>[
            TextButton(
              child: const Text('OK'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    log('notifTicketBuild: $notifTicket');
    return ValueListenableBuilder<Locale>(
      valueListenable: localeNotifier,
      builder: (context, localeValue, child) {
        // logger.d(
        //     'Language from ValueListenableBuilder: ${localeValue.countryCode} ${localeValue.toLanguageTag()}, ${localeValue.languageCode}, ${localeValue.countryCode}, ${localeValue.scriptCode}');
        return MaterialApp(
          title: 'Ticket Wallet',
          theme: ThemeData(
            scaffoldBackgroundColor: const Color(0xFFfaf9fe),
            colorScheme: ColorScheme.fromSeed(
              seedColor: const Color.fromARGB(255, 254, 179, 199),
            ),
            useMaterial3: true,
          ),
          localizationsDelegates: const [
            ...AppLocalizations.localizationsDelegates,
            EnNlMaterialLocalizationsDelegate.delegate
          ],
          supportedLocales: AppLocalizations.supportedLocales,
          locale: localeValue,
          home: Builder(
            builder: (BuildContext context) {
              return FutureBuilder(
                future: _pocketbaseSetup(context),
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.done) {
                    log('snapshot: ${snapshot.data}');
                    if (snapshot.data == null) {
                      logger.f('No connection to server');
                      return Scaffold(
                        body: Center(
                          child: Text(
                              AppLocalizations.of(context)!.noConnectionFatal),
                        ),
                      );
                    }
                    pb = snapshot.data as PocketBase;
                    // if (!online) {
                    //   return const Scaffold(
                    //     body: Center(
                    //       child: Text('No connection to server'),
                    //     ),
                    //   );
                    // }

                    return pb.authStore.isValid
                        // ? const TicketWalletApp()
                        ? notifTicket != null
                            ? TicketViewPage(ticket: notifTicket!)
                            : const TicketWalletApp()
                        : const LoginPage();
                  }
                  return const Scaffold(
                    body: Center(
                      child: CircularProgressIndicator(),
                    ),
                  );
                },
              );
            },
          ),
        );
      },
    );
  }
}

How do i fix this so that the user will go to the TicketViewPage(ticket:ticket). All the code inside the onDidRecieveNotificationResponse works. I also want that when the user clicks the little back arrow on the TicketViewPage, it goes back to the normal homepage(TicketWalletApp())

I've already tried things with a global key but that didnt work either.


Solution

  • i hope this can help you!

    First you can use NavigatorKey to push to the page you want:

    navigatorKey.currentState?.pushNamed(notificationResponse.payload!);

    notificationResponse.payload is: router name page you want.

    you would put it in the onNotificationTap function:

    enter image description here

    the ontap function is called like this: enter image description here