Hello
I have an implementation of Flutter Navigator 2.0 in my app, wich one has a SearchDelegate implemented too, but when I do tap on the search box search button, my app launch an error, the console prints this message:
I think I have a bad implementation of Navigator 2.0 or maybe the SearchDelegate is not compatible with the Navigator 2.0, any help is welcome, in advance I thank you.
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)
Here is an image of my app
https://i.stack.imgur.com/hYT3P.jpg
Here is my code:
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;
}
_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);
if (uri.pathSegments.length == 0) {
return ProductRoutePath.home();
}
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);
}
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);
if (uri.pathSegments.length == 0) {
return ProductRoutePath.home();
}
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);
}
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) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
})
];
}
@override
Widget buildLeading(BuildContext context) {
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);
},
),
)
],
);
},
);
}
}
What I have tried:
I read this article to implement Navigator 2.0
https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade but the things turn wild between Navigator 2.0 and the SearchDelegate