Skip to content

Instantly share code, notes, and snippets.

@johnpryan
Last active May 17, 2022 12:20
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save johnpryan/430c1d3ad771c43bf249c07fa3aeef14 to your computer and use it in GitHub Desktop.
Save johnpryan/430c1d3ad771c43bf249c07fa3aeef14 to your computer and use it in GitHub Desktop.
Router example
import 'package:flutter/material.dart';
void main() {
runApp(BooksApp());
}
class Book {
final String title;
final String author;
Book(this.title, this.author);
}
class BooksApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
BookRouterDelegate _routerDelegate = BookRouterDelegate();
BookRouteInformationParser _routeInformationParser =
BookRouteInformationParser();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Books App',
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
);
}
}
class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
@override
Future<BookRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location);
// Handle '/'
if (uri.pathSegments.length == 0) {
return BookRoutePath.home();
}
// Handle '/book/:id'
if (uri.pathSegments.length == 2) {
if (uri.pathSegments[0] != 'book') return BookRoutePath.unknown();
var remaining = uri.pathSegments[1];
var id = int.tryParse(remaining);
if (id == null) return BookRoutePath.unknown();
return BookRoutePath.details(id);
}
// Handle unknown routes
return BookRoutePath.unknown();
}
@override
RouteInformation restoreRouteInformation(BookRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: '/404');
}
if (path.isHomePage) {
return RouteInformation(location: '/');
}
if (path.isDetailsPage) {
return RouteInformation(location: '/book/${path.id}');
}
return null;
}
}
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
final GlobalKey<NavigatorState> navigatorKey;
Book _selectedBook;
bool show404 = false;
List<Book> books = [
Book('Left Hand of Darkness', 'Ursula K. Le Guin'),
Book('Too Like the Lightning', 'Ada Palmer'),
Book('Kindred', 'Octavia E. Butler'),
];
BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
BookRoutePath get currentConfiguration {
if (show404) {
return BookRoutePath.unknown();
}
return _selectedBook == null
? BookRoutePath.home()
: BookRoutePath.details(books.indexOf(_selectedBook));
}
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (show404)
MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen())
else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
_selectedBook = null;
show404 = false;
notifyListeners();
return true;
},
);
}
@override
Future<void> setNewRoutePath(BookRoutePath path) async {
if (path.isUnknown) {
_selectedBook = null;
show404 = true;
return;
}
if (path.isDetailsPage) {
if (path.id < 0 || path.id > books.length - 1) {
show404 = true;
return;
}
_selectedBook = books[path.id];
} else {
_selectedBook = null;
}
show404 = false;
}
void _handleBookTapped(Book book) {
_selectedBook = book;
notifyListeners();
}
}
class BookDetailsPage extends Page {
final Book book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
Route createRoute(BuildContext context) {
return MaterialPageRoute(
settings: this,
builder: (BuildContext context) {
return BookDetailsScreen(book: book);
},
);
}
}
class BookRoutePath {
final int id;
final bool isUnknown;
BookRoutePath.home()
: id = null,
isUnknown = false;
BookRoutePath.details(this.id) : isUnknown = false;
BookRoutePath.unknown()
: id = null,
isUnknown = true;
bool get isHomePage => id == null;
bool get isDetailsPage => id != null;
}
class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
@required this.book,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),
),
);
}
}
class UnknownScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('404!'),
),
);
}
}
@dleurs
Copy link

dleurs commented Oct 27, 2020

Why keeping two form of the state, in BookRouterDelegate's attributes and in BookRoutePath ?

It would be simpler just to create in BookRouterDelegate a attribute called "BookRoutePath currentState = BookRoutePath.home();"

exemple here : https://github.com/dleurs/Flutter-Navigator-2.0-demo-with-Authentication-mecanism

@stuz5000
Copy link

What I see when tapping the list items:

image

The article says the browser URL bar will reflect the path information.

@JamesVanWaza
Copy link

@johnpryan the example is not working, I got multiple errors, see screenshot attached.

Screen Shot 2021-04-25 at 1 00 18 PM

@omatt
Copy link

omatt commented Jun 3, 2021

