Flutter:使用 ListView 实现对来自 StreamBuilder 的数据的搜索功能
Flutter: implementing a search feature for data from a StreamBuilder with ListView
在我的 Flutter 应用程序中,我有一个包含所有用户的屏幕。用户列表由 StreamBuilder
生成,它从 Cloud Firestore 获取数据并将用户显示在 ListView
中。为了改进功能,我希望能够使用 Appbar
中的搜索栏搜索此用户列表。
我试过 this answer 并且效果很好,但我不知道如何让它与 StreamBuilder
一起工作。作为一个 flutter 初学者,我将不胜感激任何帮助!下面我包括了我的用户屏幕和 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'),
);
}
});
}
}
已编辑
我设法以更简单的方式实现了搜索功能,而无需更改我的大部分代码。对于其他初学者,我提供了以下代码:
在我的 _UsersScreenState
中,我在其他变量下方添加了 String searchResult = '';
。然后我将 TextField
的 onChanged
更改为:
onChanged: (String value) {
setState(() {
searchResult = value;
});
},```
我将其传递给 StreamUsersList
并在初始化中添加了它。在 ListView.Builder
中,我添加了带有 (snapshot.data.documents[index].data['login'].contains(searchResult))
的 if 语句。例如,请参阅我的 ListView.Builder
的以下代码。
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'),
);
}
您可以采用以下方法。
- 您在快照中收到了完整的数据。
- 具有像这样的小部件层次结构:
流生成器(
ValueListenableBuilder(
ListView.Builder
)
)
- 创建 ValueNotifier 并将其提供给 ValueListenable 构建器。
- 使用搜索视图更改 ValueNotifier 的值。
- 当 ValueNotifier 的值发生变化时,您的 ListView.builder 将重建,届时如果您根据对 ListView.builder 的查询提供过滤列表,那么它将按照您想要的方式为您工作它。
希望对您有所帮助,如有任何疑问,请告诉我。
已编辑
您不需要 ValueNotifier,您将需要 StreamBuilder。所以你最终的小部件层次结构是:
流生成器(
流生成器>(
ListView.Builder
)
)
我没有像你这样的环境,所以我模拟了它并创建了一个例子。你可以参考它,我希望它能给你一些想法来解决你的问题。以下是您可以参考的工作代码:
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 应用程序中,我有一个包含所有用户的屏幕。用户列表由 StreamBuilder
生成,它从 Cloud Firestore 获取数据并将用户显示在 ListView
中。为了改进功能,我希望能够使用 Appbar
中的搜索栏搜索此用户列表。
我试过 this answer 并且效果很好,但我不知道如何让它与 StreamBuilder
一起工作。作为一个 flutter 初学者,我将不胜感激任何帮助!下面我包括了我的用户屏幕和 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'),
);
}
});
}
}
已编辑
我设法以更简单的方式实现了搜索功能,而无需更改我的大部分代码。对于其他初学者,我提供了以下代码:
在我的 _UsersScreenState
中,我在其他变量下方添加了 String searchResult = '';
。然后我将 TextField
的 onChanged
更改为:
onChanged: (String value) {
setState(() {
searchResult = value;
});
},```
我将其传递给 StreamUsersList
并在初始化中添加了它。在 ListView.Builder
中,我添加了带有 (snapshot.data.documents[index].data['login'].contains(searchResult))
的 if 语句。例如,请参阅我的 ListView.Builder
的以下代码。
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'),
);
}
您可以采用以下方法。
- 您在快照中收到了完整的数据。
- 具有像这样的小部件层次结构: 流生成器( ValueListenableBuilder( ListView.Builder ) )
- 创建 ValueNotifier 并将其提供给 ValueListenable 构建器。
- 使用搜索视图更改 ValueNotifier 的值。
- 当 ValueNotifier 的值发生变化时,您的 ListView.builder 将重建,届时如果您根据对 ListView.builder 的查询提供过滤列表,那么它将按照您想要的方式为您工作它。
希望对您有所帮助,如有任何疑问,请告诉我。
已编辑
您不需要 ValueNotifier,您将需要 StreamBuilder。所以你最终的小部件层次结构是: 流生成器( 流生成器>( ListView.Builder ) )
我没有像你这样的环境,所以我模拟了它并创建了一个例子。你可以参考它,我希望它能给你一些想法来解决你的问题。以下是您可以参考的工作代码:
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';
}
}