Flutter - How to perform a search in a listview?

Asked

Viewed 2,254 times

2

Hello!

I found many tutorials on the internet, but I could not implement any successfully. Most of these tutorials, work with the creation of 2 lists, an initial and a secondary to be manipulated. Is this really the best form of implementation? I’m using the Bloc standard. So I’m having some difficulty handling the data.

Follows code:

class FollowingPage extends StatefulWidget {
  final Player players;

  const FollowingPage({Key key, this.players}) : super(key: key);

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

class _FollowingPageState extends State<FollowingPage> {
  FollowingBloc _followingBloc;
  TextEditingController editingController = TextEditingController();

  @override
  void initState() {
    _followingBloc = FollowingBloc();
    _followingBloc.dispatch(FollowingEventList(context: context));
    super.initState();
  }

  @override
  void dispose() {
    _followingBloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<FollowingEvent, FollowingState>(
        bloc: _followingBloc,
        builder: (context, state) {
          if (state is FollowingStateLoading) {
            return Center(child: CircularProgressIndicator());
          }

          if (state is FollowingStateError) {
            return Center(child: Text(state.message));
          }

          if (state is FollowingStateInitial) {
            return Center(child: Text("Nenhuma informação."));
          }

          if (state is FollowingStateSuccess) {
            Result _following = state.followingModel.result;

            return Scaffold(
              appBar: AppBarCustom(),
              body: Container(
                child: Column(
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.all(7.0),
                      child: TextField(
                        onChanged: (text) {

                        },
                        controller: editingController,
                        decoration: InputDecoration(
                            labelText: "Procurar jogadores",
                            hintText: "Informe o nome do jogador",
                            prefixIcon: Icon(Icons.search),
                            border: OutlineInputBorder(
                                borderRadius:
                                    BorderRadius.all(Radius.circular(25.0)))),
                      ),
                    ),
                    Divider(),
                    Expanded(
                      child: ListView.builder(
                        scrollDirection: Axis.vertical,
                        shrinkWrap: true,
                        itemCount: _following.players.length,
                        itemBuilder: (context, index) {
                          if (_following.players.isEmpty) {
                            return Container(
                              height: MediaQuery.of(context).size.height * 0.6,
                              child: Center(
                                child: Text(
                                    "Você ainda não segue outros jogadores"),
                              ),
                            );
                          }

                          Player _dataCurrent = _following.players[index];


                          return Card(
                            child: ListTile(
                              leading: CircleAvatar(
                                radius: 25,
                                backgroundImage: _dataCurrent.avatar != null
                                    ? NetworkImage(_dataCurrent.avatar)
                                    : AssetImage('assets/img/person.png'),
                                backgroundColor: Colors.transparent,
                              ),
                              //title: Text(_dataCurrent.name),
                              title: Text(_dataCurrent.name != null
                                  ? _dataCurrent.name
                                  : "Não informado"),
                              trailing: Icon(Icons.arrow_forward_ios),
                              onTap: () {
                                Navigator.of(context).push(MaterialPageRoute(
                                    builder: (context) => FollowingDetails(
                                        player: _dataCurrent)));
                              },
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                ),
              ),
            );
          }
          return null;
        });
  }
}

As you may notice I’m still beginner. I just want to filter this list according to what the user inform!

Follows a print: tela da listview

  • I find it strange that even following the tutorials you did not use any StreamBuilder that is essential in the use of Bloc Pattern... I do not know the class you used BlocBuilder<FollowingEvent, FollowingState>, but I will formulate an answer with the way I use the Bloc and as soon as possible I will be answering your question.

  • 1

    I’ll wait for your reply to show you where Streambuilder is. I separate these calls into another class! But I look forward to your contribution, my friend!

1 answer

4


The Bloc Pattern brings us, among many other things, the possibility of redesigning only part of our Widgets without having to "modify the whole tree".

The way you did, putting your entire widgets structure inside the BlocBuilder will cause your entire tree to be redesigned, thus losing the sense of using Pattern.

You then need to separate your Bloc class by Streams and link to it only specific Widgets making use of StreamBuilder.

I created an example based on what you showed us, not 100% correct, but the logic of to understand and you can tailor your need...

I used the package bloc_pattern, making use of their libraries

import 'package:bloc_pattern/bloc_pattern.dart';
import 'package:rxdart/rxdart.dart';

Let’s go to the examples

Bloc class

class FollowingBloc extends BlocBase{

  @override
  void dispose() {
    playersController.close();
  }

  bool isFilter = false;

  FollowingBloc(){
    /* Pega os dados recebidos pelo "inPlayers" e joga nas variáveis*/
    playersController.stream.listen((data){
      players = data;
      if (!isFilter)
        playersFiltered = data;
    });
  }

  List<Player> players;
  List<Player> playersFiltered;

  final BehaviorSubject<List<Player>> playersController = BehaviorSubject<List<Player>>(seedValue: []);
  Stream<List<Player>> get outPlayers => playersController.stream;
  Sink<List<Player>> get inPlayers => playersController.sink;

  void findPlayerName(String name){
    List<Player> players = playersController.value.where((player) => player.name.toLowerCase().contains(name)).toList();
    isFilter=true;
    inPlayers.add(players);
    isFilter=false;
  }
}

We have created here the class that will take control of your Streams. In it we create control of your player list playersController and the property inPlayers and outPlayers which will be the input and output of the data, respectively.
We also created the method findPlayers() who will be responsible for filtering the data in your list of players and return to the Stream the new data.

Fallowingpage class

class FollowingPage extends StatelessWidget {
  FollowingBloc _followingBloc = FollowingBloc();
  TextEditingController editingController = TextEditingController();

  FollowingPage({this.players});
  final List<Player> players;
  Player filteredPlayers;

  @override
  Widget build(BuildContext context) {
    if (!players.isEmpty)
      _followingBloc.inPlayers.add(players);

    return Scaffold(
      body: Container(
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(7.0),
              child: TextField(
                controller: editingController,
                decoration: InputDecoration(
                    labelText: "Procurar jogadores",
                    hintText: "Informe o nome do jogador",
                    prefixIcon: Icon(Icons.search),
                    border: OutlineInputBorder(
                        borderRadius:
                            BorderRadius.all(Radius.circular(25.0)))),
                onChanged: (text) {
                  _followingBloc.findPlayerName(text);
                },
              ),
            ),
            Divider(),
            Expanded(
              child: StreamBuilder(
                stream: _followingBloc.outPlayers,
                builder: (context, snapshot) {
                  if(!snapshot.hasData)
                    return Center(child: CircularProgressIndicator());
                  else {
                    return ListView.builder(
                      scrollDirection: Axis.vertical,
                      shrinkWrap: true,
                      itemCount: snapshot.data?.length,
                      itemBuilder: (context, index) {
                        if (snapshot.data.isEmpty) {
                          return Container(
                            height: MediaQuery.of(context).size.height * 0.6,
                            child: Center(
                              child: Text(
                                  "Você ainda não segue outros jogadores"),
                            ),
                          );
                        }

                        Player _dataCurrent = snapshot.data[index];

                        return Card(
                          child: ListTile(
                            leading: CircleAvatar(
                              radius: 25,
                              backgroundImage: _dataCurrent.avatar != null
                                  ? NetworkImage(_dataCurrent.avatar)
                                  : AssetImage('assets/img/person.png'),
                              backgroundColor: Colors.transparent,
                            ),
                            title: Text(_dataCurrent.name != null
                                ? _dataCurrent.name
                                : "Não informado"),
                            trailing: Icon(Icons.arrow_forward_ios),
                            onTap: () {
                              /*Navigator.of(context).push(MaterialPageRoute(
                                  builder: (context) => FollowingDetails(
                                      player: _dataCurrent)));*/
                            },
                          ),
                        );
                      },
                    );
                  }
                }
              )
            ),
          ],
        ),
      ),
    );
  }

I modified your hierarchy of Widgets as I commented at the beginning of the reply and as you can see, only the list of players is now within one StreamBuilder, because only it will have to be redesigned as the filter is applied.
Notice that I also modified your class of StatefulWidget for Stateless as it will be worked with Streams of the Bloc, there is no need, for example, to use the setState().

Understanding everything that’s been done

  1. I’ve turned your class into one statellesWidget, making sure your Widgets tree is drawn only once, because the way your list was and your TextField would always be redesigned, spending all processing.
  2. We created your Bloc class to control the data. Already applying the filter to your list. Note that I used to control two lists, being the players and the playersFiltered, because we need to keep the data unfiltered somewhere so we don’t need to request the API at all times.
  3. I applied the method findPlayers() in the onChange of your TextFied, so whenever you type something into it, your list will be filtered, the new data will be sent to your stream playersController where it will process the information and execute its output outPlayers, which in turn will redraw your player list.

Below is the complete example, remembering that I did it in a simple way based on other classes I use and did not perform tests in it.

import 'package:flutter/material.dart';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'package:rxdart/rxdart.dart';

class Player{
  String name;
  String avatar;
}

class FollowingBloc extends BlocBase{

  @override
  void dispose() {
    playersController.close();
  }

  bool isFilter = false;

  FollowingBloc(){

    playersController.stream.listen((data){
      players = data;
      if (!isFilter)
        playersFiltered = data;
    });
  }

  List<Player> players;
  List<Player> playersFiltered;

  final BehaviorSubject<List<Player>> playersController = BehaviorSubject<List<Player>>(seedValue: []);
  Stream<List<Player>> get outPlayers => playersController.stream;
  Sink<List<Player>> get inPlayers => playersController.sink;

  void findPlayerName(String name){
    List<Player> players = playersController.value.where((player) => player.name.toLowerCase().contains(name)).toList();
    isFilter=true;
    inPlayers.add(players);
    isFilter=false;
  }
}

class FollowingPage extends StatelessWidget {
  FollowingBloc _followingBloc = FollowingBloc();
  TextEditingController editingController = TextEditingController();

  FollowingPage({this.players});
  final List<Player> players;
  Player filteredPlayers;

  @override
  Widget build(BuildContext context) {
    if (!players.isEmpty)
      _followingBloc.inPlayers.add(players);

    return Scaffold(
      body: Container(
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(7.0),
              child: TextField(
                controller: editingController,
                decoration: InputDecoration(
                    labelText: "Procurar jogadores",
                    hintText: "Informe o nome do jogador",
                    prefixIcon: Icon(Icons.search),
                    border: OutlineInputBorder(
                        borderRadius:
                            BorderRadius.all(Radius.circular(25.0)))),
                onChanged: (text) {
                  _followingBloc.findPlayerName(text);
                },
              ),
            ),
            Divider(),
            Expanded(
              child: StreamBuilder(
                stream: _followingBloc.outPlayers,
                builder: (context, snapshot) {
                  if(!snapshot.hasData)
                    return Center(child: CircularProgressIndicator());
                  else {
                    return ListView.builder(
                      scrollDirection: Axis.vertical,
                      shrinkWrap: true,
                      itemCount: snapshot.data?.length,
                      itemBuilder: (context, index) {
                        if (snapshot.data.isEmpty) {
                          return Container(
                            height: MediaQuery.of(context).size.height * 0.6,
                            child: Center(
                              child: Text(
                                  "Você ainda não segue outros jogadores"),
                            ),
                          );
                        }

                        Player _dataCurrent = snapshot.data[index];

                        return Card(
                          child: ListTile(
                            leading: CircleAvatar(
                              radius: 25,
                              backgroundImage: _dataCurrent.avatar != null
                                  ? NetworkImage(_dataCurrent.avatar)
                                  : AssetImage('assets/img/person.png'),
                              backgroundColor: Colors.transparent,
                            ),
                            title: Text(_dataCurrent.name != null
                                ? _dataCurrent.name
                                : "Não informado"),
                            trailing: Icon(Icons.arrow_forward_ios),
                            onTap: () {
                              /*Navigator.of(context).push(MaterialPageRoute(
                                  builder: (context) => FollowingDetails(
                                      player: _dataCurrent)));*/
                            },
                          ),
                        );
                      },
                    );
                  }
                }
              )
            ),
          ],
        ),
      ),
    );
  }
}
  • 1

    Dude, just sensational. You were totally didactic. In addition to solving my problem, you gave explanation about other tasks that I had been performing incorrectly. I don’t even know how to thank you, thank you so much for your time!

  • I’m glad you could help! Just marking the answer as ACCEPTED would help a lot, both the site and me, of course, if you think your doubt is solved.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.