MaterialApp.router#

What is MaterialApp.router()?#

MaterialApp.router() is a special version of MaterialApp that uses Flutter’s new navigation system (Navigator 2.0 / Router API) instead of the old one.

In simple words:

MaterialApp.router connects your app to a Router that decides which screen to show based on the URL and app state.

It is mainly used for:
  • Web apps

  • Deep linking (/product/10)

  • Complex navigation

  • Enterprise-level apps

  • Professional routing (GoRouter, Beamer, etc.)

Big idea in ONE LINE

Old way

New way

MaterialApp()

MaterialApp.router()

β€œPush & pop pages manually”

β€œRouter decides the page for you”

Navigator 1.0

Navigator 2.0

βœ… Basic Structure of MaterialApp.router()

MaterialApp.router(
    routerConfig: myRouter,
)

Instead of this (old way):

MaterialApp(
    home: HomePage(),
    routes: {...},
)

You now write:

MaterialApp.router(
    routerConfig: myGoRouter,
)

The router controls everything.


All named parameters#

MaterialApp.router(

  1. routerConfig:

  2. routerDelegate:

  3. routeInformationParser:

  4. routeInformationProvider:

  5. backButtonDispatcher:

  6. builder:

  7. title:

  8. onGenerateTitle:

  9. color:

  10. theme:

  11. darkTheme:

  12. highContrastTheme:

  13. highContrastDarkTheme:

  14. themeMode:

  15. themeAnimationDuration:

  16. themeAnimationCurve:

  17. themeAnimationStyle:

  18. locale:

  19. supportedLocales:

  20. localizationsDelegates:

  21. localeResolutionCallback:

  22. localeListResolutionCallback:

  23. debugShowCheckedModeBanner:

  24. debugShowMaterialGrid:

  25. showPerformanceOverlay:

  26. showSemanticsDebugger:

  27. shortcuts:

  28. actions:

  29. restorationScopeId:

  30. scrollBehavior:

  31. onNavigationNotification:

)

βœ…1.routerConfig:#

This example uses GoRouter (a popular routing package) to show two pages: Home and About.

βœ… Step 1: Add dependency in pubspec.yaml

dependencies:
flutter:
    sdk: flutter
go_router: ^10.0.0

Run:

flutter pub get

βœ… Step 2: main.dart (Simple program using routerConfig)

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() {
    runApp(const MyApp());
}

// Step 3: Define your router here
final GoRouter _router = GoRouter(
    routes: [
        GoRoute(
            path: '/',
            builder: (context, state) => const HomePage(),
        ),
        GoRoute(
            path: '/about',
            builder: (context, state) => const AboutPage(),
        ),
    ],
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,
            routerConfig: _router,   // πŸ‘ˆ THIS IS THE MAIN PART
        );
    }
}

/* ------------------ HOME PAGE ------------------ */
class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text('Home')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        context.go('/about');    // Navigate using router
                    },
                    child: const Text('Go to About Page'),
                ),
            ),
        );
    }
}

/* ------------------ ABOUT PAGE ------------------ */
class AboutPage extends StatelessWidget {
    const AboutPage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text('About')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        context.go('/');  // Go back to Home
                    },
                    child: const Text('Back to Home'),
                ),
            ),
        );
    }
}

What exactly is routerConfig doing?

routerConfig is the brain of the navigation system.

It tells Flutter:

βœ… What pages exist

βœ… What their paths are (/ and /about)

βœ… How to move between them

This line connects everything:

routerConfig: _router,

Without this, the app doesn’t know how to handle the routes.


Short explanation (easy words)

Think of routerConfig like a map:
  • / β†’ HomePage

  • /about β†’ AboutPage

When you say:

context.go('/about');

Flutter looks at routerConfig and says:

β€œOkay, /about = AboutPage” β†’ then opens that screen.


βœ…2.routerDelegate:#

Here is a very simple and clean Flutter example that demonstrates the routerDelegate: parameter in MaterialApp.router using a custom RouterDelegate.

This is the pure Flutter way (no go_router, no packages) so you can clearly understand how routerDelegate works.

βœ… What this example does

Two pages:
  • Home page

  • About page

A custom MyRouterDelegate decides which page is shown.

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================== MY APP ================== */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    // Create delegate + parser once
    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ Focus of this example
            routerDelegate: _routerDelegate,               // RouterDelegate<Object>
            routeInformationParser: _routeInformationParser, // RouteInformationParser<Object>
        );
    }
}

/* =========== ROUTER DELEGATE =========== */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    MyRouterDelegate();

    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    bool _showAboutPage = false;

    // Helper methods to control navigation
    void goToAbout() {
        _showAboutPage = true;
        notifyListeners(); // tell Router to rebuild Navigator
    }

    void goToHome() {
        _showAboutPage = false;
        notifyListeners();
    }

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: [
                MaterialPage(
                    key: const ValueKey('HomePage'),
                    child: const HomePage(),
                ),
                if (_showAboutPage)
                MaterialPage(
                    key: const ValueKey('AboutPage'),
                    child: const AboutPage(),
                ),
            ],

            // βœ… New API – used instead of deprecated onPopPage
            onDidRemovePage: (Page<Object?> page) {
                // If the About page was removed (e.g. back button),
                // update our internal state to match.
                if (page.key == const ValueKey('AboutPage')) {
                    _showAboutPage = false;
                    notifyListeners();
                }
            },
        );
    }

    // Required by RouterDelegate<Object>
    @override
    Future<void> setNewRoutePath(Object configuration) async {
        // configuration is whatever parseRouteInformation returns.
        // For our simple example we only care about '/' and '/about'.
        if (configuration == '/about') {
            _showAboutPage = true;
        } else {
            _showAboutPage = false;
        }
        // no need to notifyListeners() here usually; Router may rebuild.
    }
}

/* ======== ROUTE INFORMATION PARSER ======== */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        // Very simple: check the path
        final location = routeInformation.location ?? '/';
        final uri = Uri.parse(location);

        if (uri.pathSegments.isNotEmpty && uri.pathSegments.first.toLowerCase() == 'about') {
            return '/about';
        }

        return '/'; // default
    }
}

/* ================== HOME PAGE ================== */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        // Get our custom delegate
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('Home Page')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        // πŸ‘‡ Using routerDelegate to navigate
                        delegate.goToAbout();
                    },
                    child: const Text('Go to About Page'),
                ),
            ),
        );
    }
}

/* ================== ABOUT PAGE ================== */

class AboutPage extends StatelessWidget {
    const AboutPage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('About Page')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        // Navigate back using routerDelegate
                        delegate.goToHome();
                    },
                    child: const Text('Back to Home'),
                ),
            ),
        );
    }
}

1. Where is routerDelegate in your program?

This line in MyApp is the important part:

return MaterialApp.router(
    debugShowCheckedModeBanner: false,
    routerDelegate: _routerDelegate,
    routeInformationParser: _routeInformationParser,
);

Here:

final MyRouterDelegate _routerDelegate = MyRouterDelegate();

That means:

You are telling Flutter: β€œDon’t handle navigation yourself β€” let MyRouterDelegate control it.”

So routerDelegate is like the BRAIN of navigation in your app.

2. What is routerDelegate actually doing?

The routerDelegate:
  • Creates the Navigator

  • Adds pages to the Navigator stack

  • Removes pages from the Navigator stack

  • Controls which page is currently shown

This is the part inside your MyRouterDelegate that does the real job:

@override
Widget build(BuildContext context) {
    return Navigator(
        key: navigatorKey,

        pages: [
            MaterialPage(
                key: const ValueKey('HomePage'),
                child: const HomePage(),
            ),

            if (_showAboutPage)
                MaterialPage(
                    key: const ValueKey('AboutPage'),
                    child: const AboutPage(),
                ),
        ],

        onDidRemovePage: (Page<Object?> page) {
            if (page.key == const ValueKey('AboutPage')) {
                _showAboutPage = false;
                notifyListeners();
            }
        },
    );
}
This means:
  • Home page is always shown

  • About page is shown only when:

_showAboutPage == true

So your routerDelegate says:

If _showAboutPage is true β†’ show About page

If false β†’ don’t show About page

That’s total control.

3. How does navigation happen?

In your HomePage you wrote:

final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

onPressed: () {
    delegate.goToAbout();
}

This method is inside MyRouterDelegate:

void goToAbout() {
    _showAboutPage = true;
    notifyListeners();
}
So the flow is:
  1. Button pressed β†’ delegate.goToAbout()

  2. _showAboutPage = true

  3. notifyListeners() tells Flutter: β€œRebuild pages”

  4. build() runs again

  5. Now About page is added to pages

  6. You see the About Screen

That is routerDelegate in action.

It is NOT using Navigator.push()

It is using state-based navigation controlled by the delegate.

4. Back button & onDidRemovePage

When you press the back button on Android or the back gesture on iOS:

Flutter removes the page.

This code handles that:

onDidRemovePage: (Page<Object?> page) {
    if (page.key == const ValueKey('AboutPage')) {
        _showAboutPage = false;
        notifyListeners();
    }
},

This means:

If About page is removed β†’ switch _showAboutPage back to false β†’ show Home again

Again, that logic is in routerDelegate.

So routerDelegate is not just about going forward β€” it also controls back navigation.

5. Simple understanding (real-life example)

Think of routerDelegate as a security guard at a door:

You: β€œLet the user in” Delegate: βœ… shows page

You: β€œSend the user back” Delegate: βœ… removes page

You: β€œOnly allow About page if true” Delegate: βœ… uses condition (_showAboutPage)

So:

Thing

Who controls it?

Showing Home

routerDelegate

Showing About

routerDelegate

Going back

routerDelegate

Page stack

routerDelegate

6. Short definition (you can remember this)

routerDelegate: is a controller that decides which pages are in your app’s navigation stack and when they appear or disappear.

In very simple words:

routerDelegate = Navigation Brain


βœ…3.routeInformationParser:#

βœ… What this program shows

  • How routeInformationParser reads a URL/path (like / or /about)

  • How it converts that into app state

  • How routerDelegate uses that state to show the correct page

You can run this on mobile or web.

βœ… Complete Working Example (main.dart)

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ This is our focus
            routeInformationParser: _routeInformationParser,

            // RouterDelegate uses the parsed information
            routerDelegate: _routerDelegate,
        );
    }
}

/* ============= ROUTER DELEGATE ============= */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    bool _isAboutPage = false;

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,

            pages: [
                const MaterialPage(
                    key: ValueKey('HomePage'),
                    child: HomePage(),
                ),

                if (_isAboutPage)
                const MaterialPage(
                    key: ValueKey('AboutPage'),
                    child: AboutPage(),
                ),
            ],

            onDidRemovePage: (Page<Object?> page) {
                if (page.key == const ValueKey('AboutPage')) {
                    _isAboutPage = false;
                    notifyListeners();
                }
            },
        );
    }

    // This gets the result FROM the RouteInformationParser
    @override
    Future<void> setNewRoutePath(Object configuration) async {
        if (configuration == '/about') {
            _isAboutPage = true;
        } else {
            _isAboutPage = false;
        }
    }

    // Manual navigation (button)
    void goToAbout() {
        _isAboutPage = true;
        notifyListeners();
    }

    void goHome() {
        _isAboutPage = false;
        notifyListeners();
    }
}

/* ===== ROUTE INFORMATION PARSER (MAIN FOCUS) ===== */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        // This is the URL/path in the browser or deep link
        final location = routeInformation.location ?? '/';

        debugPrint('URL received: $location');

        // Convert URL into something the RouterDelegate understands
        if (location.toLowerCase() == '/about') {
            return '/about';   // send to RouterDelegate
        }

        return '/'; // default
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('Home')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        delegate.goToAbout();
                    },
                    child: const Text('Go to About'),
                ),
            ),
        );
    }
}

/* ================= ABOUT ================= */

class AboutPage extends StatelessWidget {
    const AboutPage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('About')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        delegate.goHome();
                    },
                    child: const Text('Back to Home'),
                ),
            ),
        );
    }
}

What is routeInformationParser in simple words?

Very simple definition:

routeInformationParser = the translator between URL and your app

Example:

Browser URL

Parsed by routeInformationParser

Result

/

β€˜/’

Home Page

/about

β€˜/about’

About Page

This part is the key:

final location = routeInformation.location ?? '/';

if (location == '/about') {
    return '/about';
}

return '/';

You are saying:

β€œWhen the URL is /about, I want to show the About page.”

That value is passed into this in RouterDelegate:

Future<void> setNewRoutePath(Object configuration)
So:
  1. User types /about

  2. routeInformationParser reads /about

  3. Sends /about to RouterDelegate

  4. RouterDelegate shows About page

βœ… That is exactly how deep linking & web routing work

Super-short memory tip

routeInformationParser = URL Reader + Translator

  • Reads browser URL

  • Converts it to app state

  • Sends to routerDelegate

routerDelegate = Page Controller


βœ…4.routeInformationProvider:#

First – What is routeInformationProvider?

In simple words:

routeInformationProvider is the source of the route (URL/path) for your app.

It provides the current route (like / or /about) to:
  • routeInformationParser

  • routerDelegate

Without it, Flutter uses a default one (from the platform). When you provide your own β€” you are the source of the route.

Think of it like:

β€œWhere does the app get the current path from?”

βœ… Simple Working Example: routeInformationProvider

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();

    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    // πŸ‘‡ THIS IS OUR CUSTOM PROVIDER
    final MyRouteInformationProvider _routeInformationProvider = MyRouteInformationProvider();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ”Ή MAIN FOCUS OF THIS EXAMPLE
            routeInformationProvider: _routeInformationProvider,

            routeInformationParser: _routeInformationParser,
            routerDelegate: _routerDelegate,
        );
    }
}

/* ============ CUSTOM ROUTE INFORMATION PROVIDER ============ */

class MyRouteInformationProvider extends RouteInformationProvider with ChangeNotifier {
    RouteInformation _value = const RouteInformation(location: '/'); // default route

    @override
    RouteInformation get value => _value;

    // Custom method to update route
    void updateRoute(String newLocation) {
        _value = RouteInformation(location: newLocation);
        notifyListeners();
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    bool _isAboutPage = false;

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: [
                const MaterialPage(
                    key: ValueKey('home'),
                    child: HomePage(),
                ),
                if (_isAboutPage)
                const MaterialPage(
                    key: ValueKey('about'),
                    child: AboutPage(),
                ),
            ],

            onDidRemovePage: (Page<Object?> page) {
                if (page.key == const ValueKey('about')) {
                    _isAboutPage = false;
                    notifyListeners();
                }
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {
        if (configuration == '/about') {
            _isAboutPage = true;
        } else {
            _isAboutPage = false;
        }
    }
}

/* ============ PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        final location = routeInformation.location ?? '/';

        if (location == '/about') return '/about';

        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        final provider = Router.of(context).routeInformationProvider
            as MyRouteInformationProvider;

        return Scaffold(
            appBar: AppBar(title: const Text('Home')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        // Change route using Provider
                        provider.updateRoute('/about'); // πŸ‘ˆ THIS LINE
                        delegate.notifyListeners();      // refresh router
                    },
                    child: const Text('Go to About'),
                ),
            ),
        );
    }
}

/* ================= ABOUT ================= */

class AboutPage extends StatelessWidget {
    const AboutPage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        final provider = Router.of(context).routeInformationProvider
            as MyRouteInformationProvider;

        return Scaffold(
            appBar: AppBar(title: const Text('About')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        provider.updateRoute('/'); // go back
                        delegate.notifyListeners();
                    },
                    child: const Text('Back to Home'),
                ),
            ),
        );
    }
}

πŸ” Focus on routeInformationProvider

This is the key line in MyApp:

routeInformationProvider: _routeInformationProvider,

And this is the core logic:

class MyRouteInformationProvider extends RouteInformationProvider with ChangeNotifier {

    RouteInformation _value = const RouteInformation(location: '/');

    @override
    RouteInformation get value => _value;

    void updateRoute(String newLocation) {
        _value = RouteInformation(location: newLocation);
        notifyListeners();
    }
}

What this means:

  • _value stores the current route

  • value gives the route to Flutter

  • notifyListeners() tells Flutter:

β€œHey! The route has changed!”

In your buttons:

provider.updateRoute('/about');

That is how you change the URL/route using the provider.

Simple understanding (remember this)

Part

Job

routeInformationProvider

Gives current route (URL)

routeInformationParser

Reads & translates the route

routerDelegate

Shows correct pages

Think like this:

Provider = β€œHere is the path”

Parser = β€œI understand the path”

Delegate = β€œI show the page”


βœ…5.backButtonDispatcher:#

What is backButtonDispatcher (in simple words)?

backButtonDispatcher controls who handles the BACK button first.

It decides:
  • Should the Router handle back?

  • Or should the Navigator handle back?

  • Or should I override it?

Normally Flutter does it automatically…

But with backButtonDispatcher, you can take control.

βœ… Simple working example showing backButtonDispatcher

This app has:
  • Home page

  • About page

Using back button:
  • Goes back to home

  • Then exits app

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    // πŸ‘‡ CUSTOM BACK BUTTON DISPATCHER
    final RootBackButtonDispatcher _backButtonDispatcher =
        RootBackButtonDispatcher();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            // βœ… THIS IS THE POINT OF THIS EXAMPLE
            backButtonDispatcher: _backButtonDispatcher,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    bool _showAbout = false;

    void goToAbout() {
        _showAbout = true;
        notifyListeners();
    }

    void goHome() {
        _showAbout = false;
        notifyListeners();
    }

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,

            pages: [
                const MaterialPage(
                    key: ValueKey('home'),
                    child: HomePage(),
                ),

                if (_showAbout)
                const MaterialPage(
                    key: ValueKey('about'),
                    child: AboutPage(),
                ),
            ],

            onDidRemovePage: (Page<Object?> page) {
                if (page.key == const ValueKey('about')) {
                    _showAbout = false;
                    notifyListeners();
                }
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {
        if (configuration == '/about') {
            _showAbout = true;
        } else {
            _showAbout = false;
        }
    }
}

/* ============ PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        final location = routeInformation.location ?? '/';
        return location;
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('Home Page')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        delegate.goToAbout();
                    },
                    child: const Text('Go to About Page'),
                ),
            ),
        );
    }
}

/* ================= ABOUT ================= */

class AboutPage extends StatelessWidget {
    const AboutPage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('About Page')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        delegate.goHome();
                    },
                    child: const Text('Back to Home'),
                ),
            ),
        );
    }
}

What part is backButtonDispatcher?

This is the key line:

final RootBackButtonDispatcher _backButtonDispatcher = RootBackButtonDispatcher();

And it is connected here:

MaterialApp.router(
    backButtonDispatcher: _backButtonDispatcher,
)

This says:

β€œLet the Router system manage the back button in a smart way.”

When you press:
  • Back on About page β†’ it goes to Home

  • Back on Home β†’ app closes (or browser back works)

The RootBackButtonDispatcher automatically gives priority to the router.

Ultra-simple summary (remember this)

Parameter

Job

backButtonDispatcher

Controls back button behavior

routerDelegate

Controls pages

routeInformationParser

Reads URL

Real life analogy:

backButtonDispatcher = traffic police for the back button


βœ…6.builder:#

What is builder: in MaterialApp.router?

In very simple words:

builder is a wrapper around the whole app.

Whatever you put in builder will be applied to every page in your app β€” like a global layer.

You can use it for:
  • Themes

  • MediaQuery changes

  • Overlays

  • Global widgets (like banners, watermarks, padding, etc.)

Think like this:

builder = β€œWrap my entire app inside this widget”

βœ… Simple example: builder: in action

This example wraps every page inside a blue border and padding, so you can visually see the effect of builder.

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,
            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            // builder example still here
            builder: (context, child) {
                return Container(
                    padding: const EdgeInsets.all(20),
                    decoration: BoxDecoration(
                        border: Border.all(width: 4),
                    ),
                    child: child ?? const SizedBox.shrink(),
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    bool _showAbout = false;

    void goToAbout() {
        _showAbout = true;
        notifyListeners();
    }

    void goHome() {
        _showAbout = false;
        notifyListeners();
    }

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: [
                const MaterialPage(
                    key: ValueKey('home'),
                    child: HomePage(),
                ),
                if (_showAbout)
                const MaterialPage(
                    key: ValueKey('about'),
                    child: AboutPage(),
                ),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {
        _showAbout = configuration == '/about';
    }
}

/* ============ FIXED PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {

        // βœ… NEW WAY (no deprecation)
        final Uri uri = routeInformation.uri;

        if (uri.path == '/about') {
            return '/about';
        }

        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('Home Page')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        delegate.goToAbout();
                    },
                    child: const Text('Go to About Page'),
                ),
            ),
        );
    }
}

/* ================= ABOUT ================= */

class AboutPage extends StatelessWidget {
    const AboutPage({super.key});

    @override
    Widget build(BuildContext context) {
        final delegate = Router.of(context).routerDelegate as MyRouterDelegate;

        return Scaffold(
            appBar: AppBar(title: const Text('About Page')),
            body: Center(
                child: ElevatedButton(
                    onPressed: () {
                        delegate.goHome();
                    },
                    child: const Text('Back to Home'),
                ),
            ),
        );
    }
}

Focus: THIS is the builder parameter

builder: (context, child) {
    return Container(
        padding: const EdgeInsets.all(20),
        decoration: BoxDecoration(
        border: Border.all(color: Colors.blue, width: 4),
        ),
        child: child,
    );
},
What this means
  • child = the page that router is building (Home / About)

  • We wrap that page in a Container

  • That container has:

  • Padding

  • Blue border

So every page in your app will appear inside that blue bordered container.

This is why it is called global wrapper.

Simple memory tip

Parameter

What it does

builder:

Wraps the whole app

routerDelegate:

Controls pages

routeInformationParser:

Reads URL

builder = Global decorator / wrapper

βœ…7.title:#

βœ… Simple program to describe title: in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ THIS is the focus of this example
            title: 'My Router App',

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(
                    child: HomePage(),
                ),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {
        // Not needed for this simple example
    }
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
@override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text('Home Page')),
            body: const Center(
                child: Text(
                    'Look at the browser tab or task switcher title!',
                    textAlign: TextAlign.center,
                ),
            ),
        );
    }
}

What does title: actually do?

This line:

title: 'My Router App',

Controls:

βœ… On Flutter Web

  • The browser tab name becomes:

My Router App

βœ… On Android / iOS
  • Shows in the app switcher

  • Helps OS identify your app

❌ It does NOT affect
  • AppBar title

  • Page title inside the screen

For that, you still use:

AppBar(title: Text('Home Page'))

Simple comparison

Code

Result

title: β€˜My Router App’

Changes browser/app name

AppBar(title: Text(β€˜Home’))

Changes top bar text

βœ… Very short definition

title: in MaterialApp.router is the name of the app for the operating system and browser, not the page content.

βœ…8.onGenerateTitle:#

βœ… Simple program to describe onGenerateTitle

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ FOCUS: Dynamic title instead of fixed `title:`
            onGenerateTitle: (context) {
                return 'Dynamic Router Title';
            },

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(
                    child: HomePage(),
                ),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {
        // Not needed for this simple example
    }
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
        appBar: AppBar(title: const Text('Home Page')),
        body: const Center(
            child: Text(
                'Look at browser tab / app switcher title',
                textAlign: TextAlign.center,
            ),
        ),
        );
    }
}

What does onGenerateTitle really do?

This line is the main point:

onGenerateTitle: (context) {
    return 'Dynamic Router Title';
},

Instead of a fixed title:

title: 'My App',

you now generate the title at runtime using a function.

That function:
  • Gets a BuildContext

  • Must return a String

So this is possible too:

onGenerateTitle: (context) {
    final hour = DateTime.now().hour;
    return hour < 12 ? 'Good Morning App' : 'Good Evening App';
},

Now the app title changes based on time βœ…

βœ… Important rule

You should use:
  • Either title:

  • Or onGenerateTitle:

Not both. If both are present β†’ onGenerateTitle wins.

Very simple meaning

title: = static name

onGenerateTitle: = dynamic name (calculated by a function)


βœ…9.color:#

βœ… Simple program to describe color: in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ THIS is the focus of this example
            color: Colors.blue,   // Background color for the app window

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    @override
    final GlobalKey<NavigatorState> navigatorKey =
        GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(
                    child: HomePage(),
                ),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {
        // Not needed for this example
    }
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(
        RouteInformation routeInformation,
    ) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            backgroundColor: Colors.white, // Contrast with app color
            appBar: AppBar(title: const Text('Color Demo')),
            body: const Center(
                child: Text(
                    'The BLUE you see behind this page\ncomes from MaterialApp.color',
                    textAlign: TextAlign.center,
                ),
            ),
        );
    }
}

What does color: actually do?

This line:

color: Colors.blue,

Controls the background color behind your app’s pages.

Important facts:

Thing

Is it affected by color:?

Scaffold background

❌ No

Container color

❌ No

AppBar color

❌ No

Behind the entire app (window/background)

βœ… YES

If your Scaffold has transparent sections, you will see the MaterialApp.color behind it.

βœ… Where you notice it most

You’ll notice the color more clearly when:
  • Using web

  • Using transparent widgets

  • During transitions/loading

  • On splash / root level

It’s basically the base / root color of the app window.

Super short definition

color: in MaterialApp.router is the base background color behind your entire app.

βœ…10.theme:#

βœ… Simple program to describe theme: in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ THIS is the focus of this example
            theme: ThemeData(
                primaryColor: Colors.deepPurple,
                appBarTheme: const AppBarTheme(
                    backgroundColor: Colors.deepPurple,
                ),
                elevatedButtonTheme: ElevatedButtonThemeData(
                    style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.deepPurple,
                        foregroundColor: Colors.white,
                    ),
                ),
                textTheme: const TextTheme(
                    bodyMedium: TextStyle(fontSize: 18),
                ),
            ),

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(child: HomePage()),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {
        // Not needed for this simple example
    }
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: const Text('Theme Demo'),
            ),

            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: const [
                        Text('This color comes from the Theme'),
                        SizedBox(height: 20),
                        ThemedButton(),
                    ],
                ),
            ),
        );
    }
}

/* ================= BUTTON ================= */

class ThemedButton extends StatelessWidget {
    const ThemedButton({super.key});

    @override
    Widget build(BuildContext context) {
        return ElevatedButton(
            onPressed: () {},
            child: const Text('Themed Button'),
        );
    }
}

What does theme: do?

This part is the key:

theme: ThemeData(
    primaryColor: Colors.deepPurple,
    appBarTheme: const AppBarTheme(
        backgroundColor: Colors.deepPurple,
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
            backgroundColor: Colors.deepPurple,
            foregroundColor: Colors.white,
        ),
    ),
),

It controls the global design of your app:

Widget

What changed

AppBar

Purple

ElevatedButton

Purple with white text

Text

Bigger (18px)

Entire app look

Styled by theme

You did not set any colors in:
  • AppBar()

  • ElevatedButton()

  • Text()

The theme applied it automatically βœ…

That is the purpose of theme:.

βœ… Simple meaning (remember this)

theme: = global style controller for the entire app

Instead of setting colors on every widget, you define them once in MaterialApp.router using ThemeData.


βœ…11.darkTheme:#

βœ… Simple program to describe darkTheme: in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ LIGHT THEME
            theme: ThemeData.light(),

            // πŸ‘‡ DARK THEME (MAIN FOCUS)
            darkTheme: ThemeData.dark(),

            // πŸ‘‡ Uses device/system setting (Light or Dark)
            themeMode: ThemeMode.system,

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

@override
Widget build(BuildContext context) {
    return Navigator(
        key: navigatorKey,
        pages: const [
            MaterialPage(child: HomePage()),
        ],
    );
}

@override
Future<void> setNewRoutePath(Object configuration) async {
    // Not required for this example
}
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        final brightness = Theme.of(context).brightness;

        return Scaffold(
            appBar: AppBar(
                title: const Text('darkTheme Demo'),
            ),
            body: Center(
                child: Text(
                    brightness == Brightness.dark
                        ? 'Dark Theme is ACTIVE'
                        : 'Light Theme is ACTIVE',
                    style: const TextStyle(fontSize: 20),
                ),
            ),
        );
    }
}

What is darkTheme: doing here?

This line is the key:

darkTheme: ThemeData.dark(),

Together with:

themeMode: ThemeMode.system,

It means:

System setting

App uses

Light mode

theme:

Dark mode

darkTheme:

So:

  • Turn your phone/computer to Dark Mode

  • Reload app β†’ UI becomes dark

  • Switch back to Light Mode β†’ UI becomes light

That’s all controlled from MaterialApp.router.

βœ… Important: Relationship of 3 things

theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system,

Setting

Meaning

theme

Light theme design

darkTheme

Dark theme design

themeMode

Which one to use

You can also force it:

themeMode: ThemeMode.dark   // Always dark
themeMode: ThemeMode.light  // Always light

Super simple meaning

darkTheme: is the design your app uses when system is in Dark Mode.


βœ…12.highContrastTheme:#

βœ… Simple program for highContrastTheme in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // Normal light theme
            theme: ThemeData.light(),

            // Dark theme
            darkTheme: ThemeData.dark(),

            // βœ… HIGH CONTRAST THEME (MAIN FOCUS)
            highContrastTheme: ThemeData(
                brightness: Brightness.light,
                primaryColor: Colors.black,
                scaffoldBackgroundColor: Colors.yellow,
                appBarTheme: const AppBarTheme(
                    backgroundColor: Colors.black,
                    foregroundColor: Colors.white,
                ),
                textTheme: const TextTheme(
                    bodyMedium: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        color: Colors.black,
                    ),
                ),
            ),

            // Use system setting (Light / Dark / High Contrast)
            themeMode: ThemeMode.system,

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(child: HomePage()),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(
        RouteInformation routeInformation) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        final isHighContrast = MediaQuery.of(context).highContrast;
        final brightness = Theme.of(context).brightness;

        return Scaffold(
            appBar: AppBar(
                title: const Text('High Contrast Demo'),
            ),
            body: Center(
                child: Text(
                    isHighContrast
                        ? 'HIGH CONTRAST MODE ACTIVE'
                        : brightness == Brightness.dark
                            ? 'DARK MODE ACTIVE'
                            : 'LIGHT MODE ACTIVE',
                    textAlign: TextAlign.center,
                    style: const TextStyle(fontSize: 22),
                ),
            ),
        );
    }
}

What does highContrastTheme: do?

This part is the key:

highContrastTheme: ThemeData(
    scaffoldBackgroundColor: Colors.yellow,
    appBarTheme: const AppBarTheme(
        backgroundColor: Colors.black,
        foregroundColor: Colors.white,
    ),
),

This tells Flutter:

β€œWhen the user turns on High Contrast Mode for accessibility, use THIS theme.”

So you see:
  • Bright background (yellow)

  • Strong contrast text (black / white)

  • Bigger & bolder text

This is for people with visual difficulties πŸ‘“

βœ… When is this theme used?

When the device accessibility setting is:

βœ… High Contrast ON ❌ Not normal light/dark switch

You can detect it with:

MediaQuery.of(context).highContrast

That’s how the app knows what to display.

Simple memory tip

Setting

Used when

theme

Normal + Light Mode

darkTheme

Dark Mode

βœ… highContrastTheme

Accessibility: High Contrast ON

highContrastTheme = special theme for accessibility users


βœ…13.highContrastDarkTheme:#

βœ… Simple program for highContrastDarkTheme

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

class MyApp extends StatelessWidget {
    MyApp({super.key});

    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // 🌞 Normal Light Theme
            theme: ThemeData.light(),

            // πŸŒ™ Normal Dark Theme
            darkTheme: ThemeData.dark(),

            // βœ… High Contrast (Light)
            highContrastTheme: ThemeData(
                brightness: Brightness.light,
                scaffoldBackgroundColor: Colors.yellow,
                appBarTheme: const AppBarTheme(
                backgroundColor: Colors.black,
                foregroundColor: Colors.white,
                ),
            ),

            // βœ… High Contrast (Dark) β€” MAIN FOCUS
            highContrastDarkTheme: ThemeData(
                brightness: Brightness.dark,
                scaffoldBackgroundColor: Colors.black,
                appBarTheme: const AppBarTheme(
                    backgroundColor: Colors.white,
                    foregroundColor: Colors.black,
                ),
                textTheme: const TextTheme(
                    bodyMedium: TextStyle(
                        color: Colors.white,
                        fontSize: 22,
                        fontWeight: FontWeight.bold,
                    ),
                ),
                elevatedButtonTheme: ElevatedButtonThemeData(
                    style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.white,
                        foregroundColor: Colors.black,
                    ),
                ),
            ),

            // Use system settings
            themeMode: ThemeMode.system,

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

@override
Widget build(BuildContext context) {
    return Navigator(
        key: navigatorKey,
        pages: const [
            MaterialPage(child: HomePage()),
        ],
    );
}

@override
Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(
        RouteInformation routeInformation) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    const HomePage({super.key});

    @override
    Widget build(BuildContext context) {
        final isHighContrast = MediaQuery.of(context).highContrast;
        final brightness = Theme.of(context).brightness;

        String mode;

        if (isHighContrast && brightness == Brightness.dark) {
            mode = 'HIGH CONTRAST DARK MODE ACTIVE';
            } else if (isHighContrast && brightness == Brightness.light) {
                mode = 'HIGH CONTRAST LIGHT MODE ACTIVE';
            } else if (brightness == Brightness.dark) {
                mode = 'NORMAL DARK MODE ACTIVE';
            } else {
                mode = 'NORMAL LIGHT MODE ACTIVE';
        }

        return Scaffold(
            appBar: AppBar(
                title: const Text('High Contrast Dark Demo'),
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                        Text(
                            mode,
                            textAlign: TextAlign.center,
                        ),
                        const SizedBox(height: 20),
                        ElevatedButton(
                            onPressed: () {},
                            child: const Text('Sample Button'),
                        ),
                    ],
                ),
            ),
        );
    }
}

What is highContrastDarkTheme in simple words?

highContrastDarkTheme is used when the user turns ON:

βœ… Dark Mode

βœ… High Contrast (in Accessibility)

This theme is created especially for:

  • People with low vision

  • Better readability

  • Strong color contrast

That’s why we used:

  • Black + White only

  • Large bold fonts

  • Strong contrast

βœ… When will this theme activate?

System Mode

High Contrast

Theme Used

Light

OFF

theme

Dark

OFF

darkTheme

Light

ON

highContrastTheme

βœ… Dark

βœ… ON

highContrastDarkTheme

Ultra-short memory tip

highContrastDarkTheme = Dark Mode + Extra Visibility


βœ…14.themeMode:#

This example lets you switch between Light, Dark, and System mode by pressing buttons β€” so you can visually understand exactly what themeMode does.

βœ… Simple program to describe themeMode: in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(MyApp());
}

/* ================= APP ================= */

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

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

class _MyAppState extends State<MyApp> {
    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    // πŸ‘‡ Change this to control theme
    ThemeMode _themeMode = ThemeMode.system;

    void setLightMode() {
        setState(() {
            _themeMode = ThemeMode.light;
        });
    }

    void setDarkMode() {
        setState(() {
            _themeMode = ThemeMode.dark;
        });
    }

    void setSystemMode() {
        setState(() {
            _themeMode = ThemeMode.system;
        });
    }

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // βœ… Light Theme
            theme: ThemeData(
                brightness: Brightness.light,
                primarySwatch: Colors.blue,
            ),

            // βœ… Dark Theme
            darkTheme: ThemeData(
                brightness: Brightness.dark,
                primarySwatch: Colors.blue,
            ),

            // πŸ‘‡ MAIN FOCUS OF THIS EXAMPLE
            themeMode: _themeMode,

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            // Pass buttons to HomePage
            builder: (context, child) {
                return HomePage(
                    setLight: setLightMode,
                    setDark: setDarkMode,
                    setSystem: setSystemMode,
                    child: child!,
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    @override
    final GlobalKey<NavigatorState> navigatorKey =
        GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(child: SizedBox()),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        return '/';
    }
}

/* ================= HOME ================= */

class HomePage extends StatelessWidget {
    final VoidCallback setLight;
    final VoidCallback setDark;
    final VoidCallback setSystem;
    final Widget child;

    const HomePage({
        super.key,
        required this.setLight,
        required this.setDark,
        required this.setSystem,
        required this.child,
    });

    @override
    Widget build(BuildContext context) {
        final brightness = Theme.of(context).brightness;

        return Scaffold(
            appBar: AppBar(title: const Text('themeMode Demo')),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                        Text(
                            brightness == Brightness.dark
                                ? 'CURRENT MODE: DARK'
                                : 'CURRENT MODE: LIGHT',
                            style: const TextStyle(fontSize: 20),
                        ),
                        const SizedBox(height: 40),

                        ElevatedButton(
                            onPressed: setLight,
                            child: const Text('Light Mode'),
                        ),

                        const SizedBox(height: 10),

                        ElevatedButton(
                            onPressed: setDark,
                            child: const Text('Dark Mode'),
                        ),

                        const SizedBox(height: 10),

                        ElevatedButton(
                            onPressed: setSystem,
                            child: const Text('System Mode'),
                        ),
                    ],
                ),
            ),
        );
    }
}

What does themeMode: do (simple words)?

This line is the key:

themeMode: _themeMode,

And _themeMode can be only 3 values:

ThemeMode.light   // Always light
ThemeMode.dark    // Always dark
ThemeMode.system  // Follow device setting

So:

Button

What happens

Light Mode

App becomes light

Dark Mode

App becomes dark

System Mode

App follows device setting

This is EXACTLY what themeMode controls:

πŸ‘‰ Which theme Flutter should use right now

βœ… Super short definition

themeMode: decides which theme (Light, Dark, or System) is ACTIVE.

  • theme = light design

  • darkTheme = dark design

  • themeMode = which one Flutter should use


βœ…15.themeAnimationDuration:#

This example lets you switch between light and dark themes, and the change happens slowly, so you can see the animation duration effect.

βœ… Simple program to demonstrate themeAnimationDuration

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

/* ================= APP ================= */

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

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