@JamesVanWaza it looks like you need to migrate the code for null-safety

import 'package:flutter/material.dart';

void main() {
  runApp(BooksApp());
}

class Book {
  final String title;
  final String author;

  Book(this.title, this.author);
}

class BooksApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _BooksAppState();
}

class _BooksAppState extends State<BooksApp> {
  BookRouterDelegate _routerDelegate = BookRouterDelegate();
  BookRouteInformationParser _routeInformationParser =
  BookRouteInformationParser();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Books App',
      routerDelegate: _routerDelegate,
      routeInformationParser: _routeInformationParser,
    );
  }
}

class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
  @override
  Future<BookRoutePath> parseRouteInformation(
      RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location!);
    // Handle '/'
    if (uri.pathSegments.length == 0) {
      return BookRoutePath.home();
    }

    // Handle '/book/:id'
    if (uri.pathSegments.length == 2) {
      if (uri.pathSegments[0] != 'book') return BookRoutePath.unknown();
      var remaining = uri.pathSegments[1];
      var id = int.tryParse(remaining);
      if (id == null) return BookRoutePath.unknown();
      return BookRoutePath.details(id);
    }

    // Handle unknown routes
    return BookRoutePath.unknown();
  }

  @override
  RouteInformation? restoreRouteInformation(BookRoutePath path) {
    if (path.isUnknown) {
      return RouteInformation(location: '/404');
    }
    if (path.isHomePage) {
      return RouteInformation(location: '/');
    }
    if (path.isDetailsPage) {
      return RouteInformation(location: '/book/${path.id}');
    }
    return null;
  }
}

class BookRouterDelegate extends RouterDelegate<BookRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
  final GlobalKey<NavigatorState> navigatorKey;

  Book? _selectedBook;
  bool show404 = false;

  List<Book> books = [
    Book('Left Hand of Darkness', 'Ursula K. Le Guin'),
    Book('Too Like the Lightning', 'Ada Palmer'),
    Book('Kindred', 'Octavia E. Butler'),
  ];

  BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();

  BookRoutePath get currentConfiguration {
    if (show404) {
      return BookRoutePath.unknown();
    }
    return _selectedBook == null
        ? BookRoutePath.home()
        : BookRoutePath.details(books.indexOf(_selectedBook!));
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        MaterialPage(
          key: ValueKey('BooksListPage'),
          child: BooksListScreen(
            books: books,
            onTapped: _handleBookTapped,
          ),
        ),
        if (show404)
          MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen())
        else if (_selectedBook != null)
          BookDetailsPage(book: _selectedBook)
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }

        // Update the list of pages by setting _selectedBook to null
        _selectedBook = null;
        show404 = false;
        notifyListeners();

        return true;
      },
    );
  }

  @override
  Future<void> setNewRoutePath(BookRoutePath path) async {
    if (path.isUnknown) {
      _selectedBook = null;
      show404 = true;
      return;
    }

    if (path.isDetailsPage) {
      if (path.id! < 0 || path.id! > books.length - 1) {
        show404 = true;
        return;
      }

      _selectedBook = books[path.id!];
    } else {
      _selectedBook = null;
    }

    show404 = false;
  }

  void _handleBookTapped(Book book) {
    _selectedBook = book;
    notifyListeners();
  }
}

class BookDetailsPage extends Page {
  final Book? book;

  BookDetailsPage({
    this.book,
  }) : super(key: ValueKey(book));

  Route createRoute(BuildContext context) {
    return MaterialPageRoute(
      settings: this,
      builder: (BuildContext context) {
        return BookDetailsScreen(book: book);
      },
    );
  }
}

class BookRoutePath {
  final int? id;
  final bool isUnknown;

  BookRoutePath.home()
      : id = null,
        isUnknown = false;

  BookRoutePath.details(this.id) : isUnknown = false;

  BookRoutePath.unknown()
      : id = null,
        isUnknown = true;

  bool get isHomePage => id == null;

  bool get isDetailsPage => id != null;
}

class BooksListScreen extends StatelessWidget {
  final List<Book> books;
  final ValueChanged<Book> onTapped;

