Flutter:使用ListView为StreamBuilder中的数据实现搜索功能 [英] Flutter: implementing a search feature for data from a StreamBuilder with ListView

查看:820
本文介绍了Flutter:使用ListView为StreamBuilder中的数据实现搜索功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的Flutter应用程序中,我有一个与所有用户一起使用的屏幕.用户列表由StreamBuilder生成,该StreamBuilder从Cloud Firestore获取数据并在ListView中显示用户.为了提高功能性,我希望能够使用Appbar中的搜索栏在此用户列表中进行搜索.

In my Flutter app I have a screen with all the users. The list of users is generated by a StreamBuilder, which gets the data from Cloud Firestore and displays the users in a ListView. To improve functionality I want to be able to search through this user list with a search bar in the Appbar.

我尝试了此答案,效果很好,但我不知道如何使它与在我的情况下是StreamBuilder.作为初学者,我将不胜感激!在下面,我包括了我的用户屏幕和StreamBuilder.

I have tried this answer and that worked well but I can't figure out how to get it working with a StreamBuilder in my case. As a flutter beginner I would appreciate any help! Below I have included my user screen and the StreamBuilder.

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

class UsersScreen extends StatefulWidget {
  static const String id = 'users_screen';

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

class _UsersScreenState extends State<UsersScreen> {
  static Map<String, dynamic> userDetails = {};
  static final String environment = userDetails['environment'];
  Widget appBarTitle = Text('Manage all users');
  Icon actionIcon = Icon(Icons.search);
  final TextEditingController _controller = TextEditingController();
  String approved = 'yes';

  getData() async {
    FirebaseUser user = await FirebaseAuth.instance.currentUser();
    return await _firestore
        .collection('users')
        .document(user.uid)
        .get()
        .then((val) {
      userDetails.addAll(val.data);
    }).whenComplete(() {
      print('${userDetails['environment']}');
      setState(() {});
    });
  }

  _printLatestValue() {
    print('value from searchfield: ${_controller.text}');
  }

  @override
  void initState() {
    getData();
    _controller.addListener(_printLatestValue);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: appBarTitle,
          actions: <Widget>[
            IconButton(
              icon: actionIcon,
              onPressed: () {
                setState(() {
                  if (this.actionIcon.icon == Icons.search) {
                    this.actionIcon = Icon(Icons.close);
                    this.appBarTitle = TextField(
                      controller: _controller,
                      style: TextStyle(
                        color: Colors.white,
                      ),
                      decoration: InputDecoration(
                          prefixIcon: Icon(Icons.search, color: Colors.white),
                          hintText: "Search...",
                          hintStyle: TextStyle(color: Colors.white)),
                      onChanged: (value) {
                        //do something
                      },
                    );
                  } else {
                    this.actionIcon = Icon(Icons.search);
                    this.appBarTitle = Text('Manage all users');
                    // go back to showing all users
                  }
                });
              },
            ),
          ]),
      body: SafeArea(
        child: StreamUsersList('${userDetails['environment']}', approved),
      ),
    );
  }
}

class StreamUsersList extends StatelessWidget {
  final String environmentName;
  final String approved;
  StreamUsersList(this.environmentName, this.approved);
  static String dropdownSelected2 = '';

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
        stream: Firestore.instance
            .collection('users')
            .where('environment', isEqualTo: environmentName)
            .where('approved', isEqualTo: approved)
            .snapshots(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(
              child: CircularProgressIndicator(
                backgroundColor: Colors.lightBlueAccent,
              ),
            );
          } else if (snapshot.connectionState == ConnectionState.done &&
              !snapshot.hasData) {
            return Center(
              child: Text('No users found'),
            );
          } else if (snapshot.hasData) {
            return ListView.builder(
                padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
                itemCount: snapshot.data.documents.length,
                itemBuilder: (BuildContext context, int index) {
                  DocumentSnapshot user = snapshot.data.documents[index];
                  return Padding(
                    padding: EdgeInsets.symmetric(
                      horizontal: 7.0,
                      vertical: 3.0,
                    ),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                //This CardCustom is just a Card with some styling
                        CardCustomUsers(
                            title: user.data['unit'],
                            weight: FontWeight.bold,
                            subTitle:
                                '${user.data['name']} - ${user.data['login']}',
                        ),
                      ],
                    ),
                  );
                });
          } else {
            return Center(
              child: Text('Something is wrong'),
            );
          }
        });
  }
}

已编辑

EDITED

我设法以一种更简单的方式实现了搜索功能,而不必更改我的许多代码.对于其他初学者,我提供了以下代码:

I managed to implement the search functionality in a simpler way, without having to change much of my code. For other beginners I have included the code below:

在我的_UsersScreenState内,我在其他变量下添加了String searchResult = '';.然后,将TextFieldonChanged更改为:

Inside my _UsersScreenState I added String searchResult = ''; below my other variables. I then changed the onChanged of the TextField to:

