sailor 0.7.0

  • Readme
  • Changelog
  • Example
  • Installing
  • 83

sailor #

anchor_image

License: MIT pub_package

A Flutter package for easy navigation management.

Warning: Package is still under development, there might be breaking changes in future.

Index #

Setup and Usage #

  1. Create an instance of Sailor and add routes.
// Routes class is created by you.
class Routes {
  static final sailor = Sailor();

  static void createRoutes() {
    sailor.addRoute(SailorRoute(
        name: "/secondPage",
        builder: (context, args, params) {
          return SecondPage();
        },
      ));
  }
}
  1. Register the routes in onGenerateRoute using the generate function of Sailor and also Sailor's navigatorKey.
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sailor Example',
      home: Home(),
      navigatorKey: Routes.sailor.navigatorKey,  // important
      onGenerateRoute: Routes.sailor.generator(),  // important
    );
  }
}
  1. Make sure to create routes before starting the application.
void main() async {
  Routes.createRoutes();
  runApp(App());
}
  1. Use the instance of Sailor to navigate.
Routes.sailor.navigate("/secondPage");
  • TIP: Sailor is a callable class, so you can omit navigate and directly call the method.
Routes.sailor("/secondPage");

Passing Parameters #

Sailor allows you to pass parameters to the page that you are navigating to.

  • Before passing the parameter itself, you need to declare it while declaring your route. Let's declare a parameter named id that has a default value of 1234.
sailor.addRoutes([
  SailorRoute(
    name: "/secondPage",
    builder: (context, args, params) => SecondPage(),
    params: [
      SailorParam<int>(
        name: 'id',
        defaultValue: 1234,
      ),
    ],
  ),
);
  • Pass the actual parameter when navigating to the new route.
Routes.sailor.navigate<bool>("/secondPage", params: {
  'id': 4321,
});
  • Parameters can be retrieved from two places, first, the route builder and second, the opened page itself.

Route Builder:

sailor.addRoutes([
  SailorRoute(
    name: "/secondPage",
    builder: (context, args, params) {
      // Getting a param
      final id = params.param<int>('id');
      return SecondPage();
    },
    params: [
      SailorParam(
        name: 'id',
        defaultValue: 1234,
      ),
    ],
  ),
);

Opened page:

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final id = Sailor.param<int>(context, 'id');

    ...

  }
}

Make sure to specify the type of paramter when declaring SailorParam<T>. This type is used to make sure when the route is being opened, it is passed the correct param type. Right now Sailor logs a warning if the type of declared and passed param is not same. In future version this might throw an error.

Passing Arguments #

Sailor allows you to pass arguments to the page that you are navigating to.

  • Create a class that extends from BaseArguments.
class SecondPageArgs extends BaseArguments {
  final String text;

  SecondPageArgs(this.text);
}
  • When calling the navigate method pass these arguments.
final response = Routes.sailor.navigate(
  "/secondPage",
  args: SecondPageArgs('Hey there'),
);
  • When in the SecondPage, use Sailor.args to get the passed arguments.
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = Sailor.args<SecondPageArgs>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Compass Example'),
      ),
      body: Center(
        child: Text(args.text),
      ),
    );
  }
}

Route Guards (Experimental) #

Routes can be protected from being opened when navigate is called using route guard.

A route guard can be added when declaring a SailorRoute.

sailor.addRoutes([
  SailorRoute(
    name: "/secondPage",
    builder: (context, args, params) => SecondPage(),
    routeGuards: [
      SailorRouteGuard.simple((context, args, params) async {
        // Can open logic goes here.
        if (sharedPreferences.getToken() != null) {
          return true;
        }
        return false;
      }),
    ],
  ),
);

routeGuards takes an array of SailorRouteGuard.

There are two ways to create a route guard:

  • Using SailorRouteGuard.simple, as shown above.
SailorRouteGuard.simple((context, args, params) async {
  // Can open logic goes here.
  if (sharedPreferences.getToken() != null) {
    return true;
  }
  return false;
});
  • Extending SailorRouteGuard class.
class CustomRouteGuard extends SailorRouteGuard {
  @override
  Future<bool> canOpen(
    BuildContext context,
    BaseArguments args,
    ParamMap paramMap,
  ) async {
    return false;
  }
}

The result from each rotue guard is Future<bool>. If the value returned by each route is true the route is accepted and opened, anything else will result in route being rejected and not being opened.

Transitions #

Sailor has inbuilt support for page transitions. A transition is specified using SailorTransition.

Transition can be specified at 3 levels (ordered in priority from highest to lowest):

  • When Navigating (using Sailor.navigate).
  • While adding routes (SailorRoute).
  • Global transitions (SailorOptions).

When navigating #

Specify which transitions to use when calling the navigate method.

Routes.sailor.navigate(
  "/secondPage",
  transitions: [SailorTransition.fade_in],
);

More than one transition can be provided when navigating a single route. These transitions are composed on top of each other, so in some cases changing the order will change the animation.

Routes.sailor.navigate(
  "/secondPage",
  transitions: [
    SailorTransition.fade_in,
    SailorTransition.slide_from_right,
  ],
  transitionDuration: Duration(milliseconds: 500),
  transitionCurve: Curves.bounceOut,
);

Duration and Curve can be provided using transitionDuration and transitionCurve respectively.

Routes.sailor.navigate(
  "/secondPage",
  transitions: [
    SailorTransition.fade_in,
    SailorTransition.slide_from_right,
  ],
  transitionDuration: Duration(milliseconds: 500),
  transitionCurve: Curves.bounceOut,
);

In the above example the page will slide in from right with a fade in animation. You can specify as many transitions as you want.

When adding routes #

You can specify the default transition for a route, so you don't have to specify it again and again when navigating.

sailor.addRoute(SailorRoute(
  name: "/secondPage",
  defaultTransitions: [
    SailorTransition.slide_from_bottom,
    SailorTransition.zoom_in,
  ],
  defaultTransitionCurve: Curves.decelerate,
  defaultTransitionDuration: Duration(milliseconds: 500),
  builder: (context, args) => SecondPage(),
));

Priority: Transitions provided in Sailor.navigate while navigating to this route, will override these transitions.

Global transitions #

You can specify default transition to be used for all routes in Sailor.

SailorOptions(
  defaultTransitions: [
    SailorTransition.slide_from_bottom,
    SailorTransition.zoom_in,
  ],
  defaultTransitionCurve: Curves.decelerate,
  defaultTransitionDuration: Duration(milliseconds: 500),
)

Priority: Transitions provided while adding a route or when navigating using navigate, will override these transitions.

Custom Transitions #

Although sailor provides you with a number of out of the box transitions, you can still provide your own custom transitions.

  • To create a custom transition, extend the class CustomSailorTransition and implement buildTransition method.
class MyCustomTransition extends CustomSailorTransition {
  @override
  Widget buildTransition(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  }
}

This transition can now be provided at 3 places:

  • While calling navigate.
Routes.sailor.navigate<bool>(
  "/secondPage",
  customTransition: MyCustomTransition(),
);
  • When declaring a SailorRoute.
SailorRoute(
  name: "/secondPage",
  builder: (context, args, params) => SecondPage(),
  customTransition: MyCustomTransition(),
),
  • In SailorOptions:
static final sailor = Sailor(
  options: SailorOptions(
    customTransition: MyCustomTransition(),
  ),
);

Custom Transition Priority

NOTE: Custom transitions have the highest priority, if you provide a custom transition, they will be used over Sailor's inbuilt transitions.

The same priority rules apply to custom transitions as inbuilt sailor transitions, with the added rule that at any step if both transitions are provided (i.e. Sailor's inbuilt transitions and a CustomSailorTransition), the custom transition will be used over inbuilt one.

For example, in the below code, MyCustomTransition will be used instead of SailorTransition.slide_from_top.

Routes.sailor.navigate<bool>(
  "/secondPage",
  transitions: [
    SailorTransition.slide_from_top,
  ],
  customTransition: MyCustomTransition(),
);

Pushing Multiple Routes #

Sailor allows you to push multiple pages at the same time and get collected response from all.

final responses = await Routes.sailor.navigateMultiple(context, [
  RouteArgsPair("/secondPage", SecondPageArgs("Multi Page!")),
  RouteArgsPair("/thirdPage", ThirdPageArgs(10)),
]);

print("Second Page Response ${responses[0]}");
print("Third Page Response ${responses[1]}");

Log Navigation #

Use SailorLoggingObserver to log the push/pop navigation inside the application. Add the SailorLoggingObserver to the navigatorObservers list inside your MaterialApp.

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Compass Example',
      home: Home(),
      onGenerateRoute: Routes.sailor.generator(),
      navigatorObservers: [
        SailorLoggingObserver(),
      ],
    );
  }
}

Once added, start navigating in your app and check the logs. You will see something like this.

