Most Android apps make use of startActivityForResult at one point or another. But, how do you do this in Flutter?

The official doc

The very helpful Flutter For Android Developers page on the official Flutter website demonstrates a way to do it using Navigator.of(context).pushNamed.

However, in the official doc about Navigation, using MaterialPageRoute is recommended for Material Design apps. So, how do you combine the two?

Adapting the official doc to using MaterialPageRoute

My first attempt provided a crash, as below.

Crash I/flutter (23315): ??? EXCEPTION CAUGHT BY GESTURE ???????????????????????????????????????????????????????????????????? I/flutter (23315): The following assertion was thrown while handling a gesture: I/flutter (23315): type '_InternalLinkedHashMap' is not a subtype of type 'Null' of 'result' where I/flutter (23315): _InternalLinkedHashMap is from dart:collection I/flutter (23315): Null is from dart:core 1 2 3 4 5 I / flutter ( 23315 ) : ? ? ? EXCEPTION CAUGHT BY GESTURE ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? I / flutter ( 23315 ) : The following assertion was thrown while handling a gesture : I / flutter ( 23315 ) : type '_InternalLinkedHashMap' is not a subtype of type 'Null' of 'result' where I / flutter ( 23315 ) : _InternalLinkedHashMap is from dart : collection I / flutter ( 23315 ) : Null is from dart : core

Turned out it was due to using new MaterialPageRoute<Null>, which I had copied from the Navigation page. The solution: replace Null with dynamic.



Code tutorial

For this code tutorial, we will create an app with 2 screens. Tapping a button on the first screen will open the second screen. The second screen has 2 buttons. When tapping either of them, the screen is closed, and the first screen displays the text of the button that was tapped.

App setup

To follow the code tutorial, create a new app as follows.

Create app flutter create startactivityforresultexample 1 flutter create startactivityforresultexample

If you’re unsure how to set up a Flutter app, check out Getting started with Flutter official tutorial.

Firstly, let’s set up main.dart to launch a MaterialApp.

main.dart import 'package:flutter/material.dart'; import 'home/home_page.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'StartActivityForResult Example', theme: new ThemeData( primaryColor: const Color(0xFF43a047), accentColor: const Color(0xFFffcc00), primaryColorBrightness: Brightness.dark, ), home: new HomePage(), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import 'package:flutter/material.dart' ; import 'home/home_page.dart' ; void main ( ) { runApp ( new MyApp ( ) ) ; } class MyApp extends StatelessWidget { // This widget is the root of your application. @ override Widget build ( BuildContext context ) { return new MaterialApp ( title : 'StartActivityForResult Example' , theme : new ThemeData ( primaryColor : const Color ( 0xFF43a047 ) , accentColor : const Color ( 0xFFffcc00 ) , primaryColorBrightness : Brightness . dark , ) , home : new HomePage ( ) , ) ; } }

Secondly, we need to add HomePage to be able to build the project. We create a new folder home and, in it, home_page.dart.

