Flutter Navigation Common Util Class

A utility class for common navigation functions in Flutter applications, including navigating to new routes, replacing routes, removing previous routes, and handling navigation errors.

util / navigation_utils.dart

import 'package:flutter/material.dart';

class NavigationUtil {
  /// Navigates to a new route.
  ///
  /// The `routeName` parameter specifies the name of the route to navigate to.
  /// The `arguments` parameter (optional) provides additional arguments to pass to the new route.
  static void navigateTo(BuildContext context, String routeName, {Object? arguments}) {
    try {
      Navigator.pushNamed(context, routeName, arguments: arguments);
    } catch (error) {
      debugPrint('Navigation Error: $error');
      // Handle the error as needed
    }
  }

  /// Navigates to a new route asynchronously and returns a result from the destination screen.
  ///
  /// The `routeName` parameter specifies the name of the route to navigate to.
  /// The `arguments` parameter (optional) provides additional arguments to pass to the new route.
  ///
  /// Returns the result returned from the destination screen, if any.
  static Future<dynamic> navigateToAsync(BuildContext context, String routeName, {Object? arguments}) async {
    try {
      return await Navigator.pushNamed(context, routeName, arguments: arguments);
    } catch (error) {
      debugPrint('Navigation Error: $error');
      return null; // Handle the error as needed
    }
  }

  /// Navigates to a new route and replaces the current route in the navigation stack.
  ///
  /// The `routeName` parameter specifies the name of the route to navigate to.
  /// The `arguments` parameter (optional) provides additional arguments to pass to the new route.
  static void navigateToReplacement(BuildContext context, String routeName, {Object? arguments}) {
    try {
      Navigator.pushReplacementNamed(context, routeName, arguments: arguments);
    } catch (error) {
      debugPrint('Navigation Error: $error');
      // Handle the error as needed
    }
  }

  /// Navigates to a new route and removes all previous routes from the navigation stack.
  ///
  /// The `routeName` parameter specifies the name of the route to navigate to.
  /// The `arguments` parameter (optional) provides additional arguments to pass to the new route.
  static void navigateToAndRemoveUntil(BuildContext context, String routeName, {Object? arguments}) {
    try {
      Navigator.pushNamedAndRemoveUntil(
        context,
        routeName,
            (Route<dynamic> route) => false, // Remove all previous routes
        arguments: arguments,
      );
    } catch (error) {
      debugPrint('Navigation Error: $error');
      // Handle the error as needed
    }
  }

  /// Navigates back to the previous route and optionally returns a result to the calling screen.
  ///
  /// The `context` parameter specifies the BuildContext of the current screen.
  /// The `result` parameter (optional) is the value to be returned as the result of the previous route.
  static void navigateBack(BuildContext context, {Object? result}) {
    try {
      Navigator.pop(context, result);
    } catch (error) {
      debugPrint('Navigation Error: $error');
      // Handle the error as needed
    }
  }
}

..

Here is an example of how to use the util file in a real-time scenario:

import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter_route/util/navigation_utils.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      onGenerateRoute: _generateRoute,
      onUnknownRoute: _unknownRouteHandler,

      builder: (BuildContext context, Widget? child) {
        ErrorWidget.builder = (FlutterErrorDetails errorDetails) {
          return const Scaffold(
            body: Center(
              child: Text(
                'Error occurred. Please try again.',
                style: TextStyle(fontSize: 24),
              ),
            ),
          );
        };
        return child!;
      },

      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
    );
  }

  Route<dynamic> _generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (context) => const DemoScreen());
      case '/screen1':
        return MaterialPageRoute(builder: (context) => const Screen1());
      case '/screen2':
        final Map<String, dynamic>? arguments = settings.arguments as Map<String, dynamic>?;

        return MaterialPageRoute(
          builder: (context) => Screen2(
            id: arguments?['id'] as int?,
            name: arguments?['name'] as String?,
            email: arguments?['email'] as String?,
          ),
        );
      case '/screen3':
        return MaterialPageRoute(builder: (context) => const Screen3());
      case '/screen4':
        return MaterialPageRoute(builder: (context) => const Screen4());
      case '/screen_error':
        return MaterialPageRoute(builder: (context) => const ScreenError(items: []));

        default:
        return _unknownRouteHandler(settings);
    }
  }

  Route<dynamic> _unknownRouteHandler(RouteSettings settings) {
    return MaterialPageRoute(
      builder: (context) => const Scaffold(
        body: Center(
          child: Text(
            '404 - Page Not Found',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ),
    );
  }
}

class DemoScreen extends StatelessWidget {
  const DemoScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {


    log('Logging an error', error: 'Some error occurred');



    return Scaffold(
      appBar: AppBar(title: const Text('Navigation Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/screen1');
              },
              child: const Text('Navigate to Screen 1'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/screen2', arguments: {
                  'id': 123,
                  'name': 'John Doe',
                  'email': 'johndoe@example.com',
                });
              },
              child: const Text('Navigate to Screen 2'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/screen3');
              },
              child: const Text('Navigate to Screen 3 (Replace)'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamedAndRemoveUntil(context, '/screen4', (route) => false);
              },
              child: const Text('Navigate to Screen 4 (Remove)'),
            ),

            ElevatedButton(
              onPressed: () {
                // Navigate back to the previous route with a result
                NavigationUtil.navigateBack(context, result: true);
              },
              child: const Text('Navigate Back'),
            ),

            // ErrorBoundary widget for handling errors
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/screen_error');

              },
              child: const Text('RangeError (index): Invalid value'),
            ),


            ElevatedButton(
              onPressed: () {
                _showProductOptions(context);
              },
              child: const Text('Options'),
            )

          ],
        ),
      ),
    );
  }
}
void _showProductOptions(BuildContext context) {
  showModalBottomSheet(
    context: context,
    builder: (BuildContext context) {
      return Column(
        children: [
          ListTile(
            leading: const Icon(Icons.shopping_cart),
            title: const Text('Add to Cart'),
            onTap: () {
              // Add the product to the cart
              // Navigator.pop(context); // Close the bottom sheet
              NavigationUtil.navigateBack(context, result: true);
            },
          ),
          ListTile(
            leading: const Icon(Icons.info),
            title: const Text('View Details'),
            onTap: () {
              // Navigate to the product details screen
              // Navigator.pushNamed(context, '/screen4');
              Navigator.pushNamed(context, '/screen4');
            },
          ),
        ],
      );
    },
  );
}

class Screen1 extends StatelessWidget {
  const Screen1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {


    return Scaffold(
      appBar: AppBar(title: const Text('Screen 1')),
      body: const Center(
        child: Text('This is Screen 1'),
      ),
    );
  }
}
class Screen2 extends StatelessWidget {
  final int? id;
  final String? name;
  final String? email;

  const Screen2({Key? key, this.id, this.name, this.email}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Screen 2')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('ID: $id'),
            Text('Name: $name'),
            Text('Email: $email'),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context, 'Data returned from Screen 2');
              },
              child: const Text('Go to previous page'),
            ),
          ],
        ),
      ),
    );
  }
}
class Screen3 extends StatelessWidget {
  const Screen3({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Screen 3')),
      body: const Center(
        child: Text('This is Screen 3'),
      ),
    );
  }
}
class Screen4 extends StatelessWidget {
  const Screen4({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Screen 4')),
      body: const Center(
        child: Text('This is Screen 4'),
      ),
    );
  }
}

class ScreenError extends StatelessWidget {
  final List<String> items;

  const ScreenError({Key? key, required this.items}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final String item = items[0];

    return Scaffold(
      appBar: AppBar(title: const Text('Screen 1')),
      body: Center(
        child: Text('First Item: $item'),
      ),
    );
  }
}

..

..

Comments