class _MyAppState extends State<MyApp> {
    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    ThemeMode _themeMode = ThemeMode.light;

    void toggleTheme() {
        setState(() {
        _themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
        });
    }

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // βœ… Light Theme
            theme: ThemeData.light(),

            // βœ… Dark Theme
            darkTheme: ThemeData.dark(),

            // πŸ‘‡ MAIN FOCUS
            themeMode: _themeMode,

            // πŸ‘‡ THIS is the parameter we're demonstrating
            themeAnimationDuration: const Duration(seconds: 2),

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            // Use builder to display UI
            builder: (context, child) {
                return Scaffold(
                    appBar: AppBar(title: const Text('themeAnimationDuration Demo')),
                    body: Center(
                        child: ElevatedButton(
                            onPressed: toggleTheme,
                            child: const Text('Toggle Theme'),
                        ),
                    ),
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return const Navigator(
            pages: [
                MaterialPage(
                    child: SizedBox(), // Actual UI is built in MaterialApp.router builder
                ),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        return '/';
    }
}

What does themeAnimationDuration do?

This is the key line:

themeAnimationDuration: const Duration(seconds: 2),

It means:

When the theme changes, the transition should take 2 seconds instead of happening instantly.

So when you press β€œToggle Theme”:
  • Without this β†’ instant change

  • With this β†’ smooth animated change in 2 seconds

You can try other values too:

Duration(milliseconds: 300)   // fast
Duration(seconds: 1)         // normal
Duration(seconds: 4)         // very slow

βœ… Super simple definition

themeAnimationDuration: controls how long the theme-change animation lasts.

Think of it like:

β€œHow fast should my app fade from light ↔ dark?”


βœ…16.themeAnimationCurve:#

This example lets you toggle between light and dark themes, and the change uses a specific animation curve (not just duration). So now you can see the animation style/shape of the transition.

βœ… Simple program to demonstrate themeAnimationCurve

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

/* ================= APP ================= */

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

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

class _MyAppState extends State<MyApp> {
    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    ThemeMode _themeMode = ThemeMode.light;

    // Toggle theme on button press
    void toggleTheme() {
        setState(() {
            _themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
        });
    }

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // Light + Dark themes
            theme: ThemeData.light(),
            darkTheme: ThemeData.dark(),

            themeMode: _themeMode,

            // βœ… Duration of animation
            themeAnimationDuration: const Duration(seconds: 2),

            // πŸ‘‡ MAIN FOCUS: CURVE OF THE ANIMATION
            themeAnimationCurve: Curves.easeInOut,

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            // UI is built here
            builder: (context, child) {
                return Scaffold(
                    appBar: AppBar(
                        title: const Text('themeAnimationCurve Demo'),
                    ),
                    body: Center(
                        child: ElevatedButton(
                            onPressed: toggleTheme,
                            child: const Text('Toggle Theme'),
                        ),
                    ),
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return const Navigator(
            pages: [
                MaterialPage(
                    child: SizedBox(), // UI is built by MaterialApp.builder
                ),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation) async {
        return '/';
    }
}

What does themeAnimationCurve do?

This is the key line in the program:

themeAnimationCurve: Curves.easeInOut,

It controls HOW the theme animation moves over time, not how long it takes.

The duration is defined here:

,. code-block:: dart

themeAnimationDuration: const Duration(seconds: 2),

And the shape of that animation is defined here:

themeAnimationCurve: Curves.easeInOut,
What Curves.easeInOut does:
  • Starts slowly

  • Speeds up in the middle

  • Ends slowly

So when you press Toggle Theme:
  • The app does NOT change instantly

  • It fades with a smooth curve

You can try others too:

Curves.linear      // straight, constant speed
Curves.easeIn      // slow -> fast
Curves.easeOut     // fast -> slow
Curves.bounceIn    // bounce at start
Curves.elasticOut  // rubber-band style

Example change:

themeAnimationCurve: Curves.bounceInOut,

Now your theme change will bounce πŸ˜„

βœ… Super simple definition (remember this)

themeAnimationCurve controls the STYLE of the theme-change animation.

While:

themeAnimationDuration = how long

themeAnimationCurve = how it moves


βœ…17.themeAnimationStyle:#

βœ… Simple program to describe themeAnimationStyle

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

/* ================= APP ================= */

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

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

class _MyAppState extends State<MyApp> {
    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    ThemeMode _themeMode = ThemeMode.light;

    void _toggleTheme() {
        setState(() {
            _themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
        });
    }

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // Normal light & dark themes
            theme: ThemeData.light(),
            darkTheme: ThemeData.dark(),
            themeMode: _themeMode,

            // πŸ‘‡ MAIN FOCUS: themeAnimationStyle
            // This overrides duration + curve of the theme change.
            themeAnimationStyle: const AnimationStyle(
                duration: Duration(seconds: 2),      // slow, so you can see it
                curve: Curves.easeInOutCubic,        // smooth curve
            ),

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            // Simple UI: one page with a toggle button
            builder: (context, child) {
                return Scaffold(
                    appBar: AppBar(
                        title: const Text('themeAnimationStyle Demo'),
                    ),
                    body: Center(
                        child: ElevatedButton(
                            onPressed: _toggleTheme,
                            child: Text(
                                _themeMode == ThemeMode.light
                                    ? 'Switch to DARK theme'
                                    : 'Switch to LIGHT theme',
                            ),
                        ),
                    ),
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        // We don’t really use pages here; UI is built in MaterialApp.builder.
        return const Navigator(
            pages: [
                MaterialPage(child: SizedBox()),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation,) async {
        // Use uri instead of deprecated location
        return routeInformation.uri.path;
    }
}

What is themeAnimationStyle actually doing?

This line is the key:

themeAnimationStyle: const AnimationStyle(
duration: Duration(seconds: 2),
curve: Curves.easeInOutCubic,
),
  • AnimationStyle.duration β†’ how long the theme change animation takes

  • AnimationStyle.curve β†’ how the animation moves over time

It overrides themeAnimationDuration and themeAnimationCurve if you set them.

So when you press the Toggle button:

  1. _themeMode switches between ThemeMode.light and ThemeMode.dark

  2. Flutter animates the change using:

  • 2-second duration

  • easeInOutCubic curve

  1. You see the whole screen smoothly fade/animate between light and dark.

If you want no animation at all, you can do:

themeAnimationStyle: AnimationStyle.noAnimation,

βœ…18.locale:#

Below is a working example using locale with Pashto (Locale(β€˜ps’)). It still uses MaterialApp.router and flutter_localizations.

Make sure flutter_localizations is in your pubspec.yaml (as before):

dependencies:
flutter:
    sdk: flutter
flutter_localizations:
    sdk: flutter

βœ… Simple program to describe locale: in MaterialApp.router

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
    runApp(const MyApp());
}

/* ================= APP ================= */

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

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

class _MyAppState extends State<MyApp> {
final MyRouterDelegate _routerDelegate = MyRouterDelegate();
final MyRouteInformationParser _routeInformationParser =
    MyRouteInformationParser();

// πŸ‘‡ Start in English
Locale _locale = const Locale('en');

void _setEnglish() {
    setState(() {
    _locale = const Locale('en');
    });
}

void _setPashto() {
    setState(() {
    _locale = const Locale('ps'); // Pashto
    });
}

@override
Widget build(BuildContext context) {
    return MaterialApp.router(
        debugShowCheckedModeBanner: false,

        // Current app locale
        locale: _locale,

        // We support English + Pashto
        supportedLocales: const [
            Locale('en'),
            Locale('ps'),
        ],

        // Provide Material / Widgets / Cupertino localizations
        localizationsDelegates: GlobalMaterialLocalizations.delegates,

        routerDelegate: _routerDelegate,
        routeInformationParser: _routeInformationParser,

            // Simple UI to show locale effect
            builder: (context, child) {
                final lang = _locale.languageCode;

                return Scaffold(
                    appBar: AppBar(
                        title: Text(
                        lang == 'en' ? 'Locale Demo (English / Pashto)'
                                        : 'Ψ― ژبې بېلګه (Ψ§Ω†Ϊ«Ω„ΫŒΨ³ΩŠ / پښΨͺو)',
                        ),
                    ),
                    body: Center(
                        child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                                Text(
                                    lang == 'en'
                                        ? 'Hello (English)'
                                        : 'Ψ³Ω„Ψ§Ω… (پښΨͺو)',
                                    style: const TextStyle(fontSize: 28),
                                    textAlign: TextAlign.center,
                                ),
                                const SizedBox(height: 30),
                                ElevatedButton(
                                    onPressed: _setEnglish,
                                    child: const Text('Switch to English'),
                                ),
                                const SizedBox(height: 10),
                                ElevatedButton(
                                    onPressed: _setPashto,
                                    child: const Text('پښΨͺو ΨͺΩ‡ ΩˆΨ§Ϊ“ΩˆΨ¦'),
                                ),
                            ],
                        ),
                    ),
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey =
        GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        // UI is built in MaterialApp.router.builder
        return const Navigator(
            pages: [
                MaterialPage(child: SizedBox()),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(
        RouteInformation routeInformation,
    ) async {
        // Use uri (new API, not deprecated)
        return routeInformation.uri.path;
    }
}

What you’ll see

  • Start: English – β€œHello (English)”

  • Tap β€œΩΎΪšΨͺو ΨͺΩ‡ ΩˆΨ§Ϊ“ΩˆΨ¦β€ β†’ locale becomes ps

  • AppBar title and main text show Pashto.

  • Direction switches to right-to-left automatically for Pashto (if your Flutter version/localizations support it).

You can now reuse this pattern later for English + Pashto + any other language just by adding more Locale(…) entries and updating the text.


βœ…19.supportedLocales:#

βœ… Simple program to describe supportedLocales in MaterialApp.router

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
    runApp(const MyApp());
}

/* ================= APP ================= */

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

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

class _MyAppState extends State<MyApp> {
    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();

    // CURRENT LOCALE
    Locale _locale = const Locale('en');

    void _setEnglish() {
        setState(() => _locale = const Locale('en'));
    }

    void _setPashto() {
        setState(() => _locale = const Locale('ps'));
    }

    void _setSpanish() {
        setState(() => _locale = const Locale('es'));
    }

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // πŸ‘‡ CURRENT SELECTED LANGUAGE
            locale: _locale,

            // βœ… MAIN FOCUS: supportedLocales
            supportedLocales: const [
                Locale('en'), // English
                Locale('ps'), // Pashto
                Locale('es'), // Spanish
            ],

            // Required for Material localized texts
            localizationsDelegates: GlobalMaterialLocalizations.delegates,

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            builder: (context, child) {
                final lang = _locale.languageCode;

                return Scaffold(
                    appBar: AppBar(
                        title: const Text('supportedLocales Demo'),
                    ),
                    body: Center(
                        child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                                Text(
                                    lang == 'en'
                                        ? 'Hello (English)'
                                        : lang == 'ps'
                                            ? 'Ψ³Ω„Ψ§Ω… (پښΨͺو)'
                                            : 'Hola (Spanish)',
                                    style: const TextStyle(fontSize: 28),
                                ),
                                const SizedBox(height: 40),

                                ElevatedButton(
                                    onPressed: _setEnglish,
                                    child: const Text('English'),
                                ),
                                const SizedBox(height: 8),

                                ElevatedButton(
                                    onPressed: _setPashto,
                                    child: const Text('پښΨͺو'),
                                ),
                                const SizedBox(height: 8),

                                ElevatedButton(
                                    onPressed: _setSpanish,
                                    child: const Text('EspaΓ±ol'),
                                ),
                            ],
                        ),
                    ),
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    @override
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return const Navigator(
            pages: [
                MaterialPage(child: SizedBox()),
            ],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(
        RouteInformation routeInformation) async {
        return routeInformation.uri.path;
    }
}

What does supportedLocales: actually do?

This is the key part:

supportedLocales: const [
    Locale('en'),
    Locale('ps'),
    Locale('es'),
],

It tells Flutter:

β€œMy app is able to support only these languages.”

If the user’s phone is set to:

  • French β†’ ❌ Not in list β†’ Flutter won’t use it

  • Pashto β†’ βœ… In list β†’ Flutter will use Pashto

  • Spanish β†’ βœ… In list β†’ Flutter will use Spanish

Think of it like a whitelist of languages your app allows.

βœ… Very short definition (remember this)

supportedLocales: = the list of languages your app officially supports

And:

locale: = the one currently in use

Parameter

Meaning

supportedLocales

All allowed languages

locale

The selected language


βœ…20.localizationsDelegates:#

This example proves one thing:

localizationsDelegates is what provides localized strings (like β€œBack”, dates, numbers, text direction, etc.) for the selected locale.

Without this, you will get the red error you saw before: β€œNo MaterialLocalizations found”

This example uses:

βœ… English

βœ… Pashto (پښΨͺو)

βœ… Spanish

βœ… Simple program to describe localizationsDelegates in MaterialApp.router

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
    runApp(const MyApp());
}

/* ================= APP ================= */

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

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

class _MyAppState extends State<MyApp> {
    final MyRouterDelegate _routerDelegate = MyRouterDelegate();
    final MyRouteInformationParser _routeInformationParser =
        MyRouteInformationParser();

    Locale _locale = const Locale('en');

    void _setEnglish() => setState(() => _locale = const Locale('en'));
    void _setPashto()  => setState(() => _locale = const Locale('ps'));
    void _setSpanish() => setState(() => _locale = const Locale('es'));

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,

            // Current app language
            locale: _locale,

            // Languages app supports
            supportedLocales: const [
                Locale('en'),
                Locale('ps'),
                Locale('es'),
            ],

            // βœ… MAIN FOCUS: THIS PROVIDES TRANSLATIONS
            localizationsDelegates: const [
                GlobalMaterialLocalizations.delegate, // Material widgets
                GlobalWidgetsLocalizations.delegate,  // Text direction (LTR/RTL)
                GlobalCupertinoLocalizations.delegate, // iOS style widgets
            ],

            routerDelegate: _routerDelegate,
            routeInformationParser: _routeInformationParser,

            builder: (context, child) {
                final code = _locale.languageCode;

                return Scaffold(
                    appBar: AppBar(
                        title: const Text('localizationsDelegates Demo'),
                    ),
                    body: Center(
                        child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                                Text(
                                    code == 'en'
                                        ? 'Hello (English)'
                                        : code == 'ps'
                                            ? 'Ψ³Ω„Ψ§Ω… (پښΨͺو)'
                                            : 'Hola (Spanish)',
                                    style: const TextStyle(fontSize: 26),
                                ),
                                const SizedBox(height: 40),

                                ElevatedButton(
                                    onPressed: _setEnglish,
                                    child: const Text('English'),
                                ),
                                const SizedBox(height: 8),

                                ElevatedButton(
                                    onPressed: _setPashto,
                                    child: const Text('پښΨͺو'),
                                ),
                                const SizedBox(height: 8),

                                ElevatedButton(
                                    onPressed: _setSpanish,
                                    child: const Text('EspaΓ±ol'),
                                ),
                            ],
                        ),
                    ),
                );
            },
        );
    }
}

/* ============ ROUTER DELEGATE ============ */

class MyRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {
    @override
    final GlobalKey<NavigatorState> navigatorKey =
        GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return const Navigator(
            pages: [MaterialPage(child: SizedBox())],
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

/* ============ ROUTE PARSER ============ */

class MyRouteInformationParser extends RouteInformationParser<Object> {
    @override
    Future<Object> parseRouteInformation(RouteInformation routeInformation,) async {
        return routeInformation.uri.path;
    }
}

What does localizationsDelegates REALLY do?

This is the most important part:

localizationsDelegates: const [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
],

This tells Flutter:

β€œHere are the objects that provide translations and regional settings

for Material, Widgets, and iOS (Cupertino) components.”

Without this:

❌ You get crash

❌ No RTL support for Pashto/Arabic

❌ No translated system labels (Back, Cancel, etc.)

With this:

βœ… Proper RTL for Pashto

βœ… Normal English / Spanish

βœ… No red error

βœ… Super simple meaning (remember forever)

localizationsDelegates = the translation engine of your app

Part

What it gives you

GlobalMaterialLocalizations

Buttons like Back, Close, etc

GlobalWidgetsLocalizations

RTL / LTR text direction

GlobalCupertinoLocalizations

iOS-style text

⚠️ Important rule

You almost ALWAYS use:

localizationsDelegates: GlobalMaterialLocalizations.delegates,

or the full version I gave you.

Without it β†’ your app WILL crash when you change locale.


βœ…21.localeResolutionCallback:#

Here is a very simple Flutter example that shows how to use the localeResolutionCallback parameter in MaterialApp.router.

This callback decides which language (locale) your app should actually use, based on the device locale and the supported locales in your app.

βœ… What this example does

  • The phone language might be: Spanish, Pashto, Arabic, etc.

  • We tell Flutter:

  • If the device language is Spanish (es), use Spanish

  • Otherwise, use English (en)

  • It prints the decision in the console so you can see what happens

βœ… Simple program: localeResolutionCallback in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

// A very small RouterConfig just for demo
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // List of supported languages in this app
            supportedLocales: const [
                Locale('en', 'US'),
                Locale('es', 'ES'),
            ],

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT
            localeResolutionCallback: (Locale? deviceLocale, Iterable<Locale> supportedLocales) {
                print('Device locale is: $deviceLocale');

                // If device language is Spanish, use Spanish
                if (deviceLocale != null && deviceLocale.languageCode == 'es') {
                    print('Using Spanish');
                    return const Locale('es', 'ES');
                }

                // Otherwise, use English as default
                print('Using English');
                return const Locale('en', 'US');
            },
        );
    }
}

// ---------------- SIMPLE ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(
                    child: Scaffold(
                        body: Center(
                            child: Text(
                                "Hello from MaterialApp.router()",
                                style: TextStyle(fontSize: 22),
                            ),
                        ),
                    ),
                ),
            ],
            onPopPage: (route, result) => route.didPop(result),
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

βœ… In simple words

localeResolutionCallback means:

β€œFlutter, when the app starts, check the phone’s language. Then I will decide which language my app should use.”

Example:

Phone Language

App Language Used

Spanish

Spanish βœ…

Pashto

English (fallback) βœ…

Arabic

English (fallback) βœ…


βœ…22.localeListResolutionCallback:#

This callback is used when the device sends a list of preferred languages (not just one). You decide which one your app will use.

βœ… What this example does

  • Your phone may send a list like: [es_ES, ps_AF, en_US]

  • The app:

  • βœ”οΈ Uses Pashto if found

  • βœ”οΈ Else uses Spanish if found

  • βœ”οΈ Else uses English by default

  • It prints the decision in the console

βœ… Simple program: localeListResolutionCallback in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

// Simple router for demo
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // Languages supported by this app
            supportedLocales: const [
                Locale('en', 'US'),
                Locale('es', 'ES'),
                Locale('ps', 'AF'), // Pashto
            ],

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT
            localeListResolutionCallback:
                (List<Locale>? deviceLocales, Iterable<Locale> supportedLocales) {
                print("Device locales list: $deviceLocales");

                if (deviceLocales != null) {
                    for (Locale locale in deviceLocales) {
                        // If Pashto is found, use it
                        if (locale.languageCode == 'ps') {
                            print("Using Pashto");
                            return const Locale('ps', 'AF');
                        }

                        // If Spanish is found, use it
                        if (locale.languageCode == 'es') {
                            print("Using Spanish");
                            return const Locale('es', 'ES');
                        }
                    }
                }

                // Default to English
                print("Using English");
                return const Locale('en', 'US');
            },
        );
    }
}

// ---------------- SIMPLE ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: const [
                MaterialPage(
                    child: Scaffold(
                        body: Center(
                            child: Text(
                                "MaterialApp.router\nlocaleListResolutionCallback",
                                textAlign: TextAlign.center,
                                style: TextStyle(fontSize: 20),
                            ),
                        ),
                    ),
                ),
            ],
            onPopPage: (route, result) => route.didPop(result),
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

