问题
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
.
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:
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;
});
},```
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:
You can take the following approach.
- You receive the complete data in snapshot.
- Have a hierarchy of widgets like : StreamBuilder( ValueListenableBuilder( ListView.Builder ) )
- Create ValueNotifier and give it to ValueListenable builder.
- Use Search view to change value of ValueNotifier.
- 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
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';
}
}
来源:https://stackoverflow.com/questions/59397792/flutter-implementing-a-search-feature-for-data-from-a-streambuilder-with-listvi