João Pedro
João Pedro

Reputation: 978

How to set a loading indicator while FutureProvider is not done

I'm using FutureProvider to fetch data from a local db with SQflite, and then render a graph in the Consumer child. However, when loading the app, during a brief period an error is shown :

The following StateError was thrown building Consumer<List<Map<String, dynamic>>>(dirty,
                    dependencies: [_InheritedProviderScope<List<Map<String, dynamic>>>]):
                    Bad state: No element

After the graph is rendered fine.

How can I catch this loading state so the error disappears and I can show a CircularProgressIndicator() ?

Parent

 FutureProvider<List<Map<String, dynamic>>>(
        create: (context) {
          return RecordsDatabase.instance.getRecords();
        },
        catchError: (context, error) {
          print("error: ${error.toString()}");
          return [];
        },
        initialData: [],
        child: HomeCustom(),
      )

Child

@override
  Widget build(BuildContext context) {
    return Consumer<List<Map<String, dynamic>>>(
        builder: (context, records, child) {
      GraphState graph =GraphState(records: records, context: context);

      return ChangeNotifierProvider<GraphState>(
        create: (_) => graph,
        child: Scaffold(
          backgroundColor: Colors.black,
          body: Stack(children: [
            Center(
                child: graph.records.isEmpty
                        ? Text(
                            'No Records',
                            style: TextStyle(color: Colors.white, fontSize: 24),
                          )
                        : MyGraph()),
            Align(
              alignment: Alignment.bottomRight,
              child: Padding(
                padding: const EdgeInsets.only(right: 30, bottom: 50),
                child: FloatingActionButton(
                  child: Icon(Icons.add),
                  onPressed: _setVisible,
                ),
              ),
            )
          ]),
        ),
      );
    });
  }
}

Upvotes: 0

Views: 1232

Answers (1)

rickimaru
rickimaru

Reputation: 2490

In the Consumer, check the records value first then return the appropriate widget.

Sample...

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureProvider<List<Map<String, dynamic>>?>(
        create: (_) => _getRecords(),
        initialData: null,
        catchError: (_, __) => <Map<String, dynamic>>[
          {'error': 'Something went wrong'}
        ],
        child: HomePage(),
      ),
    );
  }

  Future<List<Map<String, dynamic>>> _getRecords() async {
    final bool isError = false; // set to "true" to check error case

    await Future<void>.delayed(const Duration(seconds: 5));

    if (isError) {
      throw Exception();
    }

    return <Map<String, dynamic>>[
      <String, int>{'item': 1},
      <String, String>{'itemTxt': 'one'},
    ];
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Consumer<List<Map<String, dynamic>>?>(
          builder: (_, List<Map<String, dynamic>>? records, __) {
            if (records == null) {
              return const CircularProgressIndicator();
            } else if (records.isNotEmpty &&
                records.first.containsKey('error')) {
              return Text(records.first['error'] as String);
            }

            return Text(records.toString());
          },
        ),
      ),
    );
  }
}

Upvotes: 2

Related Questions