Flutter
push notifications
mobile development
app navigation
Flutter tutorials

how to open particular screen on clicking on push notification for flutter

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Opening a specific screen from a push notification in Flutter is mostly a routing problem plus lifecycle handling. The same notification can reach your app while it is foregrounded, backgrounded, or terminated, and each state has a different callback path. A stable solution uses one payload format, one parser, and one navigation dispatcher.

Understand Lifecycle Entry Points

With Firebase Cloud Messaging, you typically handle three entry points:

  1. foreground message via FirebaseMessaging.onMessage
  2. background-to-foreground tap via FirebaseMessaging.onMessageOpenedApp
  3. terminated launch via FirebaseMessaging.instance.getInitialMessage()

If you only wire one callback, deep-link navigation will appear flaky. Users will report that tapping notifications works sometimes and fails other times.

A practical rule is to route only on user intent. For foreground messages, show a local notification and navigate when the user taps it, not when the message arrives.

Bootstrap Messaging and Global Navigation

Push callbacks often run outside widget context, so use a global navigator key. Initialize listeners after Firebase.initializeApp() and before heavy screen loading.

dart
1import 'package:firebase_core/firebase_core.dart';
2import 'package:firebase_messaging/firebase_messaging.dart';
3import 'package:flutter/material.dart';
4
5final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
6
7Future<void> main() async {
8  WidgetsFlutterBinding.ensureInitialized();
9  await Firebase.initializeApp();
10
11  await PushRouter.instance.init();
12  runApp(const MyApp());
13}
14
15class MyApp extends StatelessWidget {
16  const MyApp({super.key});
17
18  
19  Widget build(BuildContext context) {
20    return MaterialApp(
21      navigatorKey: navigatorKey,
22      routes: {
23        '/': (_) => const HomeScreen(),
24        '/chat': (_) => const ChatScreen(),
25        '/order': (_) => const OrderScreen(),
26      },
27    );
28  }
29}

The navigatorKey removes dependency on transient BuildContext values.

Use One Payload Contract and Dispatcher

Define a compact data contract from backend to app. Keep key names stable across versions.

Example payload data:

json
1{
2  "type": "order",
3  "id": "A1024",
4  "source": "promo"
5}

Now map payload to routes in one place:

dart
1class PushRouter {
2  PushRouter._();
3  static final instance = PushRouter._();
4
5  String? _lastMessageId;
6
7  Future<void> init() async {
8    FirebaseMessaging.onMessageOpenedApp.listen(_handle);
9
10    final initial = await FirebaseMessaging.instance.getInitialMessage();
11    if (initial != null) _handle(initial);
12  }
13
14  void _handle(RemoteMessage message) {
15    if (message.messageId != null && message.messageId == _lastMessageId) {
16      return;
17    }
18    _lastMessageId = message.messageId;
19
20    final data = message.data;
21    final type = data['type'];
22    final id = data['id'];
23
24    if (type == 'chat') {
25      navigatorKey.currentState?.pushNamed('/chat');
26      return;
27    }
28
29    if (type == 'order' && id != null) {
30      navigatorKey.currentState?.pushNamed('/order', arguments: id);
31      return;
32    }
33
34    navigatorKey.currentState?.pushNamed('/');
35  }
36}

Returning to one dispatcher avoids duplicated logic and drift between callbacks.

Delay Navigation Until App Is Ready

On cold start, route pushes can fire too early before the first frame or before route tables are ready. Queue the target route and apply it after app startup finishes.

dart
1String? pendingRoute;
2
3void scheduleRoute(String routeName) {
4  final nav = navigatorKey.currentState;
5  if (nav == null) {
6    pendingRoute = routeName;
7    return;
8  }
9  nav.pushNamed(routeName);
10}
11
12void flushPendingRoute() {
13  if (pendingRoute == null) return;
14  navigatorKey.currentState?.pushNamed(pendingRoute!);
15  pendingRoute = null;
16}

Call flushPendingRoute() when your root screen confirms initialization is complete.

Verify with a Small Test Matrix

Test all user-visible paths:

  1. app terminated, user taps notification
  2. app in background, user taps notification
  3. app in foreground, local notification tap opens target screen
  4. unknown payload keys route to a safe fallback

Also test duplicate delivery behavior by sending repeated messages with the same notification id.

Common Pitfalls

  • Handling only onMessageOpenedApp and forgetting getInitialMessage().
  • Navigating directly from callbacks without a global navigator key.
  • Parsing payload in multiple files with inconsistent key names.
  • Opening routes automatically in foreground without explicit user tap.
  • Missing deduplication, causing duplicate pushed screens.
  • Triggering navigation before route registration and app bootstrap are complete.

Summary

  • Treat push navigation as a lifecycle plus routing problem.
  • Wire all messaging entry points, not just one callback.
  • Use a single payload contract and a single route dispatcher.
  • Navigate through navigatorKey to avoid context issues.
  • Add deduplication and startup-safe routing to prevent flaky behavior.
  • Validate every lifecycle state before release.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions