Home > Software design >  How to search JSON items inside ListView flutter?
How to search JSON items inside ListView flutter?

Time:09-22

I decided not to use the searchdelegate but to use the regular search in JSON (cause I need to solve this problem in the near 2 hours). The problem is that everything seems to work well (I have no errors in the stacktrace), but I just can't understand why the search does not work. I decided that it was necessary to use the Future inside the search widget, but this also did not give any results. Can someone point me to what exactly is going wrong? Maybe I need to create an array to add the search results too? I use not just an ordinary JSON, but it goes through a sorting algorithm and I have seen in other solutions that people use a list of elements, and then fetch one element from all: like this - Future <List<Post>> fetchAllPosts and Future<Post> fetchPost. But I'm doing this:

class MarshrutesPage extends StatefulWidget {
  final int ttId;

  MarshrutesPage({this.ttId});

  @override
  _MarshrutesPageState createState() => _MarshrutesPageState();
}

class _MarshrutesPageState extends State<MarshrutesPage> {
  Box<RouteWithStops> favoriteRoutesBox;
  TransportService service = getIt<TransportService>();

  @override
  void initState() {
    super.initState();
    favoriteRoutesBox = Hive.box(favoritesBox);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(
              Icons.search,
              color: Colors.white,
            ),
            onPressed: () {
              showSearch(context: context, delegate: SearchBar());
            },
          ),
          IconButton(
            icon: Icon(
              Icons.favorite,
              color: Colors.white,
            ),
            onPressed: () {
              Navigator.pushNamed(context, 'favorite');
            },
          ),
        ],
        elevation: 0.0,
        backgroundColor: Colors.green,
        title: Text(
          'numbers',
          style: GoogleFonts.montserrat(
              color: Colors.white, fontWeight: FontWeight.w400),
        ),
      ),
      body: FutureBuilder(
          future: service.getMarshrutWithStops(widget.ttId),
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
            List<RouteWithStops> routes = snapshot.data;
            print(routes?.toString());
            return (routes == null)
                ? Center(child: CircularProgressIndicator())
                : ValueListenableBuilder(
                    valueListenable: favoriteRoutesBox.listenable(),
                    builder: (BuildContext context, value, Widget child) {
                      return ListView.builder(
                        itemCount: routes.length   1,
                        itemBuilder: (context, index) {
                          return index == 0
                              ? _searchBar()
                              : _listItem(index - 1, routes);
                        },
                      );
                    },
                  );
          }),
      bottomNavigationBar: BottomAppBar(
        child: Row(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              IconButton(
                  icon: Icon(
                    Icons.message,
                    color: Colors.grey,
                  ),
                  tooltip: 'to devs',
                  onPressed: () async {
                    await launch("mailto: @gmail.com");
                  }),
              IconButton(
                  icon: Icon(Icons.home, color: Colors.grey), onPressed: () {}),
              IconButton(
                  icon: Icon(Icons.notifications, color: Colors.grey),
                  onPressed: () {}),
              IconButton(
                  icon: Icon(Icons.map, color: Colors.grey), onPressed: () {}),
            ]),
      ),
    );
  }

  Widget getIcon(int index) {
    if (favoriteRoutesBox.containsKey(index)) {
      return Icon(Icons.favorite, color: Colors.red);
    }
    return Icon(Icons.favorite_border);
  }

  void onFavoritePress(int index) {
    List<RouteWithStops> routes;
    if (favoriteRoutesBox.containsKey(index)) {
      favoriteRoutesBox.delete(index);
      return;
    }
    favoriteRoutesBox.put(index, routes[index]);
  }

  _listItem(index, List<RouteWithStops> routes) {
    return ListTile(
      title: Text(routes[index].route.mrTitle),
      leading: Text(
        routes[index].route.mrNum,
        style: TextStyle(color: Colors.green, fontSize: 20),
      ),
      trailing: IconButton(
        icon: getIcon(index),
        onPressed: () => onFavoritePress(index),
      ),
      onTap: () {
        Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => StopsPage(
                      routeWithStops: routes[index],
                    )));
      },
    );
  }

  _searchBar() {
    return FutureBuilder(
      future: service.getMarshrutWithStops(widget.ttId),
      builder:
          (BuildContext context, AsyncSnapshot<List<RouteWithStops>> snapshot) {
        List<RouteWithStops> routes = snapshot.data;
        print('test1');
        return (routes == null)
            ?  Center(child: CircularProgressIndicator()) : Padding(
                padding: const EdgeInsets.all(8),
                child: TextField(
                  decoration: InputDecoration(hintText: 'Search',
                    hoverColor: Colors.green,
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10),
                    ),
                  prefixIcon: Icon(Icons.search),),
                  onChanged: (text) {
                    text = text.toLowerCase();
                    setState(() {
                      routes = routes.where((element) {
                        var routesTitle = element.route.mrTitle.toLowerCase();
                        return routesTitle.contains(text);
                      }).toList();
                      print('test2');
                    });
                  },
                ),
              );
      },
    );
  }
}

Also i think that it dosen't work because I use the parametrs - ttId.

The algorithm I use

Future<List<RouteWithStops>> getMarshrutWithStops(int ttId) async {
    if (routesbyTransportType.isEmpty) {
      await fetchTransportWithRoutes();
    }
    List<Routes> routes = routesbyTransportType[ttId].routes;
    List<ScheduleVariants> variants = [];

    variants.addAll(await api.fetchSchedule());

    List<RouteWithStops> routesWithStops = [];

    for (Routes route in routes) {
      final routeWithStops = RouteWithStops();

      routesWithStops.add(routeWithStops);
      routeWithStops.route = route;

      routeWithStops.variant =
          variants.where((variant) => variant.mrId == route.mrId).first;

   
    }
    return routesWithStops;
  }

Stacktrace after putting several letters in search bar:

W/IInputConnectionWrapper(25362): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper(25362): endBatchEdit on inactive InputConnection
I/flutter (25362): [Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops']
I/flutter (25362): test2
I/flutter (25362): Basic Vk9MR0E6TkVU

CodePudding user response:

routes = routes.where((element) {

This is setting your local variable named "routes". It won't do anything. I cannot really tell what you wanted to do, maybe set a variable of your state?

CodePudding user response:

The problem was solved quite simply, I just needed to be a little more careful: in general, I need to declare two arrays at the top in the widget tree itself:

List<RouteWithStops> _routes = [];
List<RouteWithStops> _routesToDisplay = [];

The first array is responsible for all the elements of the list that need to be displayed under the search bar. The second array is needed so that when you set up the search and enter a certain name / value there, then it does not leave just the value you just entered, but loads others as well. For example, you are looking for a title by author: Tolstoy "War and Peace". If you leave it as it is, without adding a second array, then you will still have Tolstoy "War and Peace" at the bottom under the search bar as the only item until the next reload. It doesn't have to be that way.

To avoid this, you need to do the following:

 @override
  void initState() {
    service.getMarshrutWithStops(widget.ttId).then((value) {
      setState(() {
        _routes.addAll(value);
        _routesToDisplay = _routes;
      });
    });
    super.initState();
  }

Data from the first array is written to the empty second array, so the search becomes, so to speak, dynamic and now all the author's books are displayed and when the author's surname is erased from the search, the rest of the array is loaded, and not just what was typed in the search.

In the tree of widgets, we do everything the same as in my question, with the only difference that in _listItem we pass only index and nothing else, and in order for the loading to occur correctly, I just need to add the load in the main widget in front of ListView.builder, using ternary operators:

body:  (_routes == null) ? CircularProgressIndicator() : ValueListenableBuilder(
        builder: (BuildContext context, value, Widget child) {  
        return ListView.builder(
                          itemCount: _routesToDisplay.length   1,
                          itemBuilder: (context, index) {
                            return index == 0
                                ? _searchBar()
                                : _listItem(index-1);
                          },
                        );

P.S There is no need for a ValueListenableBuilder, I started to save the selected elements in the database, only this line is needed to load the list items: (_routes == null)? CircularProgressIndicator (): ListView.builder

Also, in the widgets for displaying the list and search, instead of _routes, add _routesToDisplay and it turns out like this:

_listItem(index) {
    return ListTile(
      title: Text(_routesToDisplay[index].route.mrTitle),
      leading: Text(
        _routesToDisplay[index].route.mrNum,
        style: TextStyle(color: Colors.green, fontSize: 20),
      ),
      trailing: IconButton(
        icon: getIcon(index),
        onPressed: () => onFavoritePress(index, _routes),
      ),
      onTap: () {
        Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => StopsPage(
                      routeWithStops: _routes[index],
                    )));
      },
    );
  }

  _searchBar() {
    print('test1');
    return Padding(
      padding: const EdgeInsets.all(8),
      child: TextField(
        decoration: InputDecoration(
          hintText: 'Search',
          hoverColor: Colors.green,
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(10),
          ),
          prefixIcon: Icon(Icons.search),
        ),
        onChanged: (text) {
          text = text.toLowerCase();
          setState(() {
            print('object');
            _routesToDisplay = _routes.where((element) {
              var routesTitle = element.route.mrTitle.toLowerCase();
              print(routesTitle);
              return routesTitle.contains(text);
            }).toList();
          });
        },
      ),
    );
  }

Of course, you can remove all prints and use it as you want. This is quite simple task, but I am noob and wasn't careful as much as it needed.

  • Related