I have a collection 'all' in which I have docs, I each doc I have 2 fields id and name, I want that when the user enters the id or the name it should show suggestions. I want to implement this firestore search in this package material_floating_search_bar > I tried but couldn't figure out how to merge these 2.
floating search bar code: //got from package example
how to implement firestore in this:
Widget buildFloatingSearchBar() {
final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
return FloatingSearchBar(
hint: 'Search...',
scrollPadding: const EdgeInsets.only(top: 16, bottom: 56),
transitionDuration: const Duration(milliseconds: 800),
transitionCurve: Curves.easeInOut,
physics: const BouncingScrollPhysics(),
axisAlignment: isPortrait ? 0.0 : -1.0,
openAxisAlignment: 0.0,
width: isPortrait ? 600 : 500,
debounceDelay: const Duration(milliseconds: 500),
onQueryChanged: (query) {
// Call your model, bloc, controller here.
},
transition: CircularFloatingSearchBarTransition(),
actions: [
FloatingSearchBarAction(
showIfOpened: false,
child: CircularButton(
icon: const Icon(Icons.place),
onPressed: () {},
),
),
FloatingSearchBarAction.searchToClear(
showIfClosed: false,
),
],
builder: (context, transition) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.white,
elevation: 4.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: Colors.accents.map((color) {
return Container(height: 112, color: color);
}).toList(),
),
),
);
},
);
}
Not sure if this is the best way to implement this functionality
1. Get reference of your collection (getColl is variable name and 'All' your collection name).
final CollectionReference getColl = FirebaseFirestore.instance.collection('All');
2. Get QuerySnapshot of your collection in a List ( _getDataFromSnapshot, GetData , dbData names can be changed)
List<GetData> _getDataFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return GetData(
id: doc.get('id') ?? '',
name: doc.get('name') ?? '',
);
}).toList();
}
Stream<List<GetData>> get dbData {
return getColl.snapshots().map(_getDataFromSnapshot);
}
class GetData { final String id,name; GetData({this.id, this.name}) }
3. Do this where you want your search bar
Widget build (BuildContext context {
var datalist = Provider.of<List<GetData>>(context);
// Filter condition.
datalist = datalist.where((_search) {
return _search.id.toLowerCase().contains(key) ||
_search.name.toString().toLowerCase().contains(key);
}).toList();
Then implement your search bar and set onChanged
onChanged: (value) {
// Update the key when the value changes.
setState(() => key = value.toLowerCase());
},
}
Related
The only this I changed in my code and Firebase rtdb is where the data is being fetched from.
Before data was in: "users" - "parents" (Code worked perfectly here)
Now data is in: "users" - schoolName.toString() - "parents" (Code causes an error)
How can I approach/solve this issue?
Thanks.
Error:
E/flutter ( 8683): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: NoSuchMethodError: The method 'forEach' was called on null.
E/flutter ( 8683): Tried calling: forEach(Closure: (dynamic, dynamic) => void)
Code:
Future<List> getParentDetails() async {
schoolName = await getSchoolName();
databaseReference
.child("users")
.child(schoolName.toString())
.child("parents")
.onValue
.listen(
(event) {
if (event.snapshot.exists) {
setState(
() {
var value = event.snapshot.value;
parentList = Map.from(value)
.values
.map((e) => Parents.fromJson(Map.from(e)))
.toList();
},
);
} else {
print("No Data Exists");
}
},
);
return parentList;
}
UI Code:
ListView.builder(
itemCount: parentList.length,
itemBuilder: (context, int index) {
final Parents parents = parentList[index];
final String driverEmail = parents.email;
final String driverName = parents.name;
final String driverPhone = parents.phone;
// final driverRandomId = parents.randomId;
// final String driverUID = driver.uid;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0.2,
child: ExpansionTile(
// collapsedBackgroundColor: Colors.grey,
title: Text(
driverName.toUpperCase(),
style: GoogleFonts.lexendMega(
fontSize: 12,
),
textAlign: TextAlign.center,
),
children: [
Column(
children: [
Text(
driverEmail,
textAlign: TextAlign.center,
style: GoogleFonts.lexendMega(fontSize: 12),
),
SizedBox(
height: 5,
),
Text(
driverPhone,
textAlign: TextAlign.center,
style: GoogleFonts.lexendMega(fontSize: 12),
),
SizedBox(
height: 5,
),
],
)
],
),
),
);
},
),
Class Code:
class Parents {
final String email;
final String name;
final String phone;
Parents({
this.email,
this.name,
this.phone,
});
static Parents fromJson(Map<String, String> json) {
return Parents(
email: json['email'],
name: json['name'],
phone: json['phone'],
);
}
}
You should be able to check whether your snapshot has some data (I'm assuming it returns an AsyncSnapshot, which is also used by widgets like StreamBuilder and FutureBuilder.
https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html
In that case, you can call event.snapshot.hasData to determine whether data is null. If it is, you can instead return an empty list.
I assume you're using this approach as opposed to a FutureBuilder to keep your business logic and UI separate? If there's no specific reasoning, you might want to consider to instead use a FutureBuilder or StreamBuilder instead.
How to disable onTap function when a single click has been clicked. Using Flutter.
This is my code below, kindly help me check it out...
class VoteCalonUmumPage extends StatelessWidget {
const VoteCalonUmumPage({Key? key, required this.title}) : super(key: key);
final String title;
Widget _buildListItem(BuildContext context, DocumentSnapshot document) {
return ListTile(
tileColor: Color(0xff99c2ec),
title: Row(
children: [
Expanded(
child: Text(document['name'],
style: TextStyle(
color: Colors.black87,
fontSize: 20,
)),
),
Container(
decoration: const BoxDecoration(
color: Color(0xffecc399),
),
padding: const EdgeInsets.all(10.0),
child: Text(
document['votes'].toString(),
style: Theme.of(context).textTheme.headline4,
),
),
],
),
onTap: () {
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
},
);
}
}
Checkout below code a simple logic it may help you ,
bool isLoading = false; //global variable
onTap: () {
if(!isLoading)
{
isLoading = true;
try{
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap = await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {'votes': freshSnap['votes'] + 1,});
isLoading = false;
});
}catch((e){
isLoading = false
});
}
},
In order to actually disable the onTap handler you have to pass null to onTap. I would create a variable inside this class to keep track of if the onTap has been pressed yet, and if it has, pass null to onTap rather than your callback function.
onTap: onTapPressed ? null : () {
setState(() {
// call set state here so that the UI will be updated.
onTapPressed = true;
});
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
},
And then in your widget add this member.
bool onTapPressed = false;
Also ListTile also has an optional parameter called enabled, which you could set to false instead of passing null to onTap. This approach will disable all handlers on the ListTile, not just the onTap (you might also have an onLongPress handler for example). And it will also update the styling to use the disabled colors from the current Theme.
disabled: !onTapPressed,
onTap: () {
setState(() {
// call set state here so that the UI will be updated.
onTapPressed = true;
});
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
},
Please refer to below code
IgnorePointer is a built-in widget in flutter which is similar to the AbsorbPointer widget, they both prevent their children’s widget from pointer-events which are taping, clicking, dragging, scrolling, and hover.IgnorePointer widget just ignores the pointer-events without terminating it, which means if there is any other element below the IgnorePointer widget tree then it will be able to experience that pointer-event.
bool disableOnClick = false;
IgnorePointer(
ignoring: disableOnClick ?? false,
child: ListTile(
tileColor: Color(0xff99c2ec),
title: Row(
children: [
Expanded(
child: Text(document['name'],
style: TextStyle(
color: Colors.black87,
fontSize: 20,
)),
),
Container(
decoration: const BoxDecoration(
color: Color(0xffecc399),
),
padding: const EdgeInsets.all(10.0),
child: Text(
document['votes'].toString(),
style: Theme.of(context).textTheme.headline4,
),
),
],
),
onTap: () {
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
disableOnClick = true;
setState(() {});
},
),
)
you can make condition like :-
set one bool variable and set it true and when user tap on button set it false if you want to permanently disable use prefrences
bool isClicked = true;
GestureDetector(
onTap: (){
if(isClicked){
isClicked = true;
enter code here
}
}
child: Container(),
)
I'm trying to get the "Child_Name" and "Parent_Name" from firebase rtdb and create a list of the names using ListView.builder. I have done this before in another part of the app and it works perfectly. I am trying to apply the same logic again but I am getting an error.
Error is occurs inside the setState where the line childrenList = Map.from(value) is.
View of my firebase rtdb
is here (image)
Error:
- [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: NoSuchMethodError: The method 'forEach' was called on null.
- Tried calling: forEach(Closure: (dynamic, dynamic) => void)
Code(1):
Future<List> getListOfChildren() async {
print("Getting Children");
databaseReference
.child("users")
.child("Absent_Children")
.child(formattedDate)
.onValue
.listen(
(event) {
setState(
() {
var value = event.snapshot.value;
childrenList = Map.from(value)
.values
.map((e) => Children.fromJson(Map.from(e)))
.toList();
},
);
},
);
return childrenList;
}
Code(2): Class for the data
class Children {
final String childName;
final String parentName;
Children({
this.childName,
this.parentName,
});
static Children fromJson(Map<dynamic, dynamic> json) {
return Children(
childName: json["Child_Name"],
parentName: json["Parent_Name"],
);
}
}
Code(4): formattedDate
getTodaysDate() {
setState(
() {
DateTime now = DateTime.now();
var date = DateFormat("dd-mm-yyyy");
formattedDate = date.format(now).toString();
},
);
}
Code(3): My ListView.builder
body: childrenList.isEmpty
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: childrenList.length,
itemBuilder: (context, int index) {
final Children child = childrenList[index];
final String childName = child.childName;
final String parentName = child.parentName;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0,
child: ExpansionTile(
title: Text(
childName.toUpperCase(),
style: GoogleFonts.lexendMega(),
textAlign: TextAlign.center,
),
children: [
Column(
children: [
Text(
parentName,
textAlign: TextAlign.center,
style: GoogleFonts.lexendMega(fontSize: 13),
),
],
)
],
),
),
);
},
),
Thank you.
As I said here too, it looks like there's no data as databaseReference.child("users").child("Absent_Children").child(formattedDate) and your code doesn't handle that situation.
If the absence of data is a normal occurrence, you should check if the snapshot has a value before trying to access its value:
databaseReference
.child("users")
.child("Absent_Children")
.child(formattedDate)
.onValue
.listen(
(event && event.snapshot.exists) { // 👈 add exists check here
setState(
() {
var value = event.snapshot.value;
childrenList = Map.from(value)
.values
.map((e) => Children.fromJson(Map.from(e)))
.toList();
},
);
I'm working on a project with Firebase (Realtime database). In this project I will have a main screen with will have several buttons according to the user. The Buttons info are going to be stored inside the realtime database. This is basically a Home Automation project.
This is how my db looks:
The quantity, means how many buttons does that user have. button1 and button2 have the button characteristics. So what I'm attempting to do is.
When the user logs in. I have a Streambuilder that will check if the quantity has data. If I has if will run inside a For loop which will create the buttons in the user screen.
I having problem getting the specific values from the database, for example, getting the quantity and storing into a variable in the main screen.
This is how I'm attempting to get the quantity (I will use this code for getting other values too, later on) but it isn't working:
Future<int> receive_quantity() async{
final FirebaseUser user = await _auth.currentUser();
var snapshot = databaseReference.child(user.uid+"/buttons"+"/quantity").once();
var result;
await snapshot.then((value) => result = value);
print(result);
return result;
}
Error that I get:
_TypeError (type 'DataSnapshot' is not a subtype of type 'FutureOr<int>')
My StreamBuilder:
body: StreamBuilder(
stream: _auth.getButtonQuantity(),
initialData: 0,
builder: (context, snapshot) {
if (snapshot.hasError || snapshot.hasError){
return Container(color: Colors.red);
}
if (!snapshot.hasData || !snapshot.hasData){
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasData || snapshot.hasData){
return GridView.count(
padding: EdgeInsets.all(15),
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
crossAxisCount: 3,
children: [
for (int i = 0; i < buttonquant; i++){
Button(),
},
GestureDetector(
onTap: () async{
_auth.receive_quantity();
},
child: Container(
color: Colors.black,
width: 150,
height: 150,
child: Icon(Icons.add, color: Colors.white,),
),
),
],
);
}
}
),
My Button:
class Button extends StatelessWidget {
const Button({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
},
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(15)
),
child: Stack(
children: [
Positioned(
top: 10,
left: 10,
child: Icon(
Icons.lightbulb,
size: 35,
color: Colors.white,
),
),
Positioned(
top: 95,
left: 15,
child: Text("Televisao", style: TextStyle(color: Colors.white),),
),
],
),
),
);
}
}```
What you need to do is you need to get the value of the snapshot not using it directly:
Future<int> receive_quantity() async{
final FirebaseUser user = await _auth.currentUser();
var snapshot = await databaseReference.child(user.uid+"/buttons"+"/quantity").once();
var result = snapshot.value; //get the value here
print(result);
return result;
}
This is how you get the value in general:
databaseReference.once().then((DataSnapshot snapshot) {
print('Data : ${snapshot.value}');
});
I am making a chat app that displays both a Group Chat and Private Chat in the same List.
I use Firestore as the database and store the data of User, Group and Contact in there. I have a Message Screen that displays a list of Chats that the User has using StreamBuilder.
I want to display data differently depending on the group's data. The group chat has their Group picture, Private Chat with User in Contact, their avatar display, and Private Chat with a generic icon display with User not in Contact.
I iterate through the stream first in a DatabaseService class, then put it in a variable and set it as a stream for StreamBuilder. This works fine, but I also want a list to check if a user already has a private chat with another User without getting the data from Firestore.
API.dart
//this is where I put my code to connect and read/write data from Firestore
final FirebaseFirestore _db = FirebaseFirestore.instance;
Api();
....
Stream<QuerySnapshot> streamCollectionByArrayAny(
String path, String field, dynamic condition) {
return _db
.collection(path)
.where(field, arrayContainsAny: condition)
.snapshots();
}
DatabaseService.dart
...
List<GroupModel> groups; //List of Groups
Stream<List<GroupModel>> groupStream; //Stream of List Group
...
Stream<QuerySnapshot> fetchGroupsByMemberArrayAsStream(
String field, dynamic condition) {
return _api.streamCollectionByArrayAny('groups', field, condition);
}
//function to get Contact Detail using List of Group User
Future<ContactModel> getContactDetail(List<dynamic> members) async {
//remove current user id from the list
members.removeWhere((element) => element.userId == user.userId);
//getContactbyId return a ContactModel object from Firestore
ContactModel contactModel =
await getContactById(user.userId, members.first.userId);
if (contactModel != null && contactModel.userId.isNotEmpty) {
return contactModel;
} else {
return new ContactModel(
userId: members.first.userId, nickname: "", photoUrl: "");
}
}
Future<GroupModel> generateGroupMessage(GroupModel group) async {
//check if Group Chat or Private chat
if (group.type == 1) {
ContactModel contactModel = await getContactDetail(group.membersList);
group.groupName = contactModel.nickname.isNotEmpty
? contactModel.nickname
: contactModel.userId;
group.groupPhoto = contactModel.photoUrl;
}
print("Add");
//add the group data into List<GroupModel> groups
groups.add(group);
return group;
}
void refreshMessageList() {
groups = [];
print("refresh");
//get Group Data as Stream from FireStore base on the user data in the Member Array of Group then map it to Stream while also change data base on Group type in generateGroupMessage
groupStream = fetchGroupsByMemberArrayAsStream('membersList', [
{"isActive": true, "role": 1, "userId": user.userId},
{"isActive": true, "role": 2, "userId": user.userId}
]).asyncMap((docs) => Future.wait([
for (GroupModel group in docs.docs
.map((doc) => GroupModel.fromMap(doc.data()))
.toList())
generateGroupMessage(group)
]));
}
Message.dart
#override
void initState() {
super.initState();
...
databaseService.refreshMessageList();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(horizontal: 16),
margin: EdgeInsets.only(top: 24),
child: Column(
children: [
...
Flexible(
child: StreamBuilder(
stream: databaseService.groupStream,
builder: (context, AsyncSnapshot<List<GroupModel>> snapshot) {
if (!snapshot.hasData) {
print("No data");
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
),
);
} else {
print("Has data");
groups = List.from(snapshot.data);
groups.removeWhere(
(element) => element.recentMessageContent.isEmpty);
groups.sort((group1, group2) {
if (DateTime.parse(group1.recentMessageTime)
.isAfter(DateTime.parse(group2.recentMessageTime))) {
return -1;
} else {
return 1;
}
});
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
buildItem(context, groups[index]),
itemCount: groups.length,
),
),
),
}
],)));
}
Widget buildItem(BuildContext context, GroupModel group) {
if (group.recentMessageContent == '') {
return Container();
} else {
return Column(
children: [
Container(
child: InkWell(
child: Row(
children: <Widget>[
Material(
child: group.groupPhoto.isNotEmpty
? CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
strokeWidth: 1.0,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.grey),
),
width: 60.0,
height: 60.0,
padding: EdgeInsets.all(10.0),
),
imageUrl: group.groupPhoto,
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
)
: Icon(
group.type == 1
? Icons.account_circle
: Icons.group,
size: 60.0,
color: Colors.grey,
),
borderRadius: BorderRadius.all(Radius.circular(30.0)),
clipBehavior: Clip.hardEdge,
),
SizedBox(
width: 150,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
group.groupName,
style: TextStyle(
color: colorBlack,
fontSize: 12,
fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
Text(
group.recentMessageContent,
style: TextStyle(
color: Colors.grey,
fontSize: 10,
height: 1.6),
overflow: TextOverflow.ellipsis,
),
],
),
margin: EdgeInsets.only(left: 12.0),
),
),
Spacer(),
Text(
formatDateTime(group.recentMessageTime),
style: TextStyle(color: Colors.grey, fontSize: 10),
),
],
),
onTap: () {
switch (group.type) {
case 1:
Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(
settings:
RouteSettings(name: "/message/chatPage"),
builder: (context) => ChatPage(group: group)))
.then((value) => setState);
break;
case 2:
Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(
settings:
RouteSettings(name: "/message/chatGroup"),
builder: (context) =>
ChatGroupPage(group: group)))
.then((value) => {setState(() {})});
break;
}
}),
),
Divider(
color: Colors.grey,
),
],
);
}
}
The ChatPage and ChatGroupPage navigate to Private Chat and Group Chat respectively, and in there the User can add the chat partner or group member into Contact.
When adding is done I call the databaseService.refreshMessageList to refresh the Stream of List Group, so when I navigate back to the Message Screen, it will refresh and display accordingly. However, the List<GroupModel> groups becomes blank and will not add data until I navigate back to the Message Screen.
I debugged the app and found that the List became blank because it executes groups = [] but did not run the .asyncMap until I hot reload or navigate Message Screen and put the setState in .then to refresh the data.
I need the List groups to check whether the 2 users already have a private chat to create a new one when adding to Contact. I have already tried putting setState after databaseService.refreshMessageList, but it still did not work.
Can anyone please help me and provide a solution? I know this is not a good question to ask, but I have been stuck with this for almost a week now and desperately need an answer. Thank you in advance.
EDIT
Here is my data structure:
Users
/users (collection)
/userId
/user (document)
- userId
- nickname
- photoUrl
- token
- /contacts (subcollection)
/contactId
/contact (document)
- userId
- nickname
- photoUrl
Groups:
/groups (collection)
/groupId
/group (document)
- groupId
- groupName
- type
- membersList (List<Map<String, dynamic>>)
- member: userId, isActive, role
- recentMessageContent
- recentMessageTime
- recentMessageType
Messages:
/messages (collection)
/groupId
/groupMessage (document)
/messages (subcollection)
/messageId
/message (document)
- messageContent
- messageTime
- messageType
You can use array membership, for example, the array-contains method can query for elements within an array without performing any manipulation. There is an interesting article that provides some examples you might interest you.
Another alternative could be to iterate both arrays until matching the values you need. However, iteration can lead to performance issues if you do not implement it correctly.