abra
abra

Reputation: 134

Flutter - Widget that no longer appears in the widget tree or this error might indicate a memory leak Warning

I have 3 page I check transitions with bottomNavigationBar first page is Soclose in this page im gettting information from the database and print it on the screen. I'm getting information from the database smoothly but when i switch screens my console gives warning messages. An error appears in the console, but the application is working properly. When changing screens and returning to the old page(Soclose page), an error page appears and disappears within milliseconds.

I cant find similar questions and i tried to make suggestions in the warnings but either I couldn't do it or the solutions don't work.

enter image description here

enter image description here

Related soclose dart file:

class _Closesevents extends State<Soclose> {
  List<Event> eventList;
  int eventListLen;

  @override
  void initState() {
    try{
    final Future<Database> dbFuture = DbHelper.initializeDatabase();
    dbFuture.then((database) {
      Future<List<Event>> eventListFuture = DbHelper().getEventList();
      eventListFuture.then((eventList) {
        setState(() {
          this.eventList = eventList;
          this.eventListLen = eventList.length;
        });
      });
    });}
    catch (e,s)
    {
      print("[ERROR] $e");
      print("[ERROR TREE]\n$s");
    }
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Container(
      child: new ListView.builder(
          itemCount: eventListLen,
          itemBuilder: (BuildContext context, int index) =>
              buildTripCard(context, index)),
    );
  }

  Widget buildTripCard(BuildContext context, int index)
  ...

Databasehelper file

import ...

class DbHelper {
  static DbHelper _databaseHelper;    // Singleton DatabaseHelper
  static Database _database;

  static final String _tablename = EventConstants.TABLE_NAME;
  static final String _columnId = EventConstants.COLUMN_ID;
  static final String _columnTitle = EventConstants.COLUMN_TITLE;
  static final String _columnDate = EventConstants.COLUMN_DATE;
  static final String _columnStartTime = EventConstants.COLUMN_STARTTIME;
  static final String _columnFinishTime = EventConstants.COLUMUN_FINISHTIME;
  static final String _columnDesc = EventConstants.COLUMN_DESCRIPTION;
  static final String _columnIsActive = EventConstants.COLUMN_ISACTIVE;

  DbHelper._createInstance(); // Named constructor to create instance of DatabaseHelper

  factory DbHelper() {

    if (_databaseHelper == null) {
      _databaseHelper = DbHelper._createInstance(); // This is executed only once, singleton object
    }
    return _databaseHelper;
  }

  Future<Database> get database async {

    if (_database == null) {
      _database = await initializeDatabase();
    }
    return _database;
  }

  static Future<Database> initializeDatabase() async {
    Directory directory = await getApplicationDocumentsDirectory();
    String path = directory.path + 'takvimapp.db';

    // Open/create the database at a given path
    var notesDatabase = await openDatabase(path, version: 1, onCreate: _createDb);
    return notesDatabase;
  }

  static void _createDb(Database db, int newVersion) async {

    await db.execute('CREATE TABLE $_tablename ( $_columnId INTEGER PRIMARY KEY NOT NULL,$_columnTitle TEXT ,$_columnDate TEXT,$_columnStartTime TEXT,$_columnFinishTime TEXT,$_columnDesc TEXT,$_columnIsActive INTEGER);');
  }

  // Get all events --map
  Future<List<Map<String, dynamic>>> getEventMapList() async {
    Database db = await this.database;
    var result = await db.query(_tablename, orderBy: '$_columnTitle ASC');
    return result;
  }

  // Insert Operation: Insert a Event object to database
  Future<int> insertEvent(Event event) async {
    Database db = await this.database;
    var result = await db.insert(_tablename, event.toMap());
    return result;
  }

  // Update Operation: Update a Event object and save it to database
  Future<int> updateEvent(Event event) async {
    var db = await this.database;
    var result = await db.update(_tablename, event.toMap(), where: '$_columnId = ?', whereArgs: [event.id]);
    return result;
  }

  // Delete Operation: Delete a Event object from database
  Future<int> deleteEvent(int id) async {
    var db = await this.database;
    int result = await db.rawDelete('DELETE FROM $_tablename WHERE $_columnId = $id');
    return result;
  }

  // Get number of Event objects in database
  Future<int> getCount() async {
    Database db = await this.database;
    List<Map<String, dynamic>> x = await db.rawQuery('SELECT COUNT (*) from $_tablename');
    int result = Sqflite.firstIntValue(x);
    return result;
  }

  // Convert map to list
  Future<List<Event>> getEventList() async {

    var eventMapList = await getEventMapList(); // Get 'Map List' from database
    int count = eventMapList.length;         // Count the number of map entries in db table

    List<Event> eventList = List<Event>();
    // For loop to create a 'Event List' from a 'Event List'
    for (int i = 0; i < count; i++) {
      eventList.add(Event.fromMap(eventMapList[i]));
    }

    return eventList;
  }
  static Future closeDb() => _database.close();
}

The error warning is constantly written to the console in an infinite loop. To get rid of the warning, I need to close the app and restart the emulator.

Warning message:

E/flutter (30455): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() >called after dispose(): _CountDownItemState#2bbc3(lifecycle state: defunct, not mounted)

E/flutter (30455): This error happens if you call setState() on a State object for a widget that no >longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its >build). This error can occur when code calls setState() from a timer or an animation callback. E/flutter (30455): The preferred solution is to cancel the timer or stop listening to the animation >in the dispose() callback.

Another solution is to check the "mounted" property of this object >before calling setState() to ensure the object is still in the tree.

E/flutter (30455): This error might indicate a memory leak if setState() is being called because >another object is retaining a reference to this State object after it has been removed from the >tree. To avoid memory leaks, consider breaking the reference to this object during dispose().

Solution:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
          future: _db.getEventList(),
          builder: (context, snapshot) {
            if (snapshot.data == null) {
              return Container(
                child: Text("Loading....."),
              );
            } else {
              return ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (BuildContext context, int index) {
                    return ListTile(
                      title: Text(snapshot.data[index].title),
                    );
                  });
            }
          }),
    );
  }

Upvotes: 1

Views: 1728

Answers (1)

Christopher Moore
Christopher Moore

Reputation: 17113

The issue is with your initState function override. It's good practice to also call the super of initState, super.initState, before all other logic. Your Futures may be completing too quickly, and calling setState before the state is even initialized. Simply move super.initState(); as the first statement in the override. Ex.

@override
void initState() {
  super.initState();//Always call this first

  try{
    final Future<Database> dbFuture = DbHelper.initializeDatabase();
    dbFuture.then((database) {
      Future<List<Event>> eventListFuture = DbHelper().getEventList();
      eventListFuture.then((eventList) {
        setState(() {
          this.eventList = eventList;
          this.eventListLen = eventList.length;
        });
      });
    });}
    catch (e,s)
    {
      print("[ERROR] $e");
      print("[ERROR TREE]\n$s");
    }
}

Edit: However, this can still lead to errors as setState could still be called before the widget is mounted. This is why the FutureBuilder widget exists. Wrap the widget that needs this Future data in your build method, pass the Future to the future parameter of the FutureBuilder and access the data with the AsyncSnapshot that the builder provides. See more about FutureBuilder.

Upvotes: 3

Related Questions