I use firestore and streambuilder to read data in a list, when i run the application for the first time i get a message "Unexpected null value" and I realized that "snapshot.hasData" is always false and snapshot.ConnectionState.waiting is always true. But when i restart application with hot reload i can retrieve data.
This is my stream:
Stream<QuerySnapshot> _branchStream = FirebaseFirestore.instance.collection('Companies').doc(company_id).collection("Branch Offices").snapshots();
This is my StreamBuilder
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
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 ListView(
children: snapshot.data!.docs
.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Padding(
padding: const EdgeInsets.all(22.0),
child: Card(
elevation: 8,
shadowColor: Colors.blueGrey,
shape: cardShape,
child: Row(
children: [
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(22.0),
child: CircleAvatar(
radius: 50,
backgroundImage:
NetworkImage(data['branch_image'],scale: 60),
),
)),
Expanded(
flex: 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(22.0),
child: Text(data['branch_name'], style: textBlackTitle, textAlign: TextAlign.center,),
),
Padding(
padding: const EdgeInsets.all(22.0),
child: Text("UbicaciĆ³n: "+data['branch_address'], style: textGraySubTitle, textAlign: TextAlign.center,),
),
],
)),
Expanded(
flex: 2,
child: IconButton(
// focusColor: Color(color1),
// color: Color(color1),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => Home(branch_id : data['branch_id'], company_id : company_id, branch_name : data['branch_name'], branch_image : data['branch_image'])));
}, icon: Image.asset("assets/enter.png", fit: BoxFit.contain, height: 100,)))
],
),
),
);
})
.toList()
.cast(),
);
},
)
This is data that I want to get
This is what I get at the first time
This is what I get after hot reload (That I should have from the beginning).
Because your data is null at the beginning, it takes some time to load the data.
You actually already included a check, but commented it out again. Undo it and try again.
/* if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}*/
It takes some time to load snapshot data. For better UX return specific widgets for each state of the snapshot.
Make sure you're using StreamBuilder inside StatefulWidget.
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return //error widget
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
return //some widget
case ConnectionState.waiting:
return CircularProgressIndicator(),
case ConnectionState.active:
return ListView()
case ConnectionState.done:
return //some widget
}
}
);
Related
I have this code as am trying to code something to update data in firestore.
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(mid.toString()),
),
body: FutureBuilder<Member?>(
future: readMember(mid),
builder: (context, snapshot) {
if (snapshot.hasData) {
final member = snapshot.data;
/// return a form
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
if snapshot hasData I want to return a form like this
Card(
child: Row(
children: <Widget>[
TextField(
controller: controllerName,
decoration: decoration('name'),
),
const SizedBox(height: 24),
TextField(
controller: controllerAge,
decoration: decoration('Age'),
keyboardType: TextInputType.number,
),
const SizedBox(height: 24),
ElevatedButton(
child: const Text('Create'),
onPressed: () {},
),
],
));
All my attempt yield no success please I need help.
Check others state like error or if the data is null or not
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("Got Error");
}
if (snapshot.data == null) {
return Text("No data");
}
if (snapshot.hasData) {
final member = snapshot.data;
return Card( ///here form
child: Row(
children: <Widget>[],
));
} else {
return const Center(child: CircularProgressIndicator());
}
},
And provide width on TextFiled to fix overflow, TextFiled and row are trying to get infinite with.
just wrap with Expanded
Expanded(child: TextField(...)),
You can find more about unbound height& width
I am using Flutter to develop small application with floor for the database.
I am getting the data from the database using Future then listing all items in UI using FutureBuild.
This is my code
Getting the data from database:
#Query('SELECT * FROM Doctor')
Future<List<Doctor>> findAllDoctor();
Getting data to UI
Future<List<Doctor>> findAllDoctor() async {
return await database.doctorDao.findAllDoctor();
}
Setting data into FutureBuilder:
return FutureBuilder(
future: findAllDoctor(),
builder: (BuildContext context, AsyncSnapshot<List<Doctor>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
contentPadding: const EdgeInsets.all(8.0),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${snapshot.data![index].firstName} ${snapshot
.data![index].lastName}"),
Text(
snapshot.data![index].phone,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
subtitle: Text(
"${snapshot.data![index].address} ${snapshot.data![index]
.nameOfTheClinic}"),
),
);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
I want to add new widget that tells me no data if there is no data in the table.
I'm new using firestore, so im still trying to understand it.
i had Closets on the inside i had Clothes. i want to retrieve Clothes data and show it with listview.
the problem is i failed to retrieve the data and show it into the app
this is my code for the streambuilder
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection("clothes").snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Failed to load data!");
}
if (snapshot.connectionState ==
ConnectionState.waiting) {
return ActivityServices.loadings();
}
return new ListView(
children: snapshot.data.docs
.map((DocumentSnapshot doc) {
Clothes clothes;
clothes = new Clothes(
doc.data()['clothesId'],
doc.data()['clothesName'],
doc.data()['clothesDesc'],
doc.data()['clothesImage'],
doc.data()['clothesCloset'],
doc.data()['clothesAge'],
doc.data()['clothesTag'],
doc.data()['clothesStatus'],
doc.data()['clothesLaundry'],
doc.data()['createdAt'],
doc.data()['updatedAt'],
);
print(doc.data()['clothesName']);
return CardClothesLemari(clothes: clothes);
}).toList(),
);
},
),
and this is my CardClothesLemari
final Clothes clothes;
CardClothesLemari({this.clothes, this.doc});
#override
_CardClothesLemariState createState() => _CardClothesLemariState();
}
class _CardClothesLemariState extends State<CardClothesLemari> {
#override
Widget build(BuildContext context) {
Clothes cloth = widget.clothes;
final Size size = MediaQuery.of(context).size;
if (clothes == null) {
return Container();
} else {
return Padding(
padding:
EdgeInsets.only(top: 5.0, bottom: 5.0, left: 5.0, right: 5.0),
child: InkWell(
onTap: () {
Navigator.pushNamed(context, DetailClothes.routeName,
arguments: cloth);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14.0),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 3.0,
blurRadius: 5.0)
],
color: Color(0xffA77665),
),
child: Column(children: [
Padding(
padding: EdgeInsets.only(top: size.height * 0.04),
),
Hero(
tag: 'assets/images/dummy.jpg',
child: CircleAvatar(
radius: 55,
backgroundImage: AssetImage("assets/images/dummy.jpg"),
),
),
SizedBox(height: 7.0),
Text(
//show clothes name
cloth.clothes,
style: TextStyle(
fontSize: 14,
fontFamily: GoogleFonts.openSans().fontFamily,
fontWeight: FontWeight.w700,
color: Color(0xffF0E8E1)),
textAlign: TextAlign.center,
),
Padding(
padding: EdgeInsets.only(top: 8),
child:
Container(color: Color(0xFFEBEBEB), height: 2.9657),
),
]))));
}
}
}
this is the screenshot of my firestore
Add listview inside the ConnectionState.done like below code.
if (snapshot.connectionState == ConnectionState.done) {
return new ListView(
children: snapshot.data.docs
.map((DocumentSnapshot doc) {
Clothes clothes;
clothes = new Clothes(
doc.data()['clothesId'],
doc.data()['clothesName'],
doc.data()['clothesDesc'],..........<Rest of the code>......
}
As per your database structure you're entering wrong query. Kindly take a look below code
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('closet')
.doc('your_document_id')
.collection('clothes')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading...');
} else {
return ListView.builder(
itemCount: snapshot.data.docs.length,
shrinkWrap: true,
itemBuilder: (context, int index) {
QueryDocumentSnapshot<Map<String, dynamic>> data = snapshot.data.docs[index];
return Text(data.data()['clothesName']);
},
);
}
});
There is a list that displays data from the database that comes to it at the time the data is fetched and until the data appears, the CircularProgressIndicator () appears so that the user knows that there is a process happening in the background.
Excellent but there is a problem with this CircularProgressIndicator () continues to work non-stop if there is no data in the database. Herein lies the problem.
It is supposed to work for a specified time and if there is no data in the database it will stop working and disappear.
Is there a way to do this? So that if there is no data that can be fetched it stops working?
My code:
class MainListView extends StatefulWidget {
MainListViewState createState() => MainListViewState();
}
class MainListViewState extends State {
final String apiURL = 'http://====================/getStudentInfo.php';
Future<List<Studentdata>> fetchStudents() async {
var response = await http.get(apiURL);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Studentdata> studentList = items.map<Studentdata>((json) {
return Studentdata.fromJson(json);
}).toList();
return studentList;
}
else {
throw Exception('Failed to load data from Server.');
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Studentdata>>(
future: fetchStudents(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator()
);
return ListView(
children: snapshot.data
.map((data) => Column(children: <Widget>[
GestureDetector(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(20, 5, 0, 5),
child: Text(data.studentName,
style: TextStyle(fontSize: 21),
textAlign: TextAlign.left))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
},
);
}
}
You can try with the below lines
return FutureBuilder<List<Studentdata>>(
future: fetchStudents(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data
.map((data) =>
Column(children: <Widget>[
GestureDetector(
child: Row(
crossAxisAlignment: CrossAxisAlignment
.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(
20, 5, 0, 5),
child: Text(data.studentName,
style: TextStyle(fontSize: 21),
textAlign: TextAlign.left))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
}
else if (!snapshot.hasData) {
return Text("No data Available");
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(
child: CircularProgressIndicator()
);
},
);
You can also set a value to CircularProgressIndicator() to stop at full circle indicator.
Just do this:
if (!snapshot.hasData && !snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(
child: Text("No data Found"),
);
} else {
// do something here
}
It means that if status code is 200 and there is no data then you are going to get an error.
if you touch the heart icon, I want to insert or delete the product class in the Moor database and change the heart icon to setState()
Insertion or deletion is well executed, but the heart icon does not seem to change because setState() has already been executed during insertion or deletion.
I'd appreciate it if you could let me know if my method is wrong or if there's a better way than using a "stream builder."
Thank you for reading my question.
Widget setFavorite() {
ProductsDao productsDao = Provider.of<AppDatabase>(context).productsDao;
return StreamBuilder<List<mf.QueryRow>>(
stream: productsDao
.customSelect(
"SELECT * FROM Products WHERE firestoreid LIKE '${widget.product.firestoreid}'")
.watch(),
builder:
(BuildContext context, AsyncSnapshot<List<mf.QueryRow>> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return new Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("");
default:
return Positioned(
right: 0,
bottom: 0,
child: Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
snapshot.data.isEmpty
? productsDao.insertProduct(this.widget.product)
: productsDao.deleteProduct(this.widget.product);
});
},
child: Icon(
snapshot.data.isEmpty
? Icons.favorite_border
: Icons.favorite,
color: Colors.pink,
),
),
],
),
),
);
}
},
);
}
You can try to wait for the database to finish using await :
onTap: () async {
snapshot.data.isEmpty
? await productsDao.insertProduct(this.widget.product)
: await productsDao.deleteProduct(this.widget.product);
setState(() {}
});
},
I'm not sure how you have the method of inserting, but if you are using provider you need to use a function that will update a state and uses notifiyListeners().
When you are using notify listeners you don't even need setstate it will update all the values.
class AppDatabase extends ChangeNotifier {
ProductsDao _productsDao;
ProductsDao get productsDao => _productsDao;
void insertProduct(value){
this._productsDao.insertProduct(value);
notifiyListeners();
}
void deleteProduct(value){
this._productsDao.deleteProduct(value);
notifiyListeners();
}
}
Now you should use the provider insertProduct to refresh the state of your value.
Widget setFavorite() {
AppDatabase appState = Provider.of<AppDatabase>(context);
return StreamBuilder<List<mf.QueryRow>>(
stream: appState.productsDao
.customSelect(
"SELECT * FROM Products WHERE firestoreid LIKE '${widget.product.firestoreid}'")
.watch(),
builder:
(BuildContext context, AsyncSnapshot<List<mf.QueryRow>> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return new Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("");
default:
return Positioned(
right: 0,
bottom: 0,
child: Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
snapshot.data.isEmpty
? appState.insertProduct(this.widget.product)
: appState.deleteProduct(this.widget.product);
});
},
child: Icon(
snapshot.data.isEmpty
? Icons.favorite_border
: Icons.favorite,
color: Colors.pink,
),
),
],
),
),
);
}
},
);
This will refresh your value in the ui.
Hope it helped.