  BooksListScreen({
    required this.books,
    required this.onTapped,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView(
        children: [
          for (var book in books)
            ListTile(
              title: Text(book.title),
              subtitle: Text(book.author),
              onTap: () => onTapped(book),
            )
        ],
      ),
    );
  }
}

class BookDetailsScreen extends StatelessWidget {
  final Book? book;

  BookDetailsScreen({
    required this.book,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (book != null) ...[
              Text(book!.title, style: Theme.of(context).textTheme.headline6),
              Text(book!.author, style: Theme.of(context).textTheme.subtitle1),
            ],
          ],
        ),
      ),
    );
  }
}

class UnknownScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text('404!'),
      ),
    );
  }
}

@vgribok
Copy link

vgribok commented Jul 30, 2021

To make sense of the Flutter Navigator 2.x and working in part off of this example, I created https://github.com/vgribok/flutter_nav2_oop example where navigator logic is abstracted away from the app logic for cleaner and consistent programming model.

@daraul
Copy link

daraul commented Sep 2, 2021

I was able to get some routes working in my application using this. My URL doesn't change when I navigate to a page, but updating the URL myself will navigate to the right page. Does restoreRouteInformation do nothing? I threw an UnimplementedError in there just to check if it runs; nothing. What is it supposed to do? Why do I need it?

@dotarsoyak
Copy link

dotarsoyak commented Oct 7, 2021

Hello, I have implemented the example along with a SearchDelegate, when I touch the search button of the searchDelegate, the application does not work, the android studio throws an error on the console:

The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget. When the exception was thrown, this was the stack: 
#0      Navigator.of.<anonymous closure> (package:flutter/src/widgets/navigator.dart:2741:9)
#1      Navigator.of (package:flutter/src/widgets/navigator.dart:2748:6)
#2      showSearch (package:flutter/src/material/search.dart:70:20)
#3      ProductRouterDelegate.build.<anonymous closure> (package:flutter_app/main.dart:145:23)
#4      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:989:21)
#5      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:193:24)
#6      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:608:11)

I share you my code, I don´t have idea why this happens, any help is welcome, in advance I thank you for any advice.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_app/screen/cart.dart';
import 'package:flutter_app/screen/menu.dart';
import 'package:flutter_app/screen/product_detail.dart';
import 'package:flutter_app/widgets/product_list.dart';
import 'ext/ExtBottomNavigationBar.dart';
import 'model/Product.dart';
import 'widgets/banner.dart';
import 'widgets/product_offer.dart';
import 'package:http/http.dart' as http;

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

class MainApp extends StatefulWidget {
  @override
  _MainAppState createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  final ProductRouterDelegate _routerDelegate = ProductRouterDelegate();
  BookRouteInformationParser _routeInformationParser =
      BookRouteInformationParser();

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Books App',
      routerDelegate: _routerDelegate,
      routeInformationParser: _routeInformationParser,
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Container(
            alignment: Alignment.topCenter,
            color: Colors.red,
            child: MainBanner(),
          ),
          Column(
            children: <Widget>[
              Container(
                alignment: Alignment.topLeft,
                child: Text('Ofertas del día'),
              ),
              Container(
                alignment: Alignment.topCenter,
                color: Colors.blue,
                child: ProductOffer(),
              ),
            ],
          ),
          ProductOffer(),
          ProductOffer(),
        ],
      ),
    );
  }
}

class ProductRoutePath {
  final int id;
  final bool isUnknown;

  ProductRoutePath.home()
      : id = null,
        isUnknown = false;

  ProductRoutePath.details(this.id) : isUnknown = false;

  ProductRoutePath.unknown()
      : id = null,
        isUnknown = true;

  bool get isHomePage => id == null;

  bool get isDetailsPage => id != null;
}

class ProductRouterDelegate extends RouterDelegate<ProductRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<ProductRoutePath> {
  final GlobalKey<NavigatorState> navigatorKey;

  Product _selectedProduct;
  bool show404 = false;
  int _selectedIndex = 0;
  List<Widget> _screenList = [HomePage(), CartScreen(), MenuScreen()];

  ProductRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();

  List<Product> articles;

