0

I have a flutter application that gets a list of users online and it displays the data in a listview. I have tried to implement a search function to search through the data, however every time I try to type anything in the textfield, the whole page refreshes and performs an api call to fetch the data again. This is the code for getting and displaying the data

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Prospect List"),
        centerTitle: true,
      ),
      body: prospectData(),
    );
  }

  Widget prospectData() {
    return FutureBuilder(
        future: _fetchData(),
        builder: (BuildContext context, AsyncSnapshot<ProspectList> snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
            case ConnectionState.waiting:
            case ConnectionState.active:
              return Center(
                child: CircularProgressIndicator(),
              );
            case ConnectionState.done:
              if (snapshot.hasError)
                return Text("There was an error: ${snapshot.error}");
              prospectList = snapshot.data;
              return ListView.builder(
                itemCount: prospectList.data.length + 1,
                itemBuilder: (context, i) {
                  if (prospectList.data.length > 0) {
                    return i == 0 ? _searchBar() : _prospectData(i - 1);
                  } else {
                    return Center(child: CircularProgressIndicator());
                  }
                },
              );
            default:
              return null;
          }
        });
  }

  _prospectData(i) {
    final name =
        prospectList.data[i].firstname + " " + prospectList.data[i].lastname;
    final phone = prospectList.data[i].phone;
    final email = prospectList.data[i].email;
    return ListTile(
      title: Text(
        name,
        style: TextStyle(fontSize: 18),
      ),
      subtitle: Text(
        phone,
        style: TextStyle(fontSize: 16),
      ),
      onTap: () => Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => CustomerInfo(
                    name: name,
                    phone: phone,
                    email: email,
                  ))),
    );
  }

This is the search method I tried to implement but does not work properly

 _searchBar() {
    return Container(
      child: Padding(
          padding: EdgeInsets.all(8.0),
          child: TextField(
              decoration: InputDecoration(hintText: 'Search...'),
              onChanged: (text) {
                text = text.toLowerCase();
                setState(() {
                  _prospectDisplay = prospectList.data.where((post) {
                    var postTitle = post.firstname.toLowerCase();
                    return postTitle.contains(text);
                  }).toList();
                });
              })),
    );
  }

EDIT I found the solution I was looking for Here

3 Answers 3

2

Every time you update the State of your main Widget, the Widget gets rebuilt. This causes your Widget prospectData() to run again, fetch the data and reinitialize your prospectList.

I would suggest a different structure for your Widget Tree, made of two Widgets:

SearchPage > ProspectList

SearchPage is a StatelessWidget in charge of fetching the list of prospects.

ProspectList is a StatefulWidget (or, in my sample, a HookWidget) in charge of displaying and filtering the fetched list of prospects.

enter image description here

Full source code:

import 'package:faker/faker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'main.freezed.dart';

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Search Demo',
      home: SearchPage(),
    ),
  );
}

class SearchPage extends StatelessWidget {
  Future<List<Customer>> _fetchData() async {
    await Future.delayed(Duration(seconds: 2));
    return dummyData;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Prospects List"),
        centerTitle: true,
      ),
      body: FutureBuilder(
          future: _fetchData(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return (snapshot.hasError)
                  ? Text("There was an error: ${snapshot.error}")
                  : ProspectList(prospects: snapshot.data);
            }
            return Center(child: CircularProgressIndicator());
          }),
    );
  }
}

class ProspectList extends HookWidget {
  final List<Customer> prospects;

  const ProspectList({Key key, this.prospects}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final searchTerm = useState('');
    return ListView(
      children: [
        Padding(
          padding: EdgeInsets.all(8.0),
          child: TextField(
            decoration: InputDecoration(hintText: 'Search...'),
            onChanged: (value) => searchTerm.value = value,
          ),
        ),
        ...prospects
            .where((prospect) =>
                searchTerm.value.isEmpty ||
                prospect.name.toLowerCase().contains(searchTerm.value))
            .map(
              (prospect) => ListTile(
                title: Text(prospect.name, style: TextStyle(fontSize: 18)),
                subtitle: Text(prospect.phone, style: TextStyle(fontSize: 16)),
                onTap: () => {},
              ),
            )
            .toList(),
      ],
    );
  }
}

final faker = Faker();
final dummyData = List.generate(
    100,
    (index) => Customer(
          firstname: faker.person.firstName(),
          lastname: faker.person.lastName(),
          phone: '+1${faker.randomGenerator.integer(999999999)}',
          email: faker.internet.email(),
        ));

@freezed
abstract class Customer implements _$Customer {
  const factory Customer({
    String firstname,
    String lastname,
    String phone,
    String email,
  }) = _Customer;

  const Customer._();

  String get name => '$firstname $lastname';
}

Dependencies

In this sample, I used the following dependencies:

  • flutter_hooks, as a substitute for StatefulWidgets
  • freezed, to define my Customer model. Note that main.freezed.dart is just the file generated by the build_runner for freezed.
  • faker, to generate a bunch of random Customers

Trouble running the sample?

Step-by-step:

  1. Add the following dependencies to your pubspec.yaml:
name: stackoverflow
description: A new Flutter project.

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  freezed_annotation: ^0.12.0
  faker: ^1.3.0
  flutter_hooks: ^0.16.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  freezed: ^0.12.7
  build_runner: ^1.11.5

flutter:

  uses-material-design: true
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  1. Run the build_runner inside the root directory of your project:
flutter pub run build_runner watch --delete-conflicting-outputs

This will generate the class with freezed

  1. Run your project
Sign up to request clarification or add additional context in comments.

6 Comments

I am having a hard time trying to use this example.
Sorry. Check the last part of my answer, and let me know if you have any question.
I am getting this error The parameter firstname of Customer is non-nullable but is neither required nor marked with @Default
Sorry, the last version of freezed (0.14) is already in null-safety. Use the previous version instead, as defined in my pubspec.yaml. Or, let me know if you already migrated to null safety.
I downgraded the dependencies and run it. It runs ok. Thanks mate
|
0

So, that's an error with the way you implement the TextField.

Try changing onChanged to onSubmitted, and everything would work as expected.

onChanged is called every time any keyboard key is pressed.

onSubmitted only when the user presses the Submit Button, resulting in only 1 API call.

4 Comments

I have changed it, but it still does not search
Where have you placed the _searchBar in the code ??
Here itemBuilder: (context, i) {if (prospectList.data.length > 0) {return i == 0 ? _searchBar() : _prospectData(i - 1); just before the ListTile
With onSubmitted, try also changing textInputAction: TextInputAction.done in the TextField
0

Here the problem is with FutureBuilder. In order to avoid unwanted refreshes you need to first declare your Future and then in your initState you should call the future:

Future myFuture;
@override
  void initState() {
    super.initState();
    myFuture = _fetchData();
  }

...
FutureBuilder(
        future: myFuture,
...

You can find more detailed info in this video regarding your problem: https://www.youtube.com/watch?v=LYN46233cws&t=644s&ab_channel=DevGrub

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.