Créer une Search list avec Flutter

Une fois que vous avez appris à créer une liste avec Flutter, vous vous demander surement comment poursuivre votre apprentissage et comment créer une search list avec Flutter. La technique suivante est simple mais n’est pas la plus simple puisqu’elle est gourmande en ressource. Toutefois, elle est essentielle pour comprendre la logique de base. Si vous êtes confirmé, vous pouvez apprendre à créer une search list optimale avec Flutter sur ce lien.

Création d’un Stateful Widget #

D’abord on créer une nouveau fichier dart et un Stateful Widget. On importe également le widget material.dart ce qui donne ceci:

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  
}

Création de deux listes et d’un controller #

Ensuite on va créer deux listes. Une qui va s’afficher lorsque l’utilisateur n’aura rien tapé dans la barre de recherche qui va s’appeler initialList. On y insérer quelques Strings, ici des animaux pour l’exemple.

Et une qui va afficher les résultats recherchés par l’utilisateur qui est filteredList.

Enfin on va aussi créer ce qu’on appelle un “controller“. C’est un objet qui est en fait un value notifier c’est à dire qu’il va notifier à l’application dès que sa valeur va changer. En bref, dès que l’utilisateur va taper du texte, le controller va se mettre à jour en disant “ma valeur a changée”. On obtient donc ceci:

TextEditingController _textController = TextEditingController();
List<String> initialList = ["Chat", "Chien", "Rat", "Cheval", "Ours"];
List<String> filteredList = List();

Création d’un textfield #

Ensuite on commence par créer une page simple avec une appbar et un widget scaffold avec une column comme body et un textfield. On obtient cela:

 @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Test search')),
        body: Column(
          children: <Widget>[TextField()],
        ));
  }
}

Le textfield #

On va ajouter à notre textfield le controller qu’on avait créé auparavant. On va ensuite ajouter la syntaxe qui va permettre de filtre et identifier le texte recherché se retrouve parmi les éléments de initialList pour qu’on les retournent dans la liste filteredList. On va donc remplir le champ “onChanged”. On va inscrire la variable text.

onChanged: (text)

Puis ensuite on va convertir notre texte en minuscules puisque l’utilisateur pourra soit le mettre en majuscule ou le mettre en minuscule donc on veut s’assurer qu’il ne soit qu’en minuscules.

Ensuite on va créer une fonction sans la nommer dans cette exemple qui va s’actualiser instantanément. Ainsi c’est là qu’on va inscrire que, lorsque un élément correspond à ce que notre variable text contient, notre application doit créer une nouvelle liste qui renvoi la liste d’éléments correspondant. Ceci est permis par la méthode where qui recherche un des éléments de la liste contient le String “text”.

TextField(
              controller: _textController,
              onChanged: (text) {
                text = text.toLowerCase();
                setState(() {
                  filteredList = initialList
                      .where((element) => element.toLowerCase().contains(text))
                      .toList();
                });
              },
            )

Les différents cas potentiels #

On va régir les différents cas potentiels avec if, else if et else.

Si l’utilisateur n’a rien recherché #

Dans ce cas, la longueur de la liste “filteredList” est nulle. Aussi le controller.text est nul puisque l’utilisateur n’a rien entré. Donc on écrit notre condition de la manière suivante:

 if (filteredList.length == 0 && _textController.text.isEmpty)

Ensuite, on utilise le widget ListView.builder et donc on obtient ceci:

Expanded(
                  child: ListView.builder(
                      itemCount: initialList.length,
                      itemBuilder: (BuildContext context, index) {
                        return Container(
                          height: 50,
                          child: Text(initialList[index]),
                        );
                      }))

Si l’utilisateur a recherché quelque chose mais que rien ne correspond. #

On va donc dire que la liste filteredList.length est égale à 0 puisque la liste ne contient pas d’élément car aucun ne correspond. Le controller.text n’est pas nul donc on écrit la condition suivante.

else if (filteredList.length==0 && _textController.text.isNotEmpty)

Puis on ajoute un container simple pour dire qu’aucune donnée ne correspond à la recherche:

Expanded(
                child: Container(
                  child: Text('Aucune donnée'),
                ),
              )

Si la recherche correspond à un élément de la liste #

Dans ce cas, c’est simple, on a aucune condition à ajouter puisqu’il s’agit de l’ensemble des autres cas donc on demande de nous retourner juste la liste filtrée. On écrit donc:

else
              Expanded(
                child: ListView.builder(
                    itemCount: filteredList.length,
                    itemBuilder: (BuildContext context, index) {
                      return Container(
                        height: 50,
                        child: Text(filteredList[index]),
                      );
                    }),
              )

On a donc bien réussi à créer une search list avec Flutter et voici le résultat:

Searchable list flutter
search list avec flutter

Code final: #

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  TextEditingController _textController = TextEditingController();
  List<String> initialList = ["Chat", "Chien", "Rat", "Cheval", "Ours"];
  List<String> filteredList = List();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Test search')),
        body: Column(
          children: <Widget>[
            TextField(
              controller: _textController,
              onChanged: (text) {
                text = text.toLowerCase();
                setState(() {
                  filteredList = initialList
                      .where((element) => element.toLowerCase().contains(text))
                      .toList();
                });
              },
            ),
            if (filteredList.length == 0 && _textController.text.isEmpty)
              Expanded(
                  child: ListView.builder(
                      itemCount: initialList.length,
                      itemBuilder: (BuildContext context, index) {
                        return Container(
                          height: 50,
                          child: Text(initialList[index]),
                        );
                      }))
            else if (filteredList.length==0 && _textController.text.isNotEmpty)
              Expanded(
                child: Container(
                  child: Text('Aucune donnée'),
                ),
              )
            else
              Expanded(
                child: ListView.builder(
                    itemCount: filteredList.length,
                    itemBuilder: (BuildContext context, index) {
                      return Container(
                        height: 50,
                        child: Text(filteredList[index]),
                      );
                    }),
              ),
          ],
        ));
  }
}