  ProductRoutePath get currentConfiguration {
    if (show404) {
      return ProductRoutePath.unknown();
    }
    return _selectedProduct == null
        ? ProductRoutePath.home()
        : ProductRoutePath.details(articles.indexOf(_selectedProduct));
  }

  void fetchProducts(String qSearch) async {
    final response = await http.get(
        'https://somedomain/articles_json.php?word=' + qSearch);

    if (response.statusCode == 200) {
      final parsed = jsonDecode(response.body).cast<Map<String, dynamic>>();
      this.articles =
          parsed.map<Product>((json) => Product.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load articles');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        MaterialPage(
          key: ValueKey('HomePage'),
          child: Scaffold(
            appBar: AppBar(
              title: Text("Home"),
              actions: [
                IconButton(
                    icon: Icon(Icons.search),
                    onPressed: () {
                      showSearch(
                          context: context,
                          delegate:
                              DataSearch(onItemTapped: _handleBookTapped));
                    }),
              ],
            ),
            body: SafeArea(
              child: _screenList[_selectedIndex],
            ),
            bottomNavigationBar:
                extBottomNavigationBar(context, _selectedIndex, _onItemTapped),
          ),
        ),
        if (show404)
          MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen())
        else if (_selectedProduct != null)
          ProductDetail(product: _selectedProduct)
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }

        // Update the list of pages by setting _selectedBook to null
        _selectedProduct = null;
        show404 = false;
        notifyListeners();

        return true;
      },
    );
  }

  @override
  Future<void> setNewRoutePath(ProductRoutePath path) async {
    if (path.isUnknown) {
      _selectedProduct = null;
      show404 = true;
      return;
    }

    if (path.isDetailsPage) {
      if (path.id < 0 || path.id > articles.length - 1) {
        show404 = true;
        return;
      }

      _selectedProduct = articles[path.id];
    } else {
      _selectedProduct = null;
    }

    show404 = false;
  }

  void _handleBookTapped(Product product) {
    _selectedProduct = product;
    notifyListeners();
  }

  void _onItemTapped(int index) {
    _selectedIndex = index;
    notifyListeners();
  }
}

class UnknownScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text('404!'),
      ),
    );
  }
}

class ProductRouteInformationParser
    extends RouteInformationParser<ProductRoutePath> {
  @override
  Future<ProductRoutePath> parseRouteInformation(
      RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location);
    // Handle '/'
    if (uri.pathSegments.length == 0) {
      return ProductRoutePath.home();
    }

    // Handle '/article/:id'
    if (uri.pathSegments.length == 2) {
      if (uri.pathSegments[0] != 'article') return ProductRoutePath.unknown();
      var remaining = uri.pathSegments[1];
      var id = int.tryParse(remaining);
      if (id == null) return ProductRoutePath.unknown();
      return ProductRoutePath.details(id);
    }

    // Handle unknown routes
    return ProductRoutePath.unknown();
  }

  @override
  RouteInformation restoreRouteInformation(ProductRoutePath path) {
    if (path.isUnknown) {
      return RouteInformation(location: '/404');
    }
    if (path.isHomePage) {
      return RouteInformation(location: '/');
    }
    if (path.isDetailsPage) {
      return RouteInformation(location: '/article/${path.id}');
    }
    return null;
  }
}

class BookRouteInformationParser
    extends RouteInformationParser<ProductRoutePath> {
  @override
  Future<ProductRoutePath> parseRouteInformation(
      RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location);
    // Handle '/'
    if (uri.pathSegments.length == 0) {
      return ProductRoutePath.home();
    }

    // Handle '/article/:id'
    if (uri.pathSegments.length == 2) {
      if (uri.pathSegments[0] != 'book') return ProductRoutePath.unknown();
      var remaining = uri.pathSegments[1];
      var id = int.tryParse(remaining);
      if (id == null) return ProductRoutePath.unknown();
      return ProductRoutePath.details(id);
    }

    // Handle unknown routes
    return ProductRoutePath.unknown();
  }

  @override
  RouteInformation restoreRouteInformation(ProductRoutePath path) {
    if (path.isUnknown) {
      return RouteInformation(location: '/404');
    }
    if (path.isHomePage) {
      return RouteInformation(location: '/');
    }
    if (path.isDetailsPage) {
      return RouteInformation(location: '/article/${path.id}');
    }
    return null;
  }
}