βœ… Very simple explanation

Callback

What it receives

When to use

localeResolutionCallback

one Locale

When device gives one language

localeListResolutionCallback

list of Locales

When device gives multiple languages

localeListResolutionCallback =

β€œFlutter, here is a list of languages my user prefers. You choose the best one.”

Example device language list:

[ps_AF, en_US, es_ES]

Your app chooses: βœ… Pashto


βœ…23.debugShowCheckedModeBanner:#

πŸ”Ή What is debugShowCheckedModeBanner?

When you run a Flutter app in debug mode, a red label appears in the top-right corner that says:

DEBUG

That red label is called the debug banner.

The debugShowCheckedModeBanner parameter controls whether that banner is shown or hidden.

βœ… Example

MaterialApp.router(
    debugShowCheckedModeBanner: false,
    home: Scaffold(
        body: Center(child: Text('Hello')),
    ),
);

If you write:

debugShowCheckedModeBanner: false,

βœ… The red DEBUG banner will disappear.

If you write:

debugShowCheckedModeBanner: true,

βœ… The red DEBUG banner will appear.

In very simple words

debugShowCheckedModeBanner means:

β€œFlutter, should I show the red DEBUG label or not?”

Why do developers turn it OFF?

Because before publishing an app, they want the screen to look clean and professional:

  • ❌ No big DEBUG label

  • βœ… Clean UI for users

So usually developers write:

debugShowCheckedModeBanner: false,

Important

This banner:
  • Only appears in debug mode

  • Does NOT appear in release mode

  • Does NOT affect performance


βœ…24.debugShowMaterialGrid:#

Here is a very simple Flutter example that demonstrates the

debugShowMaterialGrid parameter in MaterialApp.router.

This parameter is used only in debug mode to show a layout grid on the screen, which helps you align widgets correctly (like a designer’s grid).

βœ… What this example shows

  • A grid overlay appears on the screen

  • Helps you see spacing, margins & alignment

  • Only works in debug mode (when using flutter run)

βœ… Simple Program: debugShowMaterialGrid in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

// Simple router for the demo
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            debugShowCheckedModeBanner: false,
            routerConfig: _router,

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT
            debugShowMaterialGrid: true,
        );
    }
}

// ---------------- SIMPLE ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: [
                MaterialPage(
                    child: Scaffold(
                        appBar: AppBar(title: Text("Material Grid Example")),
                        body: Center(
                            child: Text(
                                "debugShowMaterialGrid = true",
                                style: TextStyle(fontSize: 20),
                            ),
                        ),
                    ),
                ),
            ],
            onPopPage: (route, result) => route.didPop(result),
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

βœ… What you will see

When you run the app in debug mode, a grid like this will appear:

β–’ β–’ β–’ β–’ β–’
β–’ β–’ β–’ β–’ β–’
β–’ β–’ β–’ β–’ β–’

It helps in:

βœ… Checking widget alignment

βœ… Checking padding / margins

βœ… UI design fixing

Turn it OFF by changing:

debugShowMaterialGrid: false,

βœ… In simple words

Value

Result

true

Shows layout grid on screen βœ…

false

No grid (normal UI) βœ…

debugShowMaterialGrid =

β€œFlutter, show me your layout lines so I can design better.”


βœ…25.showPerformanceOverlay:#

Here’s a very simple Flutter app that demonstrates

showPerformanceOverlay **in **MaterialApp.router.

This overlay shows performance graphs (GPU & UI usage) on the screen, so you can see how smooth (or slow) your app is while running.

βœ… What this example does

  • Shows two small graphs at the top:

  • UI Thread (top)

  • GPU Thread (bottom)

  • Helps you check:

  • Frame drops

  • Jank / lag

  • Animation performance

  • Works in debug mode

βœ… Simple program: showPerformanceOverlay in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

// Simple router for the demo
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // Shows the performance overlay
            showPerformanceOverlay: true,
        );
    }
}

// ---------------- SIMPLE ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: [
                MaterialPage(
                    child: Scaffold(
                        appBar: AppBar(title: Text("Performance Overlay")),
                        body: Center(
                            child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                    Text(
                                        "showPerformanceOverlay = true",
                                        style: TextStyle(fontSize: 20),
                                    ),
                                    SizedBox(height: 20),
                                    CircularProgressIndicator(),
                                ],
                            ),
                        ),
                    ),
                ),
            ],

            // βœ… NEW and CORRECT method (replaces onPopPage)
            onDidRemovePage: (Page<Object?> page) {
                debugPrint('Page removed: ${page.key}');
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

βœ… What you will see on top

Two small bars like this:

UI:  β–‡β–…β–‡β–‚β–‡β–…
GPU: β–…β–‡β–‚β–‡β–…β–‚

Bar

Meaning

Top bar

UI rendering time

Bottom bar

GPU rendering time

If bars go above the line β†’ ⚠️ your app is becoming slow.

βœ… In very simple words

showPerformanceOverlay =

β€œFlutter, show me how fast or slow my app is running.”

Value

Result

true

Shows performance graph βœ…

false

Normal mode βœ…


βœ…26.showSemanticsDebugger:#

Here is a very simple & updated Flutter program that demonstrates

the showSemanticsDebugger parameter in MaterialApp.router β€” using the new API (onDidRemovePage) instead of the deprecated onPopPage.

showSemanticsDebugger is mainly used for accessibility debugging (screen readers, talkback, etc.).

βœ… Simple program: showSemanticsDebugger in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

// Simple router for demo
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT
            showSemanticsDebugger: true,
        );
    }
}

// ---------------- SIMPLE ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,

            pages: [
                MaterialPage(
                    child: Scaffold(
                        appBar: AppBar(title: Text("Semantics Debugger")),
                        body: Center(
                            child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                    Text(
                                        "showSemanticsDebugger = true",
                                        style: TextStyle(fontSize: 20),
                                    ),
                                    SizedBox(height: 20),
                                    ElevatedButton(
                                        onPressed: null,
                                        child: Text("Example Button"),
                                    ),
                                ],
                            ),
                        ),
                    ),
                ),
            ],

            // βœ… New API instead of onPopPage
            onDidRemovePage: (Page<Object?> page) {
                debugPrint('Page removed: ${page.key}');
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

βœ… What you will see on the screen

When this runs in debug mode, you’ll see:

  • Yellow / green outlines on widgets

  • Text showing things like:

  • Button

  • Label

  • Text

  • Boxes around UI elements

This is Flutter showing accessibility information.

This is useful for:

βœ… Screen readers

βœ… Blind/low-vision users

βœ… Accessibility testing

βœ… Improving UX

βœ… In very simple words

showSemanticsDebugger = true means:

β€œFlutter, show me how screen readers see my app.”

Value

Result

true

Shows accessibility info on UI βœ…

false

Normal screen βœ…


βœ…27.shortcuts:#

Here is a very simple Flutter example that demonstrates the shortcuts parameter in MaterialApp.router.

The shortcuts parameter lets you define keyboard shortcut keys (for hardware keyboard: laptop, desktop, tablet with keyboard).

In this example:

  • Press Ctrl + B (or Cmd + B on Mac)

  • The text on the screen changes

βœ… Simple program: shortcuts in MaterialApp.router

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
    runApp(const MyApp());
}

// Simple router for demo
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT
            shortcuts: <LogicalKeySet, Intent>{
                // When user presses Ctrl + B
                LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyB):
                    const ChangeTextIntent(),
            },
        );
    }
}

// ----------- INTENT (WHAT TO DO) ----------------

class ChangeTextIntent extends Intent {
    const ChangeTextIntent();
}

// ----------- SIMPLE ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
    bool _changed = false;

    @override
    Widget build(BuildContext context) {
        return Actions(
        actions: <Type, Action<Intent>>{
            ChangeTextIntent: CallbackAction<ChangeTextIntent>(
                onInvoke: (intent) {
                    _changed = !_changed;
                    notifyListeners();
                    return null;
                },
            ),
        },
        child: Navigator(
                key: navigatorKey,
                pages: [
                    MaterialPage(
                        child: Scaffold(
                            appBar: AppBar(title: const Text("Shortcuts Example")),
                            body: Center(
                                child: Text(
                                    _changed
                                        ? "You pressed Ctrl + B βœ…"
                                        : "Press Ctrl + B to change this text",
                                    style: const TextStyle(fontSize: 20),
                                ),
                            ),
                        ),
                    ),
                ],

                // βœ… New API instead of onPopPage
                onDidRemovePage: (Page<Object?> page) {
                    debugPrint('Page removed: ${page.key}');
                },
            ),
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

βœ… How to test it

Run your app and press:

  • Windows / Linux: Ctrl + B

  • Mac: ⌘ (Command) + B

You will see the text change on the screen.

βœ… In very simple words

shortcuts means:

β€œFlutter, when the user presses these keys, do this action.”

Part

Meaning

LogicalKeySet(…)

Which keys

Intent

What it means

Action

What to do


βœ…28.actions:#

Here is a very simple Flutter program that demonstrates the actions parameter in MaterialApp.router.

The actions parameter tells Flutter what to do when an Intent happens (for example from a keyboard shortcut, button, or gesture).

In this example:

βœ… Press Ctrl + C **(or **Cmd + C on Mac)

βœ… The text on the screen changes

βœ… Simple program: actions in MaterialApp.router

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
    runApp(const MyApp());
}

// Simple router
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

@override
Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // πŸ‘‡ SHORTCUT (Ctrl + C)
            shortcuts: <LogicalKeySet, Intent>{
                LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC):
                ChangeColorIntent(),
            },

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT: actions
            actions: <Type, Action<Intent>>{
                ChangeColorIntent: ChangeColorAction(),
            },
        );
    }
}

