I'm implementing a search functionality in the app. I'm using firebase where clause to filter the data
the filtered data appears for a second and it then disappeared immediately
here is my code
String searchName = "";
TextFormField(
onChanged: (value){
setState((){
searchName = value;
});
},
decoration: InputDecoration(
hintText: 'SEARCH',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide.none
)
),
),
StreamBuilder(
stream: searchName != "" ? FirebaseFirestore.instance.collectionGroup('user_offers').where("fieldName", isEqualTo: searchName).snapshots() : FirebaseFirestore.instance.collectionGroup('user_offers').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if(snapshot.hasData){
return ListView.builder() // returning a list
}
return const Center(child: CircularProgressIndicator(color: Colors.white));
},
),
What am I doing wrong? The data appears for a second and it then disappears instantly
You should have to use two conditions 1st for the state (e.g. ConnectionState, waiting, done) and 2nd for null check. whenever you use FIREBASE WHERE you should always use null check. (e.g. if statement and then must else).
It's true snapshot parament have data. But snapshot.data!.docs have with null. You must check it (snapshot.data!.docs) with if else statement.
disappears instantly because you haven't checked snapshot.data!.docs with if else statement either empty or not empty.
I hope it could help.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:towermarket/models/order.dart';
import '../order_details_screen.dart';
class InProgressOrders extends StatelessWidget {
const InProgressOrders({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection("orders")
.where("completed", isEqualTo: false)
.snapshots(),
builder:
(_, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return const Center(
child: CircularProgressIndicator(),
);
case ConnectionState.active:
case ConnectionState.done:
final List<Order> orders =
snapshot.data!.docs.map((e) => Order.fromSnapshot(e)).toList();
if (orders.isNotEmpty) {
return ListView.builder(
itemCount: orders.length,
itemBuilder: (_, index) {
return ListTile(
onTap: () {
Navigator.push(context, OrderDetailsScreen.route());
},
leading: Text("${index + 1}"),
title: Text(orders[index].reference!),
subtitle: Text(orders[index].address),
trailing: Text("PKR ${orders[index].total}"),
);
},
);
} else {
return const Center(
child: Text("No, In Progress Order"),
);
}
default:
return const Center(
child: Text("Somethign went wrong"),
);
}
},
);
}
}
Related
I want to keep the selected value visible after the users click the connect button. How do I keep the selected value in this dropdown still appear when the user has clicked the connect button? This is the preview of my application:
And this is my code:
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('servers')
.snapshots(includeMetadataChanges: true),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return Container(
child: DropdownSearch<String>(
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
labelText: "Server",
labelStyle: TextStyle(color: Color(0xFFB4F4C8)),
hintText: "Select a server",
hintStyle: TextStyle(color: Color(0xFFB4F4C8)),
icon: Icon(
Icons.map_outlined,
color: Color(0xFFB4F4C8),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xFFB4F4C8)),
borderRadius: BorderRadius.circular(10.0),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color(0xFFB4F4C8)),
borderRadius: BorderRadius.circular(8.0)),
),
),
dropdownBuilder: ((context, selectedItem) {
Icon(Icons.arrow_drop_down_circle_outlined,
color: Color(0xFFB4F4C8),
);
return Text(selectedItem ?? "",
style: TextStyle(color: Color(0xFFfcfcfc)),
);
}),
items: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
return data["address"];
})
.toList()
.cast<String>(),
onChanged: (var data) {
dataAddress = data;
}
),
);
},
),
Thank you in advance for any help.
It looks like you are not using showSearchBox: true on DropdownSearch widget.
Although docs shows that we can use dropdownDecoratorProps: DropDownDecoratorProps but in your case it seems it is not needed as there aren't many dropdown search fields.
Hence I have used it without those Props in my below code.
I have tried below code at my end i am able to list drop down. Here is the screenshot of my app :
main.dart with DropDownWidget :
class DropDownWidget extends StatefulWidget {
const DropDownWidget({Key? key}) : super(key: key);
#override
State<DropDownWidget> createState() => _DropDownWidgetState();
}
class _DropDownWidgetState extends State<DropDownWidget> {
String? dataAddress;
final snappy = FirebaseFirestore.instance
.collection('users')
.snapshots(includeMetadataChanges: true);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AppBar Demo')),
body: StreamBuilder<QuerySnapshot>(
stream: snappy,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
return DropdownSearch<String>(
dropdownSearchDecoration: const InputDecoration(
labelText: "Server",
hintText: "Select a server",
),
dropdownBuilder: ((context, selectedItem) {
const Icon(
Icons.arrow_drop_down_circle_outlined,
);
return Text(selectedItem ?? "");
}),
items: snapshot.data!.docs
.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return data["address"];
})
.toList()
.cast<String>(),
onChanged: (var data) {
dataAddress = data;
});
},
),
);
}
}
How to fix this,
error says,
════════ Exception caught by widgets library ═══════════════════════════════════
The following StateError was thrown building:
Bad state: field does not exist within the DocumentSnapshotPlatform
Another problem is when the user searches for something, the app should show the username without considering capital, simple words
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:insta/utils/colors.dart';
class SearchScreen extends StatefulWidget {
const SearchScreen({super.key});
#override
State<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final TextEditingController searchCon = TextEditingController();
bool isShowUsers = false;
#override
void dispose() {
searchCon.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: mobileBackgroundColor,
title: TextFormField(
controller: searchCon,
decoration: const InputDecoration(
labelText: "Search for a user",
),
onChanged: (value) {
setState(() {
isShowUsers = true;
});
},
// onFieldSubmitted: (String _) {
// setState(() {
// isShowUsers = true;
// });
// },
),
),
body: isShowUsers
? FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.where(
'username',
isGreaterThanOrEqualTo: searchCon.text,
)
.get(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: (snapshot.data! as dynamic).docs.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
(snapshot.data! as dynamic).docs[index]
['photoUrl']),
),
title: Text(
(snapshot.data! as dynamic).docs[index]['username']),
);
},
);
},
)
: FutureBuilder(
future: FirebaseFirestore.instance.collection('posts').get(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: const CircularProgressIndicator());
}
return StaggeredGridView.countBuilder(
crossAxisCount: 3,
itemCount: (snapshot.data! as dynamic).docs.length,
itemBuilder: (context, index) => Image.network(
(snapshot.data! as dynamic).docs[index]['postUrl'],
fit: BoxFit.cover,
),
staggeredTileBuilder: (index) => StaggeredTile.count(
(index % 7 == 0) ? 2 : 1, (index % 7 == 0) ? 2 : 1),
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
);
},
),
);
}
}
`
Got this error while working with firebase its due to when you are trying to access the document doesn't exist or check the spelling, syntax when you access them
The code below is what I am trying now. The page works does everything I need but now I need this database reference to use the loanuid, clientuid, and companyName to get to the right directory.
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('prosperitybank')
.doc('OHViYK8Zz6XfKGJsSXRL')
.collection('Project Information')
.snapshots()```
I need it from my collection.(userCreationRef).doc(loggedinuid) as shown in the picture. I can not figure out how to do this without the stream builders interfering any help would be greatly appreciated. I have tried to using this to help but it did not How can you nest StreamBuilders in Flutter?. I also tried looking at the documentation here https://firebase.flutter.dev/docs/firestore/usage/.
Picture of Document I need data fields from
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:photoloanupdated/screens/mains/viewproperties.dart';
import 'package:provider/provider.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final user = auth.currentUser;
final uid = user?.uid;
var users = FirebaseFirestore.instance.collection('userCreationRequests');
var companyname = "";
return Scaffold(
appBar: AppBar(
title: Text(companyname),
),
body:
FutureBuilder<DocumentSnapshot>(
future: users.doc(uid).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['companyName']} ${data['last_name']}");
}
return Text("loading");
},
);
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('prosperitybank')
.doc('OHViYK8Zz6XfKGJsSXRL')
.collection('Project Information')
.snapshots(), //key spot fV or email fix
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, int index) {
QueryDocumentSnapshot<Object?>? documentSnapshot =
snapshot.data?.docs[index];
//for date/time DateTime mydateTime = documentSnapshot['created'].toDate();
return InkWell(
onTap: () {
Navigator.of(context)
.push(
MaterialPageRoute(
builder: (context) => ViewProperties(documentSnapshot,
snapshot.data?.docs[index].reference)),
)
.then((value) {
setState(() {});
});
},
child: Card(
child: Container(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${documentSnapshot!['address']}",
style: TextStyle(
fontSize: 24.0,
fontFamily: "lato",
fontWeight: FontWeight.bold,
color: Colors.black),
),
Container(
alignment: Alignment.centerRight,
child: Text(
"${documentSnapshot!['projectcomplete'].toString() + "% Complete"}",
// for mydateTime.toString(),
style: TextStyle(
fontSize: 17.0,
fontFamily: "lato",
color: Colors.black87),
),
)
],
),
),
),
),
);
},
);
} else {
return Center(
child: Text("Loading..."),
);
}
},
),
);
}
}
String uuid;
Future<List<Map<String, dynamic>>> _onQuery() {
Future<List<Map<String, dynamic>>> res;
if (uuid != null) {
res = future.get().then((v) => v.docs
.map((e) => e.data())
.where((e) =>
e['uuid'].toLowerCase().contains(uuid))
.toList());
} else {
res = future.get().then((v) => v.docs.map((e) => e.data()).toList());
}
setState(() {});
return res;
}
now you can use _onQuery as stream.
This example from the cloud_firestore documentation uses a StreamBuilder and the ConnectionState of an AsyncSnapshot to handle the stream in its different states. Is there a similar way to manage the ConnectionState when accessing the stream via a StreamProvider instead of a StreamBuilder? What is the best way of avoiding it to return null in the short while until it actually has documents from Firestore?
Here the example from the cloud_firestore docs with the StreamBuilder:
class BookList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['title']),
subtitle: new Text(document['author']),
);
}).toList(),
);
}
},
);
}
}
I have a rather basic stream:
List<AuditMark> _auditMarksFromSnapshot(QuerySnapshot qs) {
return qs.documents.map((DocumentSnapshot ds) {
return AuditMark.fromSnapshot(ds);
}).toList();
}
Stream<List<AuditMark>> get auditMarks {
return Firestore.instance
.collection('_auditMarks')
.snapshots()
.map(_auditMarksFromSnapshot);
}
This is accessed via a StreamProvider (have omitted other providers here):
void main() async {
runApp(MultiProvider(
providers: [
StreamProvider<List<AuditMark>>(
create: (_) => DatabaseService().auditMarks, ),
],
child: MyApp(),
));
}
I have tried somehow converting the QuerySnapshot to an AsyncSnapshot<QuerySnapshot> but probably got that wrong.
Could of course give the StreamProvider some initialData like so - but this is cumbersome, error prone and probably expensive:
initialData: <AuditMark>[
AuditMark.fromSnapshot(await Firestore.instance
.collection('_auditMarks')
.orderBy('value')
.getDocuments()
.then((value) => value.documents.first))
...but I am hoping there is a smarter way of managing the connection state and avoiding it to return null before it can emit documents?
I have been dealing with this and didn't want to declare an initialData to bypass this issue.
What I did was creating a StreamBuilder as the child of StreamProvider.
So that I could use the snapshot.connectionState property of StreamBuilder in the StreamProvider.
Here's the code:
return StreamProvider<List<AuditMark>>.value(
value: DatabaseService().auditMarks,
child: StreamBuilder<List<AuditMark>>(
stream: DatabaseService().auditMarks,
builder: (context, snapshot) {
if (!snapshot.hasError) {
switch (snapshot.connectionState) {
case ConnectionState.none: // if no connection
return new Text(
"Offline!",
style: TextStyle(fontSize: 24, color: Colors.red),
textAlign: TextAlign.center,
);
case ConnectionState.waiting
// while waiting the data, this is where you'll avoid NULL
return Center(child: CircularProgressIndicator());
default:
return ListView.builder(
// in my case I was getting NULL for itemCount
itemCount: logs.length,
itemBuilder: (context, index) {
return LogsTile(log: logs[index]);
},
);
}
}
else {
return new Text(
"Error: ${snapshot.error}",
style: TextStyle(fontSize: 17, color: Colors.red),
textAlign: TextAlign.center,
);
}
}
)
);
Probably not the most elegant solution, but I ended up using a simple bool variable which is true while not all StreamProviders have emitted values.
bool _waitForStreams = false;
if (Provider.of<List<AuditMark>>(context) == null) _waitForStreams = true;
if (Provider.of<...>>(context) == null) _waitForStreams = true;
(etc. repeat for every StreamProvider)
// show "loading..." message while not all StreamProviders have supplied values
if (_waitForStreams) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 25.0),
Text('loading...'),
],
)
],
),
);
}
I don't know if it's correct but this is how I implement it.
Since streamProviser does not provide a connection state, I first use streamBuilder and then provider.value to distribute the data:
return StreamBuilder<BusinessM>(
stream: db.businessDetails(), //firebase stream mapped to business model class
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active)
return Provider<BusinessM>.value(
value: snapshot.data,
child: Businesspage(),
);
else
return Center(child: CircularProgressIndicator());
});
For someone who want to use StreamProvider but end up with no ConnectionState state to use. For some of the cases, null represent the state of "waiting for the first data", not "no data".
In StreamProvider, there is no build-in method to detect the state. But we can still warp the state outside of the data:
StreamProvider<AsyncSnapshot<QuerySnapshot?>>.value(
initialData: const AsyncSnapshot.waiting(),
value: FirebaseFirestore.instance
.collection('books')
.snapshots()
.map((snapshot) {
return AsyncSnapshot.withData(ConnectionState.active, snapshot);
}),
child: ...,
);
or for firebaseAuth:
StreamProvider<AsyncSnapshot<User?>>.value(
initialData: const AsyncSnapshot.waiting(),
value: FirebaseAuth.instance.userChanges().map(
(user) => AsyncSnapshot.withData(ConnectionState.active, user),
),
),
I have a page which displays 2 elements, both of them are different StreamBuilder but the second one depends on the first one.
To make it more clear I display this:
Firebase documents (list)
Firebase user
If we sign out both StreamBuilder disappear. That's fine, but my problem comes when I need to select a document from the list:
return ListTile(
leading: FlutterLogo(size: 40.0),
title: Text(set["title"]),
selected: _selected[index],
trailing: Badge(
badgeColor: Colors.grey,
shape: BadgeShape.circle,
toAnimate: true,
onTap: () => setState(() => _selected[index] = !_selected[index]),
);
Everytime I do the SetState() I refresh the first StreamBuilder (not sure why) and with this the second one.
This is the list widget:
Widget _mySetsLists(BuildContext context) {
List<bool> _selected = List.generate(20, (i) => false);
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
FirebaseUser user = snapshot.data;
if (snapshot.hasData) {
return StreamBuilder(
stream: Firestore.instance
.collection('users')
.document(user.uid)
.collection('sets')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot set = snapshot.data.documents[index];
return ListTile(
leading: FlutterLogo(size: 40.0),
title: Text(set["title"]),
selected: _selected[index],
onTap: () => setState(() => _selected[index] = !_selected[index]),
);
},
);
} else {
return Center(
child: new CircularProgressIndicator(),
);
}
},
);
} else {
return Text("loadin");
}
},
);
}
}
And this is the user profile:
class UserProfileState extends State<UserProfile> {
#override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildListDelegate(
[
_mySetsLists(context),
Divider(),
StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user == null) {
return Text('not logged in');
}
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
user.photoUrl,
),
),
title: Text(user.displayName),
subtitle: Text(user.email),
trailing: new IconButton(
icon: new Icon(Icons.exit_to_app),
highlightColor: Colors.pink,
onPressed: () {
authService.signOut();
}),
);
} else {
return Text("loading profile"); // <---- THIS IS WHAT I SEE
}
},
),
],
),
);
}
I also went through the same difficulty, but this is the trick i used
var itemsData = List<dynamic>();
var _documents = List<DocumentSnapshot>();
#override
void initState() {
// TODO: implement initState
super.initState();
getData();
}
getData(){
Firestore.instance
.collection('users')
.document(currentUser.uid)
.collection('set')
.getDocuments()
.then((value) {
value.documents.forEach((result) {
setState(() {
_documents.add(result);
itemsData.add(result.data);
});
});
});
}
replace your listview builder will be like this
ListView.builder(
shrinkWrap: true,
itemCount: _documents.length,
itemBuilder: (context, index) {
return ListTile(
title:Text(itemsData[index]['name'])
)
})
Hope it helps!!
If you pretend to use setstat a lot using the stream you can download the data locally. So every reload will not download data again, but just show the local data.
First step: declare the variable that will store data locally.
QuerySnapshot? querySnapshotGlobal;
Then where you read the streamData, first check if the local data you just declared is empty:
//check if its empty
if(querySnapshotGlobal==null)
//as its empty, we will download it from firestore
StreamBuilder<QuerySnapshot>(
stream: _queryAlunos.snapshots(),
builder: (context, stream){
if (stream.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
else if (stream.hasError) {
return Center(child: Text(stream.error.toString()));
}
else if(stream.connectionState == ConnectionState.active){
//QuerySnapshot? querySnapshot = stream.data;
//instead of save data here, lets save it in the variable we declared
querySnapshotGlobal = stream.data;
return querySnapshotGlobal!.size == 0
? Center(child: Text('Sem alunos nesta turma'),)
: Expanded(
child: ListView.builder(
itemCount: querySnapshotGlobal!.size,
itemBuilder: (context, index){
Map<String, dynamic> map = querySnapshotGlobal!.docs[index].data();
//let it build
return _listDeAlunoswid(map, querySnapshotGlobal!.docs[index].id);
},
),
);
}
return CircularProgressIndicator();
},
)
else
//now, if you call setstate, as the variable with the data is not empty, will call it from here e instead of download it again from firestore, will load the local data
Expanded(
child: ListView.builder(
itemCount: querySnapshotGlobal!.size,
itemBuilder: (context, index){
Map<String, dynamic> map = querySnapshotGlobal!.docs[index].data();
return _listDeAlunoswid(map, querySnapshotGlobal!.docs[index].id);
},
),
),
Hope it helps you save some money!