Search code examples
flutterfirebasedartnotificationsfirebase-cloud-messaging

Firebase Cloud Messaging, Initial Route in Terminated, Flutter


I have a project in Flutter, I have configured Firebase and FCM according to the documentation. However, when the user clicks on the notification while the app is terminated, my Flutter app is ignoring the route I defined in the getInitialMessage() of FCM. It goes directly to the home screen and then goes to the route I defined, causing multiple authentication errors because it is trying to make all the calls with the token of my application. I have tried in every way, I even created a test screen for the app to go directly to it when clicking on the notification in the terminated state. But for some reason, my app is ignoring it and continues to the home screen first.

Edit: I have already tested by removing the background and foreground states, leaving it to receive notifications only on 'terminated' in the main, but still the same behavior persists

Edit2: When I close the app and open it using the icon, it follows the flow normally, going to the splash screen and completing the other steps without generating any errors.

MAIN:

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

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  SystemChrome.setSystemUIOverlayStyle(
    const SystemUiOverlayStyle(
      statusBarColor: CustomColors.background,
      statusBarIconBrightness: Brightness.light,
    ),
  );

  Get.put(AuthController());
  Get.put(ResearchesController());

  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]).then((value) => runApp(const MyApp()));
}


@pragma('vm:entry-point')
Future<void> onBackgroundMessage(RemoteMessage message) async {}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    initializeFcm();
    super.initState();
  }

  Future<void> initializeFcm() async {
    FirebaseMessaging messaging = FirebaseMessaging.instance;
    
    FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);

   
    messaging.getInitialMessage().then((message) async {
     --->> HERE <---
      if (message != null) {
        if (message.data['route'] == 'mensagem') {
          Get.offNamed(PagesRoutes.splashRoute);
        } else if (message.data['route'] == 'correspondencia') {
          Get.offNamed(PagesRoutes.splashRoute);
        }
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      onUnknownRoute: (settings) {
        return MaterialPageRoute(builder: (context) => const SplashScreen());
      },
      localizationsDelegates: GlobalMaterialLocalizations.delegates,
      supportedLocales: const [
        Locale('pt'),
      ],
      title: 'FCM',
      theme: defaultTheme,
      debugShowCheckedModeBanner: false,
      initialRoute: PagesRoutes.splashRoute,
      getPages: AppPages.pages,
      builder: (context, child) {
        return BotToastInit()(context, child);
      },
    );
  }
}

BASESCREEN:

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

  @override
  State<BaseScreen> createState() => _BaseScreenState();
}

// Escuta as notificações em background e terminated
@pragma('vm:entry-point')
Future<void> onBackgroundMessage(RemoteMessage message) async {}

class _BaseScreenState extends State<BaseScreen> with TickerProviderStateMixin {
  late TabController _tabController;

  CorrespondenceController correspondenceController =
      Get.find<CorrespondenceController>();
  AuthController authController = Get.find<AuthController>();