flutter: [Sailor] Route Pushed: (Pushed Route='/', Previous Route='null', New Route Args=null, Previous Route Args=null)
flutter: [Sailor] Route Pushed: (Pushed Route='/secondPage', Previous Route='/', New Route Args=Instance of 'SecondPageArgs', Previous Route Args=null)
flutter: [Sailor] Route Popped: (New Route='/', Popped Route='/secondPage', New Route Args=null, Previous Route Args=Instance of 'SecondPageArgs')

Support #

If you face any issue or want a new feature to be added to the package, please create an issue. I will be more than happy to resolve your queries.

[0.7.0] - 7 April 2020 #

  • Fix Sailor API's to be compatible with latest Flutter API's.
  • Breaking Change: Sailor.pop returns void instead of bool.

[0.6.0] - 14 November 2019 #

  • Route Guards: Prevent routes from being opened based on a condition.
  • More type checking for SailorParams: SailorParams<T> now accept a generic type T, of the type of paramter that is required to be passed. When opening a route, runtimeType of the passed value is compred to the T passed when declaring SailorParam<T>.
  • Provider navigationKey: An external navigatorKey can be provided to sailor using SailorOptions.

[0.5.0] - 20 September 2019 #

  • Add support for providing your own custom transitions.

[0.4.0] - 3 September 2019 #

  • Add support for passing parameters when navigating route.

[0.3.0] - August 2019 #

  • BREAKING CHANGE: Sailor.arguments method is removed and replaced with Sailor.args.

