67

I have many screens, and I'm using the Navigator. I'd like to use "named routes", but I also need to pass non-string (such as images) to my next route.

I can't use pushNamed() because I can't pass non-string data to it.

How can I use a named route + send non-string data?

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
Seth Ladd
  • 112,095
  • 66
  • 196
  • 279

12 Answers12

77

EDIT:

It is now possible to pass complex arguments to Navigator.pushNamed:

String id;
Navigator.pushNamed(context, '/users', arguments: id);

It can then be used within onGenerateRoute to customize route building with these arguments:

MaterialApp(
  title: 'Flutter Hooks Gallery',
  onGenerateRoute: (settings) {
    final arguments = settings.arguments;
    switch (settings.name) {
      case '/users':
        if (arguments is String) {
          // the details page for one specific user
          return UserDetails(arguments);
        }
        else {
          // a route showing the list of all users
          return UserList();
        }
      default:
        return null;
    }
  },
);
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • When the route is /myRoute/${myID} the path split has 3 items in the list with first being empty string "". Perhaps we have to ignore the path[0] and make use of path[1] and path[2]? – Gautham Nov 29 '17 at 18:59
  • I'd remove the first '/' in the route instead. But this is just an example, you're free to use whatever you want to. – Rémi Rousselet Nov 29 '17 at 19:02
  • True. I just wanted to add about the issue for the benefit of those visiting this later. – Gautham Nov 29 '17 at 19:07
  • What if it's not id but an object? Can we do this? – stuckedunderflow Dec 14 '18 at 03:31
  • 2
    Is the above answer still valid? I cannot see `arguments` property available via `RouteSettings`... – Angel Todorov Feb 05 '19 at 16:35
  • 2
    It has been added very recently. You may want to switch of channel. – Rémi Rousselet Feb 05 '19 at 17:57
  • @RémiRousselet - any hints where to find the channel / the most recent version of Navigator would be greatly appreciated. As of now, looking at https://docs.flutter.io/flutter/widgets/RouteFactory.html doesn't expose any other properties than `initialRoute` and `name`. Let me know if I am wrong... – Angel Todorov Feb 08 '19 at 11:59
  • It is on `master` channel for now. It'll change over time, so I can't include that information in the answer. In theory, within a few months it should hit `beta` and a bit later `stable`. – Rémi Rousselet Feb 08 '19 at 12:07
  • 1
    Here is pull request for this feature: https://github.com/flutter/flutter/pull/27058 – Michał Powłoka Feb 19 '19 at 18:30
  • 3
    Shouldn't `UserDetails` and `UserList` be wrapped into an `MaterialPageRoute`? – Michel Feinstein Mar 13 '19 at 20:55
  • Wrap the `switch` statement into `return MaterialPageRoute(builder: (context) {});` and it should work again. – rlat Apr 09 '20 at 20:09
  • @RémiRousselet how to manage navigation routing with tabbarview in flutter – s.j Jan 28 '21 at 05:50
36

You can use the parameter routes of your App for directly passing arguments.

Like this:

  routes: {
    HomePage.route: (_) => HomePage(),
    DetailsPage.route: (context) =>
        DetailsPage(ModalRoute.of(context).settings.arguments),
  },

In this case, the complete example will look like the next:

import 'package:flutter/material.dart';

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          initialRoute: HomePage.route,
          routes: {
            HomePage.route: (_) => HomePage(),
            DetailsPage.route: (context) =>
                DetailsPage(ModalRoute.of(context).settings.arguments),
          },
        );
      }
    }

    class HomePage extends StatelessWidget {
      static const String route = '/';

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              Navigator.pushNamed(context, '/details',
                  arguments: ScreenArguments(
                    'My Details',
                    'Some Message',
                  ));
            },
          ),
        );
      }
    }

    class DetailsPage extends StatelessWidget {
      static const String route = '/details';

      final ScreenArguments arguments;

      DetailsPage(this.arguments);

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(arguments.title),
          ),
          body: Center(
            child: Text(arguments.message),
          ),
        );
      }
    }

    class ScreenArguments {
      final String title;
      final String message;

      ScreenArguments(this.title, this.message);
    }
Yuriy Luchaninov
  • 1,136
  • 1
  • 11
  • 7
35

By Using Maps

While pushing the arguments u can push that in map form and can do the same while extracting them.

e.g.

While Pushing

Navigator.of(context).pushNamed(
              'second',
              arguments: {
                'title':'This is a String',
                       or
                 'Fx': This could be any widget or Function
              }

While Extracting the arguments in the target page

final routes=ModalRoute.of(context).settings.arguments as Map<String,String>;

    return Scaffold(
      appBar: AppBar(
              title: Text(routes['title']),
              ),
      body: Container(
        child: Center(
          child: RaisedButton(
            child: Text("Back"),
            onPressed: ()=>Navigator.of(context).pop(),
          ),
        ),
      ),
    );

and choose your map accordingly accordingly
daddy_
  • 1,657
  • 1
  • 12
  • 17
11

UPDATE: 3rd April, 2021

This answer is old and Flutter navigation has evolved considerably since then. This may not be the best way to handle navigation with current versions, please consider other answers. I will leave this here for historical purposes.


Using onGenerateRoute it is easy to pass complex arguments on route transition with Navigator.pushNamed or Navigator.pushReplacementNamed

A minimal setup to show the concept would be

main.dart

import 'package:flutter/material.dart';
import 'package:navigator/routes.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.teal,
      ),
      onGenerateRoute: (RouteSettings settings) {
        return MaterialPageRoute(
          builder: (BuildContext context) => makeRoute(
                context: context,
                routeName: settings.name,
                arguments: settings.arguments,
              ),
          maintainState: true,
          fullscreenDialog: false,
        );
      },
    );
  }
}

routes.dart

In the _buildRoute method we check the route name and cast arguments to a required type.

A draw back is that the type has to be defined before hand if required argument is not a simple type.

import 'package:flutter/material.dart';

import 'package:navigator/list.dart';
import 'package:navigator/details.dart';

Widget makeRoute(
    {@required BuildContext context,
    @required String routeName,
    Object arguments}) {
  final Widget child =
      _buildRoute(context: context, routeName: routeName, arguments: arguments);
  return child;
}

Widget _buildRoute({
  @required BuildContext context,
  @required String routeName,
  Object arguments,
}) {
  switch (routeName) {
    case '/':
      return ArticleList();
    case '/ArticleView':
      Article article = arguments as Article;
      return ArticleView(article: article);
    default:
      throw 'Route $routeName is not defined';
  }
}

Views

list.dart

Construct the route argument using a defined type, Article in our case.

import 'package:flutter/material.dart';
import 'package:navigator/details.dart' show Article;

class ArticleList extends StatefulWidget {
  @override
  _ArticleListState createState() => _ArticleListState();
}

class _ArticleListState extends State<ArticleList> {
  List<Article> articles = [
    Article(
        id: 1,
        title: 'Article 1',
        author_name: 'Nilotpal',
        summary: 'Article 1 summary'),
    Article(
        id: 2,
        title: 'Article 2',
        author_name: 'Mike',
        summary: 'Article 2 summary'),
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Articles'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            ListTile(
              title: Text('${articles[0].title}'),
              subtitle: Text('by ${articles[0].author_name}'),
              onTap: () {
                Navigator.of(context)
                    .pushNamed('/ArticleView', arguments: articles[0]);
              },
            ),
            ListTile(
              title: Text('${articles[1].title}'),
              subtitle: Text('by ${articles[1].author_name}'),
              onTap: () {
                Navigator.of(context)
                    .pushNamed('/ArticleView', arguments: articles[1]);
              },
            ),
          ],
        ),
      ),
    );
  }
}

details.dart

Define a type for the arguments

import 'package:flutter/material.dart';

class Article {
  final int id;
  final String author_name;
  final String title;
  final String summary;

  Article(
      {@required this.id,
      @required this.author_name,
      @required this.title,
      @required this.summary});
}

class ArticleView extends StatelessWidget {
  final Article _article;

  ArticleView({@required Article article}) : _article = article;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('${_article.title}'),
      ),
      body: SafeArea(
        top: true,
        child: Center(
          child: Column(
            children: <Widget>[
              Text('${_article.author_name}'),
              Text('${_article.summary}'),
            ],
          ),
        ),
      ),
    );
  }
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
nilobarp
  • 3,806
  • 2
  • 29
  • 37
4

The Flutter Cookbook shows how to navigate to a new page and pass non-string data to it.

Passing data to next page

I started with Navigator.pushedNamed() because it was simple and I didn't have any data to pass. When my needs changed and I wanted to pass data, I switched to Navigator.push().

Example:

var nextPageData = {foo:'bar'};

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => 
     MyNextPage(myData: nextPageData))
 );
devdanke
  • 1,309
  • 15
  • 27
2

I am capturing images with camera then passing them through to a confirmation page like so:

   ImagePicker.pickImage(source: source).then((File file) {
    Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => MediaCaptured(file: file),
        ));
  });

You could easily do the same with any type of file or non-string data.

var foo = "non-string data";
Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => MediaCaptured(foo: foo),
        ));

Call the next page in the route by it's class name, as above.

Just make sure your new page accepts this in it's constructor.

 // Stateful Widget
class MediaCaptured extends StatefulWidget {
    MediaCaptured({ Key key, @required this.foo,}) : super(key: key);
    final var foo;
}

// StatelessWidget
class MediaCaptured extends StatelessWidget {
    MediaCaptured(this.foo);
    var foo;
}
SeanThomas
  • 79
  • 1
  • 5
2

For the outcome of this problem, I developed the package

link: https://pub.dartlang.org/packages/navigate

That provide to much your expect and easy to use

Navigate.navigate(context,
                      "home",
                      transactionType:TransactionType.fromLeft , // optional
                      replaceRoute: ReplaceRoute.thisOne, //optional
                      arg: {"transactionType":TransactionType.fromLeft,"replaceRoute":ReplaceRoute.thisOne} //optional
                      );
Ravindra Bhanderi
  • 2,478
  • 4
  • 17
  • 29
2

From First StateFul class :

Navigator.of(context).pushNamed('/pending_order',arguments: {"staff" : staffObj});

To Second StateFul class :

  class PendingOrders extends StatefulWidget {
  @override
  _PendingOrdersState createState() => _PendingOrdersState();
}

class _PendingOrdersState extends State<PendingOrders> {
  StaffModel staffModelObj;

  @override
  Widget build(BuildContext context) {
    final routes =
        ModalRoute.of(context).settings.arguments as Map<String, dynamic>;
    if (routes != null) {
      staffModelObj = routes["staff"];
    }
    return Scaffold(...);}}
Anil Kumar
  • 1,830
  • 15
  • 24
2

I wanted to use a named route navigator that has values as below

 Navigator.pushNamed(context, '/increaseBalanceAccountPage',
                        arguments: {'accountBalanceViewModel': result},);

so I should define that route in materialApp widget in the start of app but I should give parameters in the instance so I solve my problem with some modification of @YuriyLuchaninov code Like below:

MaterialApp(
    initialRoute: "/",
    routes: {
      '/': (context) => SplashScreenPage(),
      "/increaseBalanceAccountPage":

(context) =>
      UserAccountBalancePage(accountBalanceViewModel: Map<String,Object>
          .from(ModalRoute.of(context)!.settings.arguments as Map).values.first as
      AccountBalanceViewModel)
},
.....
Shojaeddin
  • 1,851
  • 1
  • 18
  • 16
1

Consider this trivial example from flutter. You have a class created as follows

class ScreenArguments {
   final String title;
   final String message;

  ScreenArguments(this.title, this.message);
}

Now we will pass an object of this class as an argument as follows

Navigator.pushNamed(
  context,
  ExtractArgumentsScreen.routeName,
  arguments: ScreenArguments(
    'Extract Arguments Screen',
    'This message is extracted in the build method.',
  ),
);

And then you can extract the arguments as follows

final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;

And that is all. Hope this helps Source: passing arguments to a named route

Ujah Jideofor
  • 73
  • 1
  • 7
1

Navigator.of(context).pushNamed(
              'second',
              arguments: {
                'title':'This is a String',
                       or
                 'Fx': This could be any widget or Function
              }
sharon
  • 363
  • 2
  • 11
-5
We can pass any type of arguments when declaring routes as constructor arguments as below,

For example to send a list of Strings,

List<String> titles = [];

void main() => runApp(
      new MaterialApp(
        home: new FirstPage(),
        routes: <String, WidgetBuilder>{
          "/SecondPage": (BuildContext context) => new SecondPage(titles),
        },
      ),
    );

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new RaisedButton(onPressed: () {
        Navigator.of(context).pushNamed('/SecondPage');
      }),
    );
  }
}

class SecondPage extends StatelessWidget {
  final List<String> titles;

  SecondPage(this.titles);

  @override
  Widget build(BuildContext context) {
    return new ListView.builder(
      itemBuilder: (context, index) {
        return new ListTile(
          title: new Text(titles[index]),
        );
      },
    );
  }
}
Vinoth Kumar
  • 12,637
  • 5
  • 32
  • 38