home/home_page.dart import 'package:flutter/material.dart'; class HomePage extends StatefulWidget { HomePage({Key key}) : super(key: key); @override _HomePageState createState() => new _HomePageState(); } class _HomePageState extends State<HomePage> { String _selection; @override Widget build(BuildContext context) { Widget buttonWidget = new FlatButton( textColor: Colors.blueGrey, color: Colors.white, child: new Text('To Selection Screen'), onPressed: _buttonTapped, ); List<Widget> widgets = new List<Widget>(); widgets.add(buttonWidget); if (_selection != null) { Widget textWidget = new Text(_selection); widgets.add(textWidget); } return new Scaffold( appBar: new AppBar( title: new Text("Home Page, with selection"), ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 4.0), child: new Column( children: widgets, ), ), ); } void _buttonTapped() { // TODO } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import 'package:flutter/material.dart' ; class HomePage extends StatefulWidget { HomePage ( { Key key } ) : super ( key : key ) ; @ override _HomePageState createState ( ) = > new _HomePageState ( ) ; } class _HomePageState extends State < HomePage > { String _selection ; @ override Widget build ( BuildContext context ) { Widget buttonWidget = new FlatButton ( textColor : Colors . blueGrey , color : Colors . white , child : new Text ( 'To Selection Screen' ) , onPressed : _buttonTapped , ) ; List < Widget > widgets = new List < Widget > ( ) ; widgets . add ( buttonWidget ) ; if ( _selection != null ) { Widget textWidget = new Text ( _selection ) ; widgets . add ( textWidget ) ; } return new Scaffold ( appBar : new AppBar ( title : new Text ( "Home Page, with selection" ) , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 4.0 ) , child : new Column ( children : widgets , ) , ) , ) ; } void _buttonTapped ( ) { // TODO } }

For HomePage, we use a stateful widget, with the variable _selection tracking the selection made in the other screen. If that variable is null, the screen shows only one button. Otherwise, it shows its value below the button. When tapping the button in HomePage, the method _buttonTapped() is fired (this is where we will launch the second screen from).

Thirdly, we add the second screen, SelectionPage, as a stateless widget. Let’s add a folder named selection and, in it, create selection_page.dart. It contains 2 buttons, which call _selectItem when tapped (this is where we will close this screen and pass on the value back to HomePage).

selection/selection_page.dart import 'package:flutter/material.dart'; const String ITEM_1 = 'Item 1'; const String ITEM_2 = 'Item 2'; class SelectionPage extends StatelessWidget { SelectionPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { Widget buttons = new Container( child: new Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ new FlatButton( textColor: Colors.blueGrey, color: Colors.white, child: new Text(ITEM_1), onPressed: () => _selectItem(ITEM_1, context), ), new FlatButton( textColor: Colors.blueGrey, color: Colors.white, child: new Text(ITEM_2), onPressed: () => _selectItem(ITEM_2, context), ), ], ), ); return new Scaffold( appBar: new AppBar( title: new Text("Selection page"), ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 4.0), child: buttons, ), ); } void _selectItem(String value, BuildContext context) { // TODO } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import 'package:flutter/material.dart' ; const String ITEM_1 = 'Item 1' ; const String ITEM_2 = 'Item 2' ; class SelectionPage extends StatelessWidget { SelectionPage ( { Key key } ) : super ( key : key ) ; @ override Widget build ( BuildContext context ) { Widget buttons = new Container ( child : new Row ( mainAxisAlignment : MainAxisAlignment . spaceEvenly , children : [ new FlatButton ( textColor : Colors . blueGrey , color : Colors . white , child : new Text ( ITEM_1 ) , onPressed : ( ) = > _selectItem ( ITEM_1 , context ) , ) , new FlatButton ( textColor : Colors . blueGrey , color : Colors . white , child : new Text ( ITEM_2 ) , onPressed : ( ) = > _selectItem ( ITEM_2 , context ) , ) , ] , ) , ) ; return new Scaffold ( appBar : new AppBar ( title : new Text ( "Selection page" ) , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 4.0 ) , child : buttons , ) , ) ; } void _selectItem ( String value , BuildContext context ) { // TODO } }

Looking for a Flutter job? Check out my job board dedicated to Flutter at flutterjobs.info

Start Activity For Result implementation

We can now implement the Flutter equivalent of Android SDK’s startActivityForResult.

The code to launch SelectionPage when tapping button on HomePage is as below.

home/home_page.dart/_buttonTapped import '../selection/selection_page.dart'; void _buttonTapped() { Navigator.of(context).push(new MaterialPageRoute<dynamic>( builder: (BuildContext context) { return new SelectionPage(); }, )); } 1 2 3 4 5 6 7 8 9 import '../selection/selection_page.dart' ; void _buttonTapped ( ) { Navigator . of ( context ) . push ( new MaterialPageRoute < dynamic > ( builder : ( BuildContext context ) { return new SelectionPage ( ) ; } , ) ) ; }

This is the equivalent of Android SDK’s startActivity.

Now, let’s implement closing SelectionPage screen and passing some data when tapping the button in SelectionPage.

selection/selection_page.dart/_selectItem void _selectItem(String value, BuildContext context) { Navigator.of(context).pop({'selection':value}); } 1 2 3 void _selectItem ( String value , BuildContext context ) { Navigator . of ( context ) . pop ( { 'selection' : value } ) ; }

And let’s read the data in HomePage (as you would do in Android SDK’s onActivityResult ). Note that instead of using a callback system as in Android SDK, we read the data sent back by SelectionPage as a Future of the code launching SelectionPage.

home/home_page.dart/_buttonTapped import 'dart:async' show Future; Future _buttonTapped() async { Map results = await Navigator.of(context).push(new MaterialPageRoute<dynamic>( builder: (BuildContext context) { return new SelectionPage(); }, )); if (results != null && results.containsKey('selection')) { setState(() { _selection = results['selection']; }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import 'dart:async' show Future ; Future _buttonTapped ( ) async { Map results = await Navigator . of ( context ) . push ( new MaterialPageRoute < dynamic > ( builder : ( BuildContext context ) { return new SelectionPage ( ) ; } , ) ) ; if ( results != null && results . containsKey ( 'selection' ) ) { setState ( ( ) { _selection = results [ 'selection' ] ; } ) ; } }

Voila!

What next?

Bookmark Flutter for Android Developers as it is full of great tricks that will save you time. Then, if you haven’t done so already, read How to write an integration test in Flutter and write a UI test for this code tutorial.

Related