class DataSearch extends SearchDelegate<String> {
  ValueChanged<Product> onItemTapped;

  DataSearch({@required this.onItemTapped});

  @override
  List<Widget> buildActions(BuildContext context) {
    // TODO: implement buildActions
    return [
      IconButton(
          icon: Icon(Icons.clear),
          onPressed: () {
            query = "";
          })
    ];
  }

  @override
  Widget buildLeading(BuildContext context) {
    // TODO: implement buildLeading
    return IconButton(
        icon: AnimatedIcon(
          icon: AnimatedIcons.menu_arrow,
          progress: transitionAnimation,
        ),
        onPressed: () {
          close(context, null);
        });
  }

  @override
  Widget buildResults(BuildContext context) {
    if (query.trim().length == 0) {
      return Text("");
    }

    return FutureBuilder<List<Product>>(
      future: fetchArticle(query),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return ProductListScreen(
              items: snapshot.data, onTapped: onItemTapped);
        } else if (snapshot.hasError) {
          return Center(
            child: Text("${snapshot.error}"),
          );
        }

        return Center(
          child: CircularProgressIndicator(),
        );
      },
    );
  }

  @override
  Widget buildSuggestions(BuildContext context) {
    if (query.trim().length > 0) {
      return FutureBuilder<List<Product>>(
        future: fetchArticle(query),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return ProductListSuggestions(
              articles: snapshot.data,
              onPressed: _onPressed,
            );
          } else if (snapshot.hasError) {
            return Center(
              child: Text("${snapshot.error}"),
            );
          }

          return Center(
            child: CircularProgressIndicator(),
          );
        },
      );
    } else {
      query = '';
      return Text("");
    }
  }

  void _onPressed(String dato) {
    query = dato;
  }
}

Future<List<Product>> fetchArticle(String qSearch) async {
  final response = await http.get(
      'https://somedomain/sidic/articles_json.php?word=' + qSearch);

  if (response.statusCode == 200) {
    return parseArticles(response.body);
  } else {
    throw Exception('Failed to load album');
  }
}

List<Product> parseArticles(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
  return parsed.map<Product>((json) => Product.fromJson(json)).toList();
}

class ProductListSuggestions extends StatelessWidget {
  final List<Product> articles;
  final onPressed;

  ProductListSuggestions({Key key, this.articles, this.onPressed})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: articles.length,
      itemBuilder: (context, index) {
        return Row(
          children: [
            Expanded(
              child: TextButton(
                child: Text(articles[index].name),
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) =>
                          ProductDetailsScreen(product: articles[index]),
                    ),
                  );
                },
              ),
            ),
            Expanded(
              child: IconButton(
                icon: Icon(Icons.add),
                onPressed: () {
                  this.onPressed(articles[index].name);
                },
              ),
            )
          ],
        );
      },
    );
  }
}

@mduccc
Copy link

mduccc commented May 17, 2022

I asked myself, where is the getter currentConfiguration used?

Cannot understand why you declare and implement currentConfiguration, but not reference to it. Until I read code of RouterDelegate class.

Should be changed from:

  BookRoutePath get currentConfiguration {
    if (show404) {
      return BookRoutePath.unknown();
    }
    return _selectedBook == null
        ? BookRoutePath.home()
        : BookRoutePath.details(books.indexOf(_selectedBook));
  }

to:

  @override
  BookRoutePath get currentConfiguration {
    if (show404) {
      return BookRoutePath.unknown();
    }
    return _selectedBook == null
        ? BookRoutePath.home()
        : BookRoutePath.details(books.indexOf(_selectedBook));
  }

@vgribok
Copy link

vgribok commented May 17, 2022

I have continued to refine this sample into a usable starter project with tab-based navigation: https://github.com/vgribok/flutter_nav2_oop. I added Riverpod state management and state restoration, among quite a few other features. The example directory shows the sample app with nearly zero boilerplate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment