A Nice way to Navigate in Flutter without Context.

A Nice way to Navigate in Flutter without Context.

#flutter #flutternavigation

Picking the right title was a little bit harder than I thought, but here we go.

When building a mobile application or any web application at all, developers might worry about switching from one screen to the other. Often it is easier with web development when you can easily slap on an anchor tag and you are on your way to a new page or screen for building basic HTML pages or using the Link tag when building with react.

Mobile applications don't have this luxury.

If you here, it means you understand how Navigator works in Flutter, and you are looking for the next best thing.

Now down to what we are here for, Flutter navigation. Flutter gives us a way to navigate to other screens using the ;

Navigator.of(context).pushNamed("routeNmae");
Navigator.of(context).pop();
Navigator.of(context).popAndPushNamed("routeNmae");
Navigator.of(context).pushRepacement(MaterialPageRoute(builder: (BuildContext context)=> MyHomePage()));

Above are some ways you could navigate to other screens. the downside to this is that it has to be called within the widget tree and or the context gets passed to a function like this;

 void pleaseNavigate(context) {
    Navigator.of(context).pushNamed("routeName");
  }

and it gets called like this;

pleaseNavigate(context);

either way, the context still needs to be made available for the Navigator Class.

There is absolutely nothing wrong with this method, but as you grow, it becomes less convenient especially when you start to separate UI logic from business logic when building mobile applications. This helps for easy debugging of your application.

I am a fan of services we are going to create a NavigationService Class, and classes in dart are very easy to create.

class NavigationService {}

The next step is to convert the class to a singleton. This allows you to maintain one single instance of your class throughout your application's running and maintain any state the class has within that period.


class NavigationService {
  /// Creating the first instance
  static final NavigationService _instance = NavigationService._internal();
  NavigationService._internal();

  /// With this factory setup, any time  NavigationService() is called
  /// within the appication _instance will be returned and not a new instance
  factory NavigationService() => _instance;
}

The next would be to add our final GlobalKey with type NavigatorState

  final GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();
import 'package:flutter/material.dart';

class NavigationService {
  /// Creating the first instance
  static final NavigationService _instance = NavigationService._internal();
  NavigationService._internal();

  /// With this factory setup, any time  NavigationService() is called
  /// within the appication _instance will be returned and not a new instance
  factory NavigationService() => _instance;

  ///This would allow the app to monitor the current screen state during navigation.
  ///
  ///This is where the singleton setup we did
  ///would help as the state is internally maintained
  final GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();
}

the next step is to add some navigation methods so we can call them when we need to navigate.


  /// For navigating back to the previous screen
  dynamic goBack([dynamic popValue]) {
    return navigationKey.currentState.pop(popValue);
  }

  /// This allows you to naviagte to the next screen by passing the screen widget
  Future<dynamic> navigateToScreen(Widget page, {arguments}) async => navigationKey.currentState.push(
        MaterialPageRoute(
          builder: (_) => page,
        ),
      );

  /// This allows you to naviagte to the next screen and
  /// also replace the current screen by passing the screen widget
  Future<dynamic> replaceScreen(Widget page, {arguments}) async => navigationKey.currentState.pushReplacement(
        MaterialPageRoute(
          builder: (_) => page,
        ),
      );

  /// Allows you to pop to the first screen to when the app first launched.
  /// This is useful when you need to log out a user,
  /// and also remove all the screens on the navigation stack.
  /// I find this very useful
  void popToFirst() => navigationKey.currentState.popUntil((route) => route.isFirst);

Now Putting everything together we get this magical class.

import 'package:flutter/material.dart';

class NavigationService {
  /// Creating the first instance
  static final NavigationService _instance = NavigationService._internal();
  NavigationService._internal();

  /// With this factory setup, any time  NavigationService() is called
  /// within the appication _instance will be returned and not a new instance
  factory NavigationService() => _instance;

  ///This would allow the app to monitor the current screen state during navigation.
  ///
  ///This is where the singleton setup we did
  ///would help as the state is internally maintained
  final GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();

  /// For navigating back to the previous screen
  dynamic goBack([dynamic popValue]) {
    return navigationKey.currentState.pop(popValue);
  }

  /// This allows you to naviagte to the next screen by passing the screen widget
  Future<dynamic> navigateToScreen(Widget page, {arguments}) async => navigationKey.currentState.push(
        MaterialPageRoute(
          builder: (_) => page,
        ),
      );

  /// This allows you to naviagte to the next screen and
  /// also replace the current screen by passing the screen widget
  Future<dynamic> replaceScreen(Widget page, {arguments}) async => navigationKey.currentState.pushReplacement(
        MaterialPageRoute(
          builder: (_) => page,
        ),
      );

  /// Allows you to pop to the first screen to when the app first launched.
  /// This is useful when you need to log out a user,
  /// and also remove all the screens on the navigation stack.
  /// I find this very useful
  void popToFirst() => navigationKey.currentState.popUntil((route) => route.isFirst);
}

Now the final step is to attach the navigation service to your application. This can be done in your main.dart file, where the material app was created,

We would need to attach the navigationKey in our class to the navigatorKey property of the MaterialApp like this;


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.purple,
      ),
      home: MyHomePage(title: 'Navigation Demo Home Page'),
      navigatorKey: NavigationService().navigationKey,
    );
  }
}

And that's all you need to set up to enjoy navigating without carrying any context along. Who doesn't like a light trip? Now to use this service class anywhere in your application, all you have to do is ;

NavigationService().navigateToScreen(MyHomePage());

NavigationService().replaceScreen(MyHomePage());

NavigationService().goBack();

NavigationService().popToFirst();

And that's all. I hope this helps someone in their journey to become a productive developer. Cheers. You can check out the Github repo HERE