  @override
  void initState() {
    super.initState();
    initializeFcm();
    _tabController = TabController(vsync: this, length: 5);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  Future<void> initializeFcm() async {
    print('ENTREI AQUI NO INITIALIZE BASE');
    final PermissionStatus notificationPermissionStatus =
        await RequestPermission.notificationRequest();

    if (notificationPermissionStatus.isGranted) {
      //Escuta as notificações em background
      FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);

      //Ação de clicar na notificação em background
      FirebaseMessaging.onMessageOpenedApp.listen((message) {
        print('ENTREI AQUI NO BACKGROUND BASE');
        if (message.data['route'] == 'mensagem' &&
            authController.token.isNotEmpty) {
          _tabController.animateTo(3);
        } else if (message.data['route'] == 'correspondencia' &&
            authController.token.isNotEmpty) {
          Get.toNamed(PagesRoutes.correspondencesRoute);
          correspondenceController.getAllCorrespondences();
        } else {
          Get.toNamed(PagesRoutes.signInRoute);
        }
      });
    } else if (Platform.isAndroid &&
            notificationPermissionStatus.isPermanentlyDenied ||
        Platform.isIOS && notificationPermissionStatus.isDenied) {
      // ignore: use_build_context_synchronously
      await showDialog(
        context: context,
        builder: (context) => const PermissionAlertDialog(),
      );
    }

    //Escuta as notificações em foreground
    FirebaseMessaging.onMessage.listen((message) {
      print('ENTREI AQUI NO FOREGROUND BASE');
      if (message.notification != null) {
        if (mounted) {
          Flushbar(
            title: '${message.notification!.title}',
            message: '${message.notification!.body}',
            duration: const Duration(seconds: 15),
            backgroundColor: CustomColors.darkBackground,
            titleColor: Colors.white,
            flushbarPosition: FlushbarPosition.TOP,
            margin: const EdgeInsets.symmetric(
              horizontal: 10,
              vertical: 10,
            ),
            onTap: (value) {
              if (message.data['route'] == 'mensagem') {
                _tabController.animateTo(3);
              } else if (message.data['route'] == 'correspondencia') {
                Get.toNamed(PagesRoutes.correspondencesRoute);
                correspondenceController.getAllCorrespondences();
              }
            },
            borderRadius: const BorderRadius.all(Radius.circular(5)),
          ).show(context);
        }
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return Material(
      child: DefaultTabController(
        length: 5,
        initialIndex: 0,
        child: Scaffold(
          resizeToAvoidBottomInset: false,
          backgroundColor: CustomColors.grey50,
          drawer: DrawerMenu(
            tabController: _tabController,
          ),
          appBar: AppBar(
            title: Align(
              alignment: Alignment.bottomRight,
              child: Image.asset(
                'assets/images/image.png',
                width: size.width * .35,
              ),
            ),
            elevation: 0,
            bottom: PreferredSize(
              preferredSize: Size.fromHeight(
                size.height * .08,
              ),
              child: Column(
                children: [
                  Padding(
                    padding: EdgeInsets.only(left: size.height * .001),
                    child: TabBar(
                      controller: _tabController,
                      physics: const BouncingScrollPhysics(),
                      isScrollable: true,
                      indicatorPadding: EdgeInsets.symmetric(
                        vertical: size.height * .005,
                      ),
                      indicatorSize: TabBarIndicatorSize.label,
                      indicator: BoxDecoration(
                        border: Border(
                          bottom: BorderSide(
                            color: CustomColors.orange,
                            width: size.height * .004,
                          ),
                        ),
                      ),
                      labelPadding: EdgeInsets.symmetric(
                        horizontal: size.width * .04,
                      ),
                      tabs: const [
                        TabBarTile(
                          image: 'assets/images/home.png',
                          label: 'Home',
                        ),
                        TabBarTile(
                          image: 'assets/images/pesquisas.png',
                          label: 'Pesquisas',
                        ),
                        TabBarTile(
                          image: 'assets/images/agendamentos.png',
                          label: 'Agendamentos',
                        ),
                        TabBarTile(
                          image: 'assets/images/mensagens.png',
                          label: 'Mensagens',
                        ),
                        TabBarTile(
                          image: 'assets/images/cadastros.png',
                          label: 'Cadastros',
                        ),
                      ],
                    ),
                  ),
                  Container(
                    color: Colors.white,
                    height: 1,
                  ),
                ],
              ),
            ),
          ),
          body: Stack(
            children: [
              TabBarView(
                controller: _tabController,
                children: const [
                  HomeTab(),
                  ResearchesTab(),
                  SchedulesTab(),
                  MessagesTab(),
                  RegistrationsTab(),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

APPROUTES:


abstract class AppPages {
  static final pages = <GetPage>[
    GetPage(
      name: PagesRoutes.splashRoute,
      page: () => const SplashScreen(),
    ),
    GetPage(
      name: PagesRoutes.signInRoute,
      page: () => const SignInScreen(),
    ),
    GetPage(
      name: PagesRoutes.termsOfUseRoute,
      page: () => const TermsOfUseScreen(),
    ),
    GetPage(
      name: PagesRoutes.correspondencesRoute,
      page: () => const CorrespondencesScreen(),
    ),
    GetPage(
      name: PagesRoutes.classifiedsRoute,
      page: () => ClassifiedsScreen(),
    ),
    GetPage(
      name: PagesRoutes.newClassifiedRoute,
      page: () => const NewClassifiedScreen(),
    ),
    GetPage(
      name: PagesRoutes.downloadsRoute,
      page: () => const DownloadsScreen(),
    ),
    GetPage(
      name: PagesRoutes.baseRoute,
      page: () => const BaseScreen(),
      bindings: [
        HomeBinding(),
        MessagesBinding(),
        ResidentRegistrationBinding(),
        VehicleBinding(),
        BikeBinding(),
        AnimalBinding(),
        VisitsAndGuestsBinding(),
        ServiceProvidersBinding(),
        SchedulesBinding(),
        GuestsBinding(),
        CorrespondenceBinding(),
        ClassifiedsBinding(),
        DownloadsBinding(),
      ],
    ),
    GetPage(
      name: PagesRoutes.viewMessageRoute,
      page: () => ViewMessageScreen(),
    ),
    GetPage(
      name: PagesRoutes.searchVisitsRoute,
      page: () => const SearchVisitsScreen(),
    ),
    GetPage(
      name: PagesRoutes.sendMessageRoute,
      page: () => const SendMessageScreen(),
      bindings: [
        SendMessageBinding(),
      ],
    ),
  ];
}

abstract class PagesRoutes {
  static const String baseRoute = '/';
  static const String splashRoute = '/splash';
  static const String signInRoute = '/signIn';
  static const String termsOfUseRoute = '/termsOfUse';
  static const String classifiedsRoute = '/classifieds';
  static const String newClassifiedRoute = '/newClassified';
  static const String noticeRoute = '/notice';
  static const String viewMessageRoute = '/viewmessage';
  static const String sendMessageRoute = '/sendmessage';
  static const String correspondencesRoute = '/correspondences';
  static const String downloadsRoute = '/downloads';
  static const String searchVisitsRoute = '/searchVisits';
}

My goal is that when the user clicks on the notification they are receiving, the app follows my instruction and goes to the splash screen, where it will be checked whether they are logged in or not. If they are logged in, the splash screen will direct them to the home screen; if not, it will redirect them to the login screen.


Solution

  • For those who have the same issue, I managed to unravel the mystery. What was happening was that the route manager always uses the "/" route as the main route. In my case, it was being set with the baseScreen. What I did was change the "/" route to be the splashScreen, ensuring that it followed the normal flow without errors.