Harry76
Harry76

Reputation: 104

Trying to implement loading spinner while loading data from Firestore with Flutter

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

Answers (3)

Jan
Jan

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

Blasanka
Blasanka

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

Peter Haddad
Peter Haddad

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

Related Questions