onChanged: (String value) {
                        setState(() {
                          searchResult = value;
                        });
                      },```

我将其传递给StreamUsersList并将其添加到初始化中.在ListView.Builder中,我在(snapshot.data.documents[index].data['login'].contains(searchResult))中添加了if语句.有关示例,请参见我的ListView.Builder的以下代码.

I passed this on to the StreamUsersList and added it in the initialization. And in the ListView.Builder I added an if-statement with (snapshot.data.documents[index].data['login'].contains(searchResult)). See the below code of my ListView.Builder for an example.

else if (snapshot.hasData) {
            return ListView.builder(
                padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
                itemCount: snapshot.data.documents.length,
                itemBuilder: (BuildContext context, int index) {
                  DocumentSnapshot user = snapshot.data.documents[index];
                  final record3 = Record3.fromSnapshot(user);
                  String unitNr = user.data['unit'];
                  if (user.data['login'].contains(searchResult) ||
                      user.data['name'].contains(searchResult) ||
                      user.data['unit'].contains(searchResult)) {
                    return Padding(
                      padding: EdgeInsets.symmetric(
                        horizontal: 7.0,
                        vertical: 3.0,
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
//This CardCustom is just a Card with some styling
                           CardCustomUsers(
                              title: unitNr,
                              color: Colors.white,
                              weight: FontWeight.bold,
                              subTitle:
                                  '${user.data['name']}\n${user.data['login']}',
                          ),
                        ],
                      ),
                    );
                  } else {
                    return Visibility(
                      visible: false,
                      child: Text(
                        'no match',
                        style: TextStyle(fontSize: 4.0),
                      ),
                    );
                  }
                });
          } else {
            return Center(
              child: Text('Something is wrong'),
            );
          }

推荐答案

您可以采用以下方法.

  1. 您将在快照中收到完整的数据.
  2. 具有小部件的层次结构,例如: StreamBuilder( ValueListenableBuilder( ListView.Builder ) )
  3. 创建ValueNotifier并将其提供给ValueListenable构建器.
  4. 使用搜索视图更改ValueNotifier的值.
  5. 当ValueNotifier的值更改时,您的ListView.builder将会重建,届时,如果您要根据查询将过滤后的列表提供给ListView.builder,则它将按您想要的方式为您工作.
  1. You receive the complete data in snapshot.
  2. Have a hierarchy of widgets like : StreamBuilder( ValueListenableBuilder( ListView.Builder ) )
  3. Create ValueNotifier and give it to ValueListenable builder.
  4. Use Search view to change value of ValueNotifier.
  5. When value of ValueNotifier will change your ListView.builder will rebuild and at that time if you are giving the filtered list according to query to the ListView.builder then it will work for you the way you want it.

我希望这会有所帮助,如有任何疑问,请告诉我.

I hope this helps, in case of any doubt please let me know.

已编辑

EDITED

您将不需要ValueNotifier,而是需要StreamBuilder.因此,小部件的最终层次结构为: StreamBuilder( StreamBuilder>( ListView.Builder ) )

You won't need ValueNotifier instead You will need a StreamBuilder. So your final hierarchy of widgets would be: StreamBuilder( StreamBuilder>( ListView.Builder ) )

我没有像您这样的环境,所以我嘲笑了它并创建了一个示例.您可以参考它,希望它能给您一些解决问题的想法.以下是您可以参考的工作代码:

I didn't have an environment like yours so I have mocked it and created an example. You can refer it and I hope it can give you some idea to solve your problem. Following is a working code which you can refer:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: SearchWidget(),
      ),
    ));

class SearchWidget extends StatelessWidget {
  SearchWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          TextField(onChanged: _filter),
          StreamBuilder<List<User>>( // StreamBuilder<QuerySnapshot> in your code.
            initialData: _dataFromQuerySnapShot, // you won't need this. (dummy data).
            // stream: Your querysnapshot stream.
            builder:
                (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
              return StreamBuilder<List<User>>(
                key: ValueKey(snapshot.data),
                initialData: snapshot.data,
                stream: _stream,
                builder:
                    (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
                      print(snapshot.data);
                  return ListView.builder(
                    shrinkWrap: true,
                    itemCount: snapshot.data.length,
                    itemBuilder: (BuildContext context, int index) {
                      return Text(snapshot.data[index].name);
                    },
                  );
                },
              );
            },
          )
        ],
      ),
    );
  }
}

StreamController<List<User>> _streamController = StreamController<List<User>>();
Stream<List<User>> get _stream => _streamController.stream;
_filter(String searchQuery) {
  List<User> _filteredList = _dataFromQuerySnapShot
      .where((User user) => user.name.toLowerCase().contains(searchQuery.toLowerCase()))
      .toList();
  _streamController.sink.add(_filteredList);
}

List<User> _dataFromQuerySnapShot = <User>[
  // every user has same enviornment because you are applying
  // such filter on your query snapshot.
  // same is the reason why every one is approved user.
  User('Zain Emery', 'some_enviornment', true),
  User('Dev Franco', 'some_enviornment', true),
  User('Emilia ONeill', 'some_enviornment', true),
  User('Zohaib Dale', 'some_enviornment', true),
  User('May Mcdougall', 'some_enviornment', true),
  User('LaylaRose Mitchell', 'some_enviornment', true),
  User('Beck Beasley', 'some_enviornment', true),
  User('Sadiyah Walker', 'some_enviornment', true),
  User('Mae Malone', 'some_enviornment', true),
  User('Judy Mccoy', 'some_enviornment', true),
];

class User {
  final String name;
  final String environment;
  final bool approved;

  const User(this.name, this.environment, this.approved);

  @override
  String toString() {
    return 'name: $name environment: $environment approved: $approved';
  }
}

这篇关于Flutter:使用ListView为StreamBuilder中的数据实现搜索功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