flutterpaginateddatatableflutter-datatable

Flutter app state not mounted (setState) on click inside DataTable


Whenever I click within a DataRow and try to setState(), apparently the main app state is not mounted. If anyone can point me into the right direction as to why this happens, I would greatly appreciate it. I really need to be able to update the app (setState) after clicking within a DataTable.

Example code:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  setState(fn){
    if (mounted) {
      super.setState(fn);
    } else {
      debugPrint('Not mounted');
    }
  }

  update() => setState((){});

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo'),
      ),
      body: PaginatedDataTable(
        showCheckboxColumn: true,
        columns: const <DataColumn>[
          DataColumn(
            label: Expanded(
              child: Text(
                'Name',
                style: TextStyle(fontStyle: FontStyle.italic),
              ),
            ),
          ),
          DataColumn(
            label: Expanded(
              child: Text(
                'Age',
                style: TextStyle(fontStyle: FontStyle.italic),
              ),
            ),
          ),
          DataColumn(
            label: Expanded(
              child: Text(
                'Role',
                style: TextStyle(fontStyle: FontStyle.italic),
              ),
            ),
          ),
        ],
        source: MyDataTableSource(),
      ),
    );
  }
}

class MyDataTableSource extends DataTableSource {
  @override
  DataRow getRow(int index) {
    return DataRow.byIndex(
      index: index,
      cells: <DataCell>[
        DataCell(GestureDetector(
          onTap: () => _MyHomePageState().update(),
          child: const Text('Sarah'),
        )),
        DataCell(GestureDetector(
          onTap: () => _MyHomePageState().update(),
          child: const Text('19'),
        )),
        DataCell(GestureDetector(
          onTap: () => _MyHomePageState().update(),
          child: const Text('Student'),
        )),
      ],
    );
  }

  @override
  bool get isRowCountApproximate => false;

  @override
  int get rowCount => 3;

  @override
  int get selectedRowCount => 0;
}

Solution

  • You are triggering .update() on a new state reference. Use a proper state management or you're forced to use setState use "Key" like this

    import 'package:flutter/material.dart';
    
    GlobalKey<MyHomePageState> myHomePageKey = GlobalKey<MyHomePageState>();
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return  MaterialApp(
          title: 'Flutter Demo',
          home: MyHomePage(key:myHomePageKey),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key});
    
      @override
      State<MyHomePage> createState() => MyHomePageState();
    }
    
    class MyHomePageState extends State<MyHomePage> {
    
      @override
      setState(fn){
        if (mounted) {
          super.setState(fn);
        } else {
          debugPrint('Not mounted');
        }
      }
    
      update() => setState((){});
    
      @override
      Widget build(BuildContext context) {
    
        return Scaffold(
          appBar: AppBar(
            title: const Text('Flutter Demo'),
          ),
          body: PaginatedDataTable(
            showCheckboxColumn: true,
            columns: const <DataColumn>[
              DataColumn(
                label: Expanded(
                  child: Text(
                    'Name',
                    style: TextStyle(fontStyle: FontStyle.italic),
                  ),
                ),
              ),
              DataColumn(
                label: Expanded(
                  child: Text(
                    'Age',
                    style: TextStyle(fontStyle: FontStyle.italic),
                  ),
                ),
              ),
              DataColumn(
                label: Expanded(
                  child: Text(
                    'Role',
                    style: TextStyle(fontStyle: FontStyle.italic),
                  ),
                ),
              ),
            ],
            source: MyDataTableSource(),
          ),
        );
      }
    }
    
    class MyDataTableSource extends DataTableSource {
        
      @override
      DataRow getRow(int index) {
        return DataRow.byIndex(
          index: index,
          cells: <DataCell>[
            DataCell(GestureDetector(
              onTap: () => myHomePageKey.currentState!.update(),
              child: const Text('Sarah'),
            )),
            DataCell(GestureDetector(
              onTap: () => myHomePageKey.currentState!.update(),
              child: const Text('19'),
            )),
            DataCell(GestureDetector(
              onTap: () => myHomePageKey.currentState!.update(),
              child: const Text('Student'),
            )),
          ],
        );
      }
    
      @override
      bool get isRowCountApproximate => false;
    
      @override
      int get rowCount => 3;
    
      @override
      int get selectedRowCount => 0;
    }