[0.3.0] - 19 August 2019 #

  • SailorStackObserver lets you get the current stack of routes.
  • Fixcrashing while retrievingargumentswhen usingNavigationType.pushAndRemoveUntil`.
  • Refactor logs in SailorLoggingObserver.

[0.2.0] - 4 August 2019 #

  • BREAKING CHANGE: Sailor now uses a navigatorKey to carry out all navigation operations, there is no need of passing context any more in any of sailor's instance methods. Make sure to add Sailor's navigatorKey in your MaterialApp or CupertinoApp.

[0.1.0] - 2 August 2019 #

[0.0.5] - 2 August 2019 #

  • Inbuilt page transitions.

[0.0.4] - 18 May 2019 #

  • Launch multiple routes at the same time using navigateMultiple.
  • Ability to add default arguments when registering routes with addRoute.

example/lib/main.dart

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

void main() async {
  Routes.createRoutes();
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Compass Example',
      home: Home(),
      onGenerateRoute: Routes.sailor.generator(),
      navigatorKey: Routes.sailor.navigatorKey,
      navigatorObservers: [
        SailorLoggingObserver(),
        Routes.sailor.navigationStackObserver,
      ],
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Page'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            RaisedButton(
              child: Text('Open Second Page'),
              onPressed: () async {
                final response = await Routes.sailor.navigate<bool>(
                  "/secondPage",
                  transitions: [
                    SailorTransition.slide_from_top,
                  ],
                  customTransition: MyCustomTransition(),
                  params: {
                    'id': null,
                  },
                );

                print("Response from SecondPage: $response");
              },
            ),
            RaisedButton(
              child: Text('Open Multi Page (Second and Third)'),
              onPressed: () async {
                final responses = await Routes.sailor.navigateMultiple([
                  RouteArgsPair(
                    "/secondPage",
                    args: SecondPageArgs("Multi Page!"),
                  ),
                  RouteArgsPair(
                    "/thirdPage",
                    args: ThirdPageArgs(10),
                  ),
                  RouteArgsPair("/pushReplacePage"),
                ]);

                print("Second Page Response ${responses[0]}");
                print("Third Page Response ${responses[1]}");
                print("Third Page Response ${responses[2]}");
              },
            ),
            RaisedButton(
              child: Text('Push Replace Page'),
              onPressed: () async {
                Routes.sailor.navigate("/pushReplacePage");
              },
            ),
            RaisedButton(
              child: Text('Print navigation stack!'),
              onPressed: () {
                Routes.sailor.navigationStackObserver.prettyPrintStack();
              },
            ),
            RaisedButton(
              child: Text('Push Replace Page'),
              onPressed: () async {
                Routes.sailor.navigate("/pushReplacePage");
              },
            ),
            RaisedButton(
              child: Text('Push Replace Page'),
              onPressed: () async {
                Routes.sailor.navigate("/pushReplacePage");
              },
            ),
            RaisedButton(
              child: Text('Print navigation stack!'),
              onPressed: () {
                Routes.sailor.navigationStackObserver.prettyPrintStack();
              },
            ),
          ],
        ),
      ),
    );
  }
}

class SecondPageArgs extends BaseArguments {
  final String text;

  SecondPageArgs(this.text) : assert(text != null);
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = Sailor.args<SecondPageArgs>(context);
    final id = Sailor.param<int>(context, 'id');

    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text(args?.text ?? 'Second Page'),
            Text("Param('id'): $id"),
            RaisedButton(
              child: Text('Close Page'),
              onPressed: () {
                Routes.sailor.pop(true);
              },
            ),
            RaisedButton(
              child: Text('Print navigation stack!'),
              onPressed: () {
                Routes.sailor.navigationStackObserver.prettyPrintStack();
              },
            ),
          ],
        ),
      ),
    );
  }
}

class ThirdPageArgs extends BaseArguments {
  final int count;

  ThirdPageArgs(this.count);
}

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = Sailor.args<ThirdPageArgs>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Third Page'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text("Count from args is :${args?.count}"),
            RaisedButton(
              child: Text('Close Page'),
              onPressed: () {
                Routes.sailor.pop(10);
              },
            ),
            RaisedButton(
              child: Text('Print navigation stack!'),
              onPressed: () {
                Routes.sailor.navigationStackObserver.prettyPrintStack();
              },
            ),
          ],
        ),
      ),
    );
  }
}

class PushReplacePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Third Page'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            RaisedButton(
              child: Text('Push Replace'),
              onPressed: () {
                Routes.sailor.navigate(
                  "/secondPage",
                  navigationType: NavigationType.pushReplace,
                );
              },
            ),
            RaisedButton(
              child: Text('Push Unitl First and Replace'),
              onPressed: () {
                Routes.sailor.navigate(
                  "/thirdPage",
                  navigationType: NavigationType.pushAndRemoveUntil,
                  removeUntilPredicate: (route) => route.isFirst,
                );
              },
            ),
            RaisedButton(
              child: Text('Print navigation stack!'),
              onPressed: () {
                Routes.sailor.navigationStackObserver.prettyPrintStack();
              },
            ),
            Text(Routes.sailor.navigationStackObserver
                .getRouteStack()[0]
                .toString())
          ],
        ),
      ),
    );
  }
}

class Routes {
  static final sailor = Sailor(
    options: SailorOptions(
      handleNameNotFoundUI: true,
      isLoggingEnabled: true,
      customTransition: MyCustomTransition(),
      defaultTransitions: [
        SailorTransition.slide_from_bottom,
        SailorTransition.zoom_in,
      ],
      defaultTransitionCurve: Curves.decelerate,
      defaultTransitionDuration: Duration(milliseconds: 500),
    ),
  );

  static void createRoutes() {
    sailor.addRoutes(
      [
        SailorRoute(
          name: "/secondPage",
          builder: (context, args, params) => SecondPage(),
          defaultArgs: SecondPageArgs('From default arguments!'),
          customTransition: MyCustomTransition(),
          params: [
            SailorParam<String>(
              name: 'id',
            ),
          ],
          defaultTransitions: [
            SailorTransition.slide_from_bottom,
            SailorTransition.zoom_in,
          ],
          routeGuards: [
            SailorRouteGuard.simple((context, args, params) async {
              return true;
            }),
            CustomRouteGuard(),
          ],
        ),
        SailorRoute(
          name: "/thirdPage",
          builder: (context, args, params) => ThirdPage(),
          defaultTransitions: [SailorTransition.slide_from_left],
        ),
        SailorRoute(
          name: "/pushReplacePage",
          builder: (context, args, params) => PushReplacePage(),
          routeGuards: [
            SailorRouteGuard.simple(
              (context, args, params) => Future.value(true),
            )
          ],
        ),
      ],
    );
  }
}

class MyCustomTransition extends CustomSailorTransition {
  @override
  Widget buildTransition(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  }
}

class CustomRouteGuard extends SailorRouteGuard {
  @override
  Future<bool> canOpen(
    BuildContext context,
    BaseArguments args,
    ParamMap paramMap,
  ) async {
    return false;
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  sailor: ^0.7.0

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:sailor/sailor.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
89
Health:
Code health derived from static analysis. [more]
75
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
80
Overall:
Weighted score of the above. [more]
83
Learn more about scoring.

We analyzed this package on Apr 7, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.7
  • Flutter: 1.12.13+hotfix.9

Health issues and suggestions

Fix lib/src/sailor.dart. (-25 points)

Analysis of lib/src/sailor.dart failed with 1 error:

line 429 col 12: A value of type 'bool can't be returned from method 'pop' because it has a return type of 'void'.

Format lib/sailor.dart.

Run flutter format to format lib/sailor.dart.

Maintenance issues and suggestions

No valid SDK. (-20 points)

The analysis could not detect a valid SDK that can use this package.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test
test any