Reputation: 104
I'm working on an app that display spinner when backend loading data from Firestore but it's not worked as intended and I'm having a struggle to find the flaw.
My Orders Page code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:table_service/providers/order.dart';
import '../providers/session.dart';
class OrdersPage extends StatefulWidget {
bool isLoading = true;
@override
_OrdersPageState createState() => _OrdersPageState();
}
class _OrdersPageState extends State<OrdersPage> {
List<Order> _orders = [];
@override
Widget build(BuildContext context) {
final session = Provider.of<Session>(context, listen: false);
return Scaffold(
floatingActionButton: session.privilege == 'Administrator' ||
session.privilege == 'Waiter' ||
session.privilege == 'Customer'
? FloatingActionButton(
heroTag: 'OrdersPageFAB',
onPressed: () {},
child: Icon(Icons.add, color: Colors.white),
)
: null,
body: FutureBuilder(
future: session.fetchOrdersData(),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
print(snapshot.data);
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 2 / 2,
),
itemCount: _orders.length,
itemBuilder: (_, i) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: Card(
child: GridTile(
child: Icon(
Icons.library_books,
size: 100.0,
color: Colors.grey,
),
footer: GridTileBar(
backgroundColor: Colors.black54,
title: Text('Order by: ${_orders[i].name}'),
),
),
),
);
},
);
}
},
),
);
}
}
The fetchOrdersData() handler
final Auth auth = Auth();
final Firestore database = Firestore.instance;
String user_name;
String privilege;
List<Food> _foods = [];
List<Order> _orders = [];
List<TransactionModel.Transaction> _transactions = [];
...
...
Future fetchOrdersData() async {
_orders.clear();
return await database.collection('orders').getDocuments().then((documents) {
documents.documents.forEach((order) {
database
.collection('users')
.document(order.data['uid'])
.get()
.then((user) {
_orders.add(Order(
id: order.documentID,
tableNumber: order.data['tablenumber'],
orderDate: (order.data['orderdate'] as Timestamp).toDate(),
status: order.data['status'],
note: order.data['note'],
uid: order.data['uid'],
name: user.data['user_name'],
));
});
});
return _orders;
});
notifyListeners();
}
get getOrders {
return [..._orders];
}
I have tried many methods including StreamBuilder, setState() and recently FutureBuilder. Did i just missing an important code? Or did i use the wrong method?
The problem was Orders Page showing 0 data even though List on _fetchOrdersData() have 1 element.
for full source code here on github
Upvotes: 1
Views: 4867
Reputation: 1492
The other answers look reasonable. They are just missing data validation checks, which I find is required in all my apps. Because if I have a good connection, and hasData is true and hasError is false, there might be no documents at all though. This should be checked. Here is a snippet from my projects.
Checking connection state is the same as just checking snapshot.hasError.
Widget _getMyFriends() {
return StreamBuilder<QuerySnapshot>(
stream: Database.getFriendsByUserId(widget.loggedInUserId),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return Center(child: Text("Error"));
else if (!snapshot.hasData)
return Center(child: Text("Loading..."));
else if (snapshot.data.documents.isEmpty) //also check if empty! show loader?
return Center(child: Text("No friends added yet."));
else
return ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return SimpleUserPanel(userId: document['friendid']);
}).toList(),
);
}
);
}
Upvotes: 2
Reputation: 22437
Loking at your code, you have to check for ConnectionState.active
also with your snapshot.connectionState == ConnectionState.waiting
.
Actually you have more power and controll when using FutureBuilder
or StreamBuilder
. Below is a sample code snippet:
switch (snapshot.connectionState) {
case ConnectionState.none:
return Center(child: Text("Check Connection"));
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator(backgroundColor: Theme.of(context).primaryColorLight,));
case ConnectionState.done:
if (snapshot.hasError) {
return Center(child: Text("Error occured!"));
} else if (snapshot.hasData) {
return YourWidgetWithData();
} else {
debugPrint("What went wrong");
return SizedBox();
}
break;
default:
return SizedBox();
}
Upvotes: 0
Reputation: 80924
You should do the following:
else {
if(snapshot.hasData){
print(snapshot.data);
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 2 / 2,
),
itemCount: _orders.length,
itemBuilder: (_, i) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: Card(
child: GridTile(
child: Icon(
Icons.library_books,
size: 100.0,
color: Colors.grey,
),
footer: GridTileBar(
backgroundColor: Colors.black54,
title: Text('Order by: ${_orders[i].name}'),
),
),
),
);
},
// By default, show a loading spinner.
return CircularProgressIndicator();
},
So first check if snapshot
has data using the property hasData
and since this is asynchronous it will first execute the return CircularProgressIndicator();
and then execute the if
block.
Upvotes: 0