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'),
);
}