// ------------ INTENT ------------

class ChangeColorIntent extends Intent {
    const ChangeColorIntent();
}

// ------------ ACTION ------------

class ChangeColorAction extends Action<ChangeColorIntent> {
    @override
    Object? invoke(ChangeColorIntent intent) {
        _SimpleRouterDelegate.changeColor();
        return null;
    }
}

// ------------ ROUTER ------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    static bool _isRed = false;

    static void changeColor() {
        _isRed = !_isRed;
        _instance?.notifyListeners();
    }

    static _SimpleRouterDelegate? _instance;

    _SimpleRouterDelegate() {
        _instance = this;
    }

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            pages: [
                MaterialPage(
                    child: Scaffold(
                        appBar: AppBar(title: const Text("Actions Example")),
                        body: Center(
                            child: Text(
                                "Press Ctrl + C",
                                style: TextStyle(
                                    fontSize: 22,
                                    color: _isRed ? Colors.red : Colors.black,
                                ),
                            ),
                        ),
                    ),
                ),
            ],

            // βœ… New API instead of onPopPage
            onDidRemovePage: (Page<Object?> page) {
                debugPrint('Page removed: ${page.key}');
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

βœ… What is happening (very simple)

Part

Meaning

Intent

What the user wants

Action

What the app does

actions:

Connects intent ➜ action

actions = β€œWhen this thing happens, do this job”

βœ… Press the keys

  • Windows / Linux: Ctrl + C

  • Mac: ⌘ + C

Text color will change:
  • Black ➜ Red ➜ Black ➜ Red …


βœ…29.restorationScopeId:#

Here is a very simple Flutter program that demonstrates the restorationScopeId **parameter in **MaterialApp.router.

This feature lets Flutter restore the app’s state automatically (like text, scroll position, counters) after the app is closed and reopened or killed by the OS.

In this example:

βœ… A counter value is saved automatically

βœ… Close the app

βœ… Open it again β†’ the counter is restored

βœ… Simple program: restorationScopeId in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

// Simple router
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // βœ… ONLY ONE restorationScopeId
            restorationScopeId: 'root',
        );
    }
}

// ---------------- ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,
            restorationScopeId: 'navigator', // child scope

            pages: const [
                MaterialPage(
                    restorationId: 'home_page',
                    child: CounterPage(),
                ),
            ],

            // New API instead of onPopPage
            onDidRemovePage: (Page<Object?> page) {
                debugPrint('Page removed: ${page.key}');
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

// ---------------- COUNTER PAGE ----------------

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

    @override
    State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> with RestorationMixin {

    final RestorableInt _counter = RestorableInt(0);

    @override
    String? get restorationId => 'counter_page';

    @override
    void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
        registerForRestoration(_counter, 'counter_value');
    }

    void _increment() {
        setState(() {
        _counter.value++;
        });
    }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text('Restoration Example')),
            body: Center(
                child: Text(
                    'Counter: ${_counter.value}',
                    style: const TextStyle(fontSize: 30),
                ),
            ),
            floatingActionButton: FloatingActionButton(
                onPressed: _increment,
                child: const Icon(Icons.add),
            ),
        );
    }
}

βœ… Why this now works

Location

What it does

MaterialApp.router

Defines root restoration ID

Navigator

Defines child restoration area

MaterialPage

Has page restoration ID

RestorableInt

Saves counter value

Now your app can:

βœ… Close

βœ… Reopen

βœ… Restore previous counter

In simple words (again)

restorationScopeId is like giving a memory name to your app.

Flutter: β€œRemember everything under this name.”

Only one name per widget is allowed.


βœ…30.scrollBehavior:#

Here is a very simple Flutter example that demonstrates the scrollBehavior parameter in MaterialApp.router.

scrollBehavior changes how scrolling works (with mouse, touch, stylus, trackpad, etc.). In this example, we customize it so mouse dragging also scrolls the page on desktop.

βœ… Simple program: scrollBehavior in MaterialApp.router

import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';

void main() {
    runApp(const MyApp());
}

// Simple router
    final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT
            scrollBehavior: const MyCustomScrollBehavior(),
        );
    }
}

// -------- CUSTOM SCROLL BEHAVIOR --------

class MyCustomScrollBehavior extends MaterialScrollBehavior {
    const MyCustomScrollBehavior();

    @override
    Set<PointerDeviceKind> get dragDevices => const {
            PointerDeviceKind.touch,   // Mobile touch
            PointerDeviceKind.mouse,   // Desktop mouse drag
            PointerDeviceKind.trackpad,
            PointerDeviceKind.stylus,
        };
    }

    // -------- SIMPLE ROUTER --------

    class _SimpleRouterDelegate extends RouterDelegate<Object>
        with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,

            pages: const [
                MaterialPage(
                child: ScrollPage(),
                ),
            ],

            onDidRemovePage: (Page<Object?> page) {
                debugPrint('Page removed: ${page.key}');
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

// -------- PAGE WITH SCROLL --------

class ScrollPage extends StatelessWidget {
    const ScrollPage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text("Scroll Behavior Example")),
            body: ListView.builder(
                itemCount: 50,
                itemBuilder: (context, index) {
                    return ListTile(
                        title: Text("Item ${index + 1}"),
                    );
                },
            ),
        );
    }
}

βœ… What you will notice

Run the app and try to scroll:

Behavior

Works?

Touch screen

βœ…

Mouse wheel

βœ…

Mouse drag

βœ… (because of custom scrollBehavior)

Trackpad

βœ…

Without scrollBehavior, mouse drag scrolling usually does NOT work.

βœ… In very simple words

scrollBehavior =

β€œFlutter, I want to customize how scrolling works in my app.”

Example change

Result

Add PointerDeviceKind.mouse

Drag with mouse βœ…

Remove it

Only wheel works ❌

Add stylus

Stylus dragging βœ…


βœ…31.onNavigationNotification:#

Here is a very simple and clean Flutter example that demonstrates the onNavigationNotification parameter in MaterialApp.router.

This parameter lets your app listen to navigation events (like page change, back button press, push/pop) and react to them.

In this example:

βœ… When navigation happens, a message is printed in the console

βœ… And a small text on screen changes

βœ… Simple program: onNavigationNotification in MaterialApp.router

import 'package:flutter/material.dart';

void main() {
    runApp(const MyApp());
}

// Simple Router
final RouterConfig<Object> _router = RouterConfig(
    routerDelegate: _SimpleRouterDelegate(),
);

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

    @override
    Widget build(BuildContext context) {
        return MaterialApp.router(
            routerConfig: _router,

            // πŸ‘‡ THIS IS WHAT YOU ASKED ABOUT
            onNavigationNotification: (NavigationNotification notification) {
                debugPrint('Navigation happened: ${notification.runtimeType}');
                return true; // means "I handled it"
            },
        );
    }
}

// ---------------- ROUTER ----------------

class _SimpleRouterDelegate extends RouterDelegate<Object> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Object> {

    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

    bool _secondPage = false;

    void goToNext() {
        _secondPage = true;
        notifyListeners();
    }

    void goBack() {
        _secondPage = false;
        notifyListeners();
    }

    @override
    Widget build(BuildContext context) {
        return Navigator(
            key: navigatorKey,

            pages: [
                MaterialPage(
                    child: HomePage(onNext: goToNext),
                ),
                if (_secondPage)
                MaterialPage(
                    child: SecondPage(onBack: goBack),
                ),
            ],

            // βœ… New API
            onDidRemovePage: (Page<Object?> page) {
                debugPrint("Page removed: ${page.key}");
            },
        );
    }

    @override
    Future<void> setNewRoutePath(Object configuration) async {}
}

// ---------------- HOME PAGE ----------------

class HomePage extends StatelessWidget {
    final VoidCallback onNext;

    const HomePage({super.key, required this.onNext});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text("Home Page")),
            body: Center(
                child: ElevatedButton(
                onPressed: onNext,
                child: const Text("Go to Next Page"),
                ),
            ),
        );
    }
}

// ---------------- SECOND PAGE ----------------

class SecondPage extends StatelessWidget {
    final VoidCallback onBack;

    const SecondPage({super.key, required this.onBack});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text("Second Page")),
            body: Center(
                child: ElevatedButton(
                onPressed: onBack,
                child: const Text("Go Back"),
                ),
            ),
        );
    }
}

βœ… What you will see in the console

When you click the button to navigate:

Navigation happened: NavigationNotification

And when a page is removed:

Page removed: null

βœ… In very simple words

onNavigationNotification means:

β€œFlutter, tell me whenever navigation happens in my app.”

You can use it for:

βœ… Debugging navigation

βœ… Analytics (track page change)

βœ… Custom logging

βœ… Security checks

βœ… Quick comparison

Parameter

Purpose

onNavigationNotification

Listen to navigation events

onDidRemovePage

Know when a page is removed

routerConfig

Controls which page is shown