Search code examples
androidflutterdart

Why is a provider value in my drawer not updating in real time?


I have a form called Forms that has an endDrawer. Inside this form, I am using a provider called GlobalsProvider that stores all my global variables. One of them is pendingOutgoingTransactions, which shows how many pending transactions are stored in sqflite waiting to be sent to SQL Server.

class Forms extends StatefulWidget {
  const Forms({super.key});
  @override
  State<Forms> createState() => _FormsState();
}
class _FormsState extends State<Forms> {
  @override
  Widget build(BuildContext context) {
    return Consumer<GlobalsProvider>(
        builder: (context, globals, child) => Scaffold(
          appBar: //...
          endDrawer: Drawer(
            child: Column(
              children: [
                Expanded(flex: 16,
                    child: DrawerHeader(
                        child: Column(
                          children: [
                            Row(
                              children: [
                                const Text('Pending Transactions: ', style: TextStyle(fontSize: 20.0, color: Colors.grey),),
                                Text(globals.pendingOutgoingTransactions.toString(), style: const TextStyle(fontSize: 20.0),)
                              ],
                            ),
                          ],
                        )
                    )
                ),
              ],
            ),
          ),
          body: //...
          )
        )
    );
  }
}

When I go to another form and write to sqflite then hit back to go to Forms, this variable correctly increments by 1. I have a timer that fires every 30 seconds to send any pending transactions to SQL Server and, after sending, it decrements the pendingOutgoingTransactions variable. This is the part that isn't working right. While I'm on the Forms screen, I have the drawer opened and I don't see the value going down. It just stays at 1. Below are the relevant parts of GlobalsProvider.

GlobalsProvider:

class GlobalsProvider extends ChangeNotifier {

  Timer? localDBOutgoingStreamTimer; ///Used to continuously send data from localDB to SQL Server
  int pendingOutgoingTransactions = 0;

  Future<Map<String, dynamic>> writeToLocalDB() async {
    Map<String, dynamic> returnMap = {};

    //...write...

    final physInvRows = await localDBQueryRowCount(table: myTable); //Gets the current number of pending rows
    pendingOutgoingTransactions = physInvRows;

    notifyListeners();

    return returnMap;
  }

  void localDBStartOutgoingStream() {
    localDBOutgoingStreamTimer = Timer.periodic(const Duration(seconds: 30), (Timer t) => localDBOutgoingStream());

    notifyListeners();
  }

  void localDBOutgoingStream() async {
    //...send any rows to SQL Server...

    final physInvRowsAfter = await localDBQueryRowCount(table: myTable);

    pendingOutgoingTransactions = physInvRowsAfter;

    notifyListeners();
  }

Login Screen:

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

  @override
  State<Login> createState() => _LoginState();
}

class _LoginState extends State<Login> {
  final globalsClass = GlobalsProvider();

  @override
  void initState() {
    super.initState(); ///Run this first
    initialize();
  }

  @override
  dispose() {
    super.dispose(); ///Run this last
  }

  void initialize() async {
    globalsClass.localDBStartOutgoingStream();

    if (serverList.isNotEmpty) {
      //...runs a function that runs "setState(() {})" at the end
    }
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<GlobalsProvider>(
        builder: (context, globals, child) => Scaffold(
            appBar: //...
            body: //...
        )
    );
  }
}

Main:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => GlobalsProvider(),
      child: GetMaterialApp(
        title: 'My App',
        home: const MyHomePage(title: 'My App',),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return const Wrapper();
  }
}

Solution

  • For now try this so you're sure its the same object

    class Login extends StatefulWidget {
      const Login({super.key});
    
      @override
      State<Login> createState() => _LoginState();
    }
    
    class _LoginState extends State<Login> {
    
      @override
      void initState() {
        super.initState(); ///Run this first
        initialize();
      }
    
      @override
      dispose() {
        super.dispose(); ///Run this last
      }
    
      void initialize() async {
        /// initialize the variable here nad use it only here,
        /// I don't know where or how globalsClass is created in your app
        final globalsClass = Provider.of<GlobalsProvider>(context, listen: false);
        globalsClass.localDBStartOutgoingStream();
    
        if (serverList.isNotEmpty) {
          //...runs a function that runs "setState(() {})" at the end
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Consumer<GlobalsProvider>(
            builder: (context, globals, child) => Scaffold(
                appBar: //...
                body: //...
            )
        );
      }
    }