Flutter – ScrollController was used after being disposed

Issue

class HomeScreen extends StatefulWidget {
  final userName;
  HomeScreen(this.userName);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

Future<ClientDashboardModel?>? _meterReadingResponse;
BatchSummaryModel? _batchResponse;
AMRDashboardModel? _amrDashboardResponse;
String _userName = '';
int? qcAssignedReads = 0;
int? qcMrCompleted = 0;
int? qcCompletedReads = 0;
int? qcPendingReads = 0;
int? qcApprovedReads = 0;
int? qcRejectedReads = 0;
bool isFirstTimeCalled = false;
Timer? _timer;

int _currentIndex = 0;

final pages = [
  const MDMScreen(),
  const AMRDashboard(whichScreen: 0),
  const AMRDashboard(whichScreen: 1),
  const AMRDashboard(whichScreen: 2)
];

class _HomeScreenState extends State<HomeScreen> {
  @override
  void initState() {
    setUpTimedFetch();
    super.initState();
    if (widget.userName != null) _userName = (widget.userName!);
  }

  @override
  void dispose() {
    isFirstTimeCalled = false;
    _timer?.cancel();
    super.dispose();
  }

  void setUpTimedFetch() {
    if (!isFirstTimeCalled) {
      _meterReadingResponse = _getDashboardData();
    }
    isFirstTimeCalled = true;
  }

  Future<ClientDashboardModel?> _getDashboardData() async {
    ClientDashboardModel? meterReadingResponse;

    SharedPreferences prefs = await SharedPreferences.getInstance();
    String token = prefs.getString('token')!;

    var responseData = await Dashboard().getClientDashboard(token);
    if (responseData.statusCode == 200) {
      String data = responseData.body;

      var decodedData = jsonDecode(data);
      meterReadingResponse = null;
      meterReadingResponse = ClientDashboardModel.fromJson(decodedData);

      return meterReadingResponse;
    }
    return null;
  }

  void logout() async {
    SharedPreferences.getInstance().then((value) {
      value.remove('token');
      value.remove('userName');
    });
    Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) {
      return const LoginScreen();
    }));
  }

  Future<void> _showMyDialog() async {
    return showDialog<void>(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return AlertDialog(
            title: const Text(Strings.kLogoutLabel),
            content: const Text(Strings.kConfirmLogout),
            actions: <Widget>[
              TextButton(
                child: const Text(Strings.kCancelLabel),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
              TextButton(
                child: const Text(Strings.kOKLabel),
                onPressed: () {
                  Navigator.of(context).pop();
                  logout();
                },
              )
            ],
          );
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          currentIndex: _currentIndex,
          backgroundColor: const Color(0XFF116AFF),
          unselectedItemColor: Colors.white,
          selectedLabelStyle: const TextStyle(color: Colors.white),
          showUnselectedLabels: false,
          onTap: (value) {
            setState(() => _currentIndex = value);
          },
          items: [
            BottomNavigationBarItem(
              label: 'MDM',
              icon: Container(
                decoration: BoxDecoration(
                    color:
                        _currentIndex == 0 ? Colors.white : Colors.transparent,
                    shape: BoxShape.circle),
                child: const Padding(
                  padding: EdgeInsets.all(10.0),
                  child: Icon(Icons.electric_meter_outlined),
                ),
              ),
            ),
            BottomNavigationBarItem(
              label: 'Site Survey',
              icon: Container(
                decoration: BoxDecoration(
                    color:
                        _currentIndex == 1 ? Colors.white : Colors.transparent,
                    shape: BoxShape.circle),
                child: const Padding(
                  padding: EdgeInsets.all(10.0),
                  child: Icon(Icons.location_city_outlined),
                ),
              ),
            ),
            BottomNavigationBarItem(
              label: 'Meter Replace',
              icon: Container(
                decoration: BoxDecoration(
                    color:
                        _currentIndex == 2 ? Colors.white : Colors.transparent,
                    shape: BoxShape.circle),
                child: const Padding(
                  padding: EdgeInsets.all(10.0),
                  child: Icon(Icons.library_books_outlined),
                ),
              ),
            ),
            BottomNavigationBarItem(
              label: 'TroubleShoot',
              icon: Container(
                decoration: BoxDecoration(
                    color:
                        _currentIndex == 3 ? Colors.white : Colors.transparent,
                    shape: BoxShape.circle),
                child: const Padding(
                  padding: EdgeInsets.all(10.0),
                  child: Icon(Icons.info_outline),
                ),
              ),
            ),
          ],
        ),
        appBar: AppBar(
          automaticallyImplyLeading: false,
          title: const Text(Strings.kMRDLabel),
          actions: [
            IconButton(
                onPressed: () {
                  _showMyDialog();
                },
                icon: const Icon(Icons.power_settings_new))
          ],
        ),
        body: childView());
  }

  Widget childView() {
    return SingleChildScrollView(
        scrollDirection: Axis.vertical,
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text(
                        Strings.kWelcome2Label,
                        style: TextStyle(
                            color: Colors.amber.shade700,
                            fontSize: 16.0,
                            fontWeight: FontWeight.w600),
                      ),
                      Text(
                        _userName.toTitleCase(),
                        style: const TextStyle(
                            color: Colors.black,
                            fontSize: 16.0,
                            fontWeight: FontWeight.w400),
                      )
                    ],
                  ),
                  const Spacer(),
                  ClipRRect(
                    borderRadius: BorderRadius.circular(10),
                    child: Container(
                      padding: const EdgeInsets.all(10),
                      decoration: BoxDecoration(color: Colors.grey.shade200),
                      child: SvgPicture.asset(
                        'images/profile.svg',
                        height: 54.0,
                        width: 54.0,
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(
                height: 30,
              ),
              Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [pages[_currentIndex]],
              ),
            ],
          ),
        ));
  }
}

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:manager/components/dotted_line.dart';
import 'package:manager/model/lookup_cycle.dart';
import 'package:manager/model/mr_report_model.dart';
import 'package:manager/services/dashboard_service.dart';
import 'package:manager/utils/debouncer.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:manager/utils/string_captialize.dart';

