Click here to Skip to main content
15,885,665 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
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:

Dart
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);
                },
              ),
            )
          ],
        );
      },
    );
  }
}


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
Posted
Updated 6-Oct-21 5:35am
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900