Problem when Displaying the fetched data for the first time in flutter App

Viewed 608

In My Application, I fetch data from an API and save the data onto a List. When I try to display the data on the screen, it gives a red screen with a warning for the first few seconds and then loads the data into the screen.

compiler gives this warning message to console


════════ Exception caught by widgets library ═══════════════════════════════════
The following StateError was thrown building StatusScreen(dirty, dependencies: [_InheritedProviderScope<CovidData>], state: _StatusScreenState#54e51):
Bad state: No element

The relevant error-causing widget was
StatusScreen
When the exception was thrown, this was the stack
#0      List.first (dart:core-patch/growable_array.dart:332:5)
#1      _StatusScreenState.build
#2      StatefulElement.build
#3      ComponentElement.performRebuild
#4      StatefulElement.performRebuild
...
════════════════════════════════════════════════════════════════════════════════

This is the status screen

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:bubble_tab_indicator/bubble_tab_indicator.dart';

import 'package:Health_app/data/covidData.dart';  /* ---> the fetched data method and List of data is from here  */

import 'package:Health_app/screens/covidBarChart.dart';   /*-----> stateless widget that renders a barchart from provided data   */

import 'package:Health_app/config/palatte.dart';  
import 'package:Health_app/config/styles.dart';

import 'package:Health_app/widgets/custom_app_bar.dart';
import 'package:Health_app/widgets/statusGridViewer.dart';  /*----->stateless widget that provide information based on the bool values provided */

class StatusScreen extends StatefulWidget {
  static const routeName = '/statusScreen';
  @override
  _StatusScreenState createState() => _StatusScreenState();
}

class _StatusScreenState extends State<StatusScreen> {
  var _isInit = true;
  var _isLoading = false;


  var _local = true;
  var _today = true;

  get local {
    return _local;
  }

  get today {
    return _today;
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print("Didchangedependanciescalled");
    if (_isInit) {
      setState(() {
        _isLoading = true;
      });
      Provider.of<CovidData>(context, listen: true)
      .fetchAndSetDataCovid()
      .then((_) => {
      setState(() {
        _isLoading = false;
      }),
      });
    }
    _isInit = false;
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {

/* ----> I think the problem occurs from here, 
 when I'm calling the Provider to obtain the fetched data, the data obtained is stored in a `List<CovidProperties>` named **data**. 
    

    final covidData = Provider.of<CovidData>(context, listen: false);
    final dataInstance = covidData.data;

    final List<String> numList = [];
    final List<String> numList2 = [];
    final List<int> newNumList;

/*Here I'm selecting the 'pcrData' list, it consist of key value pairs of data,
 where value is another list of values with 'pcr_count' and 'date'     */
   
    dataInstance.first.pcrData.forEach((element) {
      numList.add(element['pcr_count']);
      numList2.add(element['date']);
    });

    newNumList = numList.map((e) => int.parse(e)).toList();

    return Scaffold(
      appBar: CustomAppBar(),
      backgroundColor: Palette.primaryColor,
      body: _isLoading
          ? Center(
              child: CircularProgressIndicator(),
            )
          : CustomScrollView(
              physics: ClampingScrollPhysics(),
              slivers: [
                _buildHeader(),
                _buildRegionTabBar(),
                _buildStateTabBar(),
                SliverPadding(
                  padding: const EdgeInsets.symmetric(horizontal: 10.0),
                  sliver: SliverToBoxAdapter(
                    child: StatusGridViewer(local, today),
                  ),
                ),
                SliverPadding(
                  padding: const EdgeInsets.symmetric(horizontal: 10.0),
                  sliver: SliverToBoxAdapter(
                    child: CovidBarChart(
                      covidCases: newNumList.sublist(1, 8),
                      covidDates: numList2,
                    ),
                  ),
                ),
              ],
            ),
    );
  }

  SliverPadding _buildHeader() {
    return SliverPadding(
      padding: const EdgeInsets.all(20.0),
      sliver: SliverToBoxAdapter(
        child: const Center(
          child: const Text(
            'Statistics',
            style: const TextStyle(
              color: Colors.white,
              fontSize: 25.0,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    );
  }

  SliverToBoxAdapter _buildRegionTabBar() {
    return SliverToBoxAdapter(
      child: DefaultTabController(
        length: 2,
        child: Container(
          margin: EdgeInsets.symmetric(horizontal: 20.0),
          height: 50.0,
          decoration: BoxDecoration(
            color: Colors.white24,
            borderRadius: BorderRadius.circular(25.0),
          ),
          child: TabBar(
            indicator: const BubbleTabIndicator(
              tabBarIndicatorSize: TabBarIndicatorSize.tab,
              indicatorHeight: 40.0,
              indicatorColor: Colors.white60,
            ),
            labelStyle: Styles.tabTextStyle,
            labelColor: Colors.black,
            unselectedLabelColor: Colors.white,
            tabs: [
              const Text('Local'),
              const Text('Global'),
            ],
            onTap: (index) {
              print('Statmeaer changed ' + index.toString());

              setState(() {
                _local = !_local;
              });
            },
          ),
        ),
      ),
    );
  }

  SliverPadding _buildStateTabBar() {
    return SliverPadding(
      padding: const EdgeInsets.all(20.0),
      sliver: SliverToBoxAdapter(
        child: DefaultTabController(
          length: 2,
          child: TabBar(
            indicatorColor: Colors.transparent,
            labelStyle: Styles.tabTextStyle,
            labelColor: Colors.white,
            unselectedLabelColor: Colors.white54,
            tabs: [
              const Text('Today'),
              const Text('Total'),
            ],
            onTap: (index) {
              print('Date to show details selected ' + index.toString());
              setState(() {
                _today = !_today;
              });
            },
          ),
        ),
      ),
    );
  }
}

This is the covid properties class

class CovidProperties {
  final String updateDate;
  final int localNewCases;
  final int localTotalCases;
  final int totalHospitalized;
  final int localDeaths;
  final int localNewDeaths;
  final int localRecovered;
  final int localActiveCases;
  final int globalNewCases;
  final int globalTotalCases;
  final int globalDeaths;
  final int globalNewDeaths;
  final int globalRecovered;
  final int totalPCR;
  final List<dynamics> pcrData;

  CovidProperties(
      {required this.updateDate,
      required this.localNewCases,
      required this.localTotalCases,
      required this.totalHospitalized,
      required this.localDeaths,
      required this.localNewDeaths,
      required this.localRecovered,
      required this.localActiveCases,
      required this.globalNewCases,
      required this.globalTotalCases,
      required this.globalDeaths,
      required this.globalNewDeaths,
      required this.globalRecovered,
      required this.totalPCR,
      required this.pcrData
      });
}

Link to the API where I fetch Data data-link

2 Answers

If you are fetching the data at the start of the widget I think that you should use a FutureBuilder in order to do that. In fact, you will avoid errors trying to access your fetched data in case you have not recieved yet the data from the API.

      class _StatusScreenState extends State<StatusScreen> {
      var _isLoading = false;   
      var _local = true;
      var _today = true;
      Future _initFuture;
    
      get local {
        return _local;
      }
    
      get today {
        return _today;
      }
    
      @override
      void initState() {
        _initFuture = Provider.of<CovidData>(context, listen: false).fetchAndSetDataCovid();  
        super.initState();
      }
    
      @override
      void didChangeDependencies() {     
        super.didChangeDependencies();
      }
    
      @override
      Widget build(BuildContext context) {
        final covidDataProvider = Provider.of<CovidData>(context, listen: false);
        
        //Move this logic to the provider
        //final List<String> numList = [];
        //final List<String> numList2 = [];
        //final List<int> newNumList;
  
        //dataInstance.first.pcrData.forEach((element) {
        // numList.add(element['pcr_count']);
        // numList2.add(element['date']);
        //});
    
        //newNumList = numList.map((e) => int.parse(e)).toList();
    
        return Scaffold(
          appBar: CustomAppBar(),
          backgroundColor: Palette.primaryColor,
          body: FutureBuilder(
          future: _initFuture,
          builder: (ctx, snapshot) {
          if(snapshot.connectionState != ConnectionState.done)
           return Center(
                  child: CircularProgressIndicator(),
                )
              else 
           return CustomScrollView(
                  physics: ClampingScrollPhysics(),
                  slivers: [
                    _buildHeader(),
                    _buildRegionTabBar(),
                    _buildStateTabBar(),
                    SliverPadding(
                      padding: const EdgeInsets.symmetric(horizontal: 10.0),
                      sliver: SliverToBoxAdapter(
                        child: StatusGridViewer(local, today),
                      ),
                    ),
                    SliverPadding(
                      padding: const EdgeInsets.symmetric(horizontal: 10.0),
                      sliver: SliverToBoxAdapter(
                        child: CovidBarChart(
                          covidCases: covidDataProvider.newNumList.sublist(1, 8),
                          covidDates: covidDataProvider.numList2,
                        ),
                      ),
                    ),
                  ],
                ),
        );
      }

I was able to Resolve this issue by moving the methods that I use to create the numList,NumList1, newNumList inside to my Providers fetchAndSetDataCovid method.

first I implemented List<int> pcrData and List<string> date separately and created this function to populate them. and after that Running the method inside of fetchAndSetCovidData() Method.

  void getPcrCount() {
    final List<String> pcrCount1 = [];

    data.first.pcrData.forEach((element) {
      pcrCount1.add(element['pcr_count']);
      date.add(element['date']);
    });

    pcrCount = pcrCount1.map((e) => int.parse(e)).toList();
  }
Related