class MRPerformance extends StatefulWidget {
  const MRPerformance({super.key});

  @override
  State<MRPerformance> createState() => _MRPerformanceState();
}

bool isFilter = false;
bool isPerformingRequest = false;
int pageNumber = 0;
String _chosenValue = '';
List<MRReportResult> users = [];
Future<List<String>?>? dropDownValue;
ScrollController _scrollController = ScrollController();
final _debouncer = Debouncer(milliseconds: 500);

class _MRPerformanceState extends State<MRPerformance> {
  @override
  void initState() {
    super.initState();

    _getMoreData(_chosenValue);
    dropDownValue = getAllCategory();

    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        pageNumber++;

        _getMoreData(_chosenValue);
      }
    });
  }

  Future<List<String>?>? getAllCategory() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String token = prefs.getString('token')!;

    var cycleResponse = await Dashboard().getAllCycle(token);
    try {
      if (cycleResponse.statusCode == 200) {
        List<String> items = [];
        var jsonData = json.decode(cycleResponse.body) as List;
        List<LookUpCycle> lookupCycle = jsonData
            .map<LookUpCycle>((json) => LookUpCycle.fromJson(json))
            .toList();
        items.add('Select');
        for (var element in lookupCycle) {
          if (element.name != null) {
            items.add(element.name!);
          }
        }
        return items;
      }
    } catch (ex) {
      throw (ex.toString());
    }
    return null;
  }

  @override
  void dispose() {
    super.dispose();
    _scrollController.dispose();
  }

  void _getMoreData(String searchCycle) async {
    List<MRReportResult>? resultResponse = [];
    if (!isPerformingRequest) {
      setState(() {
        isPerformingRequest = true;
      });
      SharedPreferences prefs = await SharedPreferences.getInstance();
      String token = prefs.getString('token')!;
      debugPrint(token);
      var responseData =
          await Dashboard().getMRReport(token, pageNumber, 10, searchCycle);
      if (responseData.statusCode == 200) {
        String data = responseData.body;
        var decodedData = jsonDecode(data);
        MRReportModel newEntries = MRReportModel.fromJson(decodedData);
        if (newEntries.result == null) {
          if (newEntries.result!.isEmpty) {
            double edge = 50.0;
            double offsetFromBottom =
                _scrollController.position.maxScrollExtent -
                    _scrollController.position.pixels;
            if (offsetFromBottom < edge) {
              _scrollController.animateTo(
                  _scrollController.offset - (edge - offsetFromBottom),
                  duration: const Duration(milliseconds: 500),
                  curve: Curves.easeOut);
            }
          }
        }
        setState(() {
          users.addAll(newEntries.result!);
          isPerformingRequest = false;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        resizeToAvoidBottomInset: false,
        appBar: AppBar(title: const Text('MR Performance')),
        body: filterView());
  }

  void setFilterState() {
    setState(() {
      if (isFilter == true) {
        isFilter = false;
      } else {
        isFilter = true;
      }
    });
  }

  Widget _buildProgressIndicator() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Center(
        child: Opacity(
          opacity: isPerformingRequest ? 1.0 : 0.0,
          child: const CircularProgressIndicator(),
        ),
      ),
    );
  }

  Widget filterView() {
    return Column(
      children: [
        Row(
          children: [
            Visibility(
              visible: isFilter ? true : false,
              child: Flexible(
                child: Card(
                  shape: RoundedRectangleBorder(
                      side: const BorderSide(color: Color(0XFFDCDCDC)),
                      borderRadius: BorderRadius.circular(10)),
                  elevation: 2,
                  child: Column(
                    children: [
                      Row(
                        children: [
                          Expanded(
                            child: TextField(
                              textInputAction: TextInputAction.search,
                              decoration: const InputDecoration(
                                border: InputBorder.none,
                                prefixIcon: InkWell(
                                  child: Icon(Icons.search),
                                ),
                                contentPadding: EdgeInsets.all(8.0),
                                hintText: 'Search ',
                              ),
                              onChanged: (string) {
                                _debouncer.run(() {});
                              },
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
            Visibility(
              visible: isFilter ? false : true,
              child: Flexible(
                child: Card(
                  elevation: 4,
                  shape: RoundedRectangleBorder(
                      side: const BorderSide(color: Color(0XFFDCDCDC)),
                      borderRadius: BorderRadius.circular(10)),
                  child: Padding(
                      padding: const EdgeInsets.only(
                        left: 10,
                      ),
                      child: FutureBuilder<List<String>?>(
                        future: dropDownValue,
                        builder: (context, snapshot) {
                          if (snapshot.connectionState ==
                              ConnectionState.done) {
                            if (snapshot.hasError) {
                              return const Text('Something wrong');
                            } else if (snapshot.hasData) {
                              var data = snapshot.data!;
                              return DropdownButtonHideUnderline(
                                child: DropdownButton<String>(
                                  icon: const Icon(
                                    Icons.expand_more_outlined,
                                    size: 35,
                                    color: Color(0XFF116AFF),
                                  ),
                                  borderRadius: BorderRadius.circular(10),
                                  isExpanded: true,
                                  // value: _chosenValue.isNotEmpty ? _chosenValue : null,
                                  elevation: 16,
                                  style: const TextStyle(
                                      color: Colors.black,
                                      fontSize: 14,
                                      fontWeight: FontWeight.w400),
                                  items: data.map((String value) {
                                    return DropdownMenuItem(
                                        value: value, child: Text(value));
                                  }).toList(),
                                  hint: Padding(
                                    padding: const EdgeInsets.all(15),
                                    child: Text(
                                      _chosenValue.isEmpty
                                          ? 'Cycle'
                                          : _chosenValue,
                                      style: const TextStyle(
                                          color: Colors.black,
                                          fontSize: 16,
                                          fontWeight: FontWeight.w400),
                                    ),
                                  ),
                                  onChanged: (String? value) {
                                    setState(() {
                                      if (value != null) {
                                        pageNumber = 0;
                                        users.clear();
                                        if (value == 'Select') {
                                          _chosenValue = '';
                                        } else {
                                          _chosenValue = value;
                                        }
                                        _getMoreData(_chosenValue);
                                      }
                                    });
                                  },
                                ),
                              );
                            }
                          }
                          return const CircularProgressIndicator();
                        },
                      )),
                ),
              ),
            ),
            InkWell(
              onTap: () => {setFilterState()},
              child: Card(
                elevation: 4,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10)),
                child: Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: isFilter
                      ? const Icon(Icons.filter_alt, color: Color(0XFF116AFF))
                      : const Icon(
                          Icons.search,
                          color: Color(0XFF116AFF),
                        ),
                ),
              ),
            ),
          ],
        ),
        Expanded(
          child: ListView.builder(
              shrinkWrap: true,
              scrollDirection: Axis.vertical,
              itemCount: users.length + 1,
              controller: _scrollController,
              itemBuilder: (BuildContext context, int index) {
                if (index == users.length) {
                  return _buildProgressIndicator();
                } else {
                  return cardView(users, index);
                }
              }),
        ),
      ],
    );
  }

  Widget cardView(List<MRReportResult>? users, int index) {
    return Card(
      shape: RoundedRectangleBorder(
          side: const BorderSide(color: Color(0XFFDCDCDC)),
          borderRadius: BorderRadius.circular(10)),
      margin: const EdgeInsets.all(8),
      child: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Container(
              decoration: BoxDecoration(
                  border: Border.all(
                    color: Colors.orange,
                  ),
                  borderRadius: const BorderRadius.all(Radius.circular(20))),
              margin: const EdgeInsets.all(10),
              padding: const EdgeInsets.all(10),
              child: Row(
                children: [
                  Text(
                    '${users?[index].mRNumber}: ',
                    style: const TextStyle(
                        color: Colors.orange,
                        fontSize: 10,
                        fontWeight: FontWeight.w600),
                  ),
                  Text(
                    '${users?[index].meterReaderName}'.toTitleCase(),
                    style: const TextStyle(
                        color: Colors.black,
                        fontSize: 10,
                        fontWeight: FontWeight.w400),
                  ),
                ],
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 20),
              child: Row(
                children: [
                  const Text(
                    'Supervisor: ',
                    style: TextStyle(
                        color: Color(0XFF004AC6),
                        fontSize: 10,
                        fontWeight: FontWeight.w600),
                  ),
                  Expanded(
                    child: Text(
                      '${users?[index].supervisor}'.toTitleCase(),
                      overflow: TextOverflow.fade,
                      style: const TextStyle(
                          color: Colors.black,
                          fontSize: 10,
                          fontWeight: FontWeight.w400),
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 10),
            const MySeparator(),
            const SizedBox(height: 10),
            Padding(
              padding: const EdgeInsets.only(left: 30, right: 30),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Row(
                    children: [
                      Column(
                        children: [
                          Text(
                            '${users?[index].unreadPercenatge}%',
                            style: const TextStyle(
                                color: Colors.orange,
                                fontSize: 10,
                                fontWeight: FontWeight.w600),
                          ),
                          const Text(
                            'Unread',
                            style: TextStyle(
                                color: Color(0XFF004AC6),
                                fontSize: 12,
                                fontWeight: FontWeight.w400),
                          )
                        ],
                      )
                    ],
                  ),
                  Row(
                    children: [
                      Column(
                        children: [
                          Text(
                            '${users?[index].readPercenatge}%',
                            style: const TextStyle(
                                color: Colors.green,
                                fontSize: 10,
                                fontWeight: FontWeight.w600),
                          ),
                          const Text(
                            'Read',
                            style: TextStyle(
                                color: Color(0XFF004AC6),
                                fontSize: 12,
                                fontWeight: FontWeight.w400),
                          )
                        ],
                      )
                    ],
                  ),
                  Row(
                    children: [
                      Column(
                        children: [
                          Text(
                            '${users?[index].plusorMinusTwoReadPercentage}%',
                            style: const TextStyle(
                                color: Colors.green,
                                fontSize: 10,
                                fontWeight: FontWeight.w600),
                          ),
                          const Text(
                            '+/- 2',
                            style: TextStyle(
                                color: Color(0XFF004AC6),
                                fontSize: 12,
                                fontWeight: FontWeight.w400),
                          )
                        ],
                      )
                    ],
                  ),
                  Row(
                    children: [
                      Column(
                        children: [
                          Text(
                            '${users?[index].above32ReadPercentage}%',
                            style: const TextStyle(
                                color: Colors.orange,
                                fontSize: 10,
                                fontWeight: FontWeight.w600),
                          ),
                          const Text(
                            '>32',
                            style: TextStyle(
                                color: Color(0XFF004AC6),
                                fontSize: 12,
                                fontWeight: FontWeight.w400),
                          )
                        ],
                      )
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}


════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building Builder:
A ScrollController was used after being disposed.

Once you have called dispose() on a ScrollController, it can no longer be used.
The relevant error-causing widget was
MaterialApp
lib/main.dart:13
When the exception was thrown, this was the stack

Solution

You have declared _getMoreData two times in init(), so remove it before _scrollController as shown below

void initState() {
super.initState();

_getMoreData(_chosenValue);-------------> remove this from here
dropDownValue = getAllCategory();

_scrollController.addListener(() {
  if (_scrollController.position.pixels ==
      _scrollController.position.maxScrollExtent) {
    pageNumber++;

    _getMoreData(_chosenValue);
  }
});

}

OR

Declare ScrollController _scrollController = ScrollController(); in state class _MRPerformanceState as below code:

class _MRPerformanceState extends State<MRPerformance> {
ScrollController _scrollController = ScrollController();

Answered By – Nams

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *