0

I have this StatefulWidget that supposed to retrieve data from MySQL database based on condition and add those retrieved values into an array so I can use the array values to be display on ListView. My Problem is that sometimes when the array (_fileValues) and _custUsername variable are either empty or not updated and no exception is thrown.

Here's my statefulwidget (main.dart)

String? _custUsername;
List<String> _fileValues = <String>[];

class cakeLogin extends StatefulWidget {
  @override
  cakeLoginPageMain createState() => cakeLoginPageMain(); 
}

class cakeLoginPageMain extends State<cakeLogin> {

  String? _CustEmailInit; 
  BuildContext? loginContext;

  List<String> _fileNameStores = <String>[];

  void callData() {
    // @ Get username based on user email and assign it to _custUsername variable
    UsernameGetter().Connection(_CustEmailInit!).then((UsernameValue) {
      _custUsername = UsernameValue!;
    });
    
    // @ Get item names based on the user username (_custUsername) and add them into
    // _fileNameStores
    NameGetter().Connection(_custUsername).then((NamesValues) {
      for(var _nameItems in NamesValues) {
        setState(() {
          _fileNameStores.add(_nameItems);
        });
      }
    });
    // @ Remove duplicates value and add them into main list (_fileValues)
    var _removeDuplicates = _fileNameStores.toSet().toList();
    for(var item in _removeNamesDupes) {
      setState(() {
       _fileValues.add(item);
      });
     }

  }
  
  @override
  Widget build(BuildContext context) {

    final _emailController = TextEditingController();

    return Scaffold(
      appBar: AppBar(
        title: Text("Login to App",
          style: TextStyle(
            fontSize: 18,
            color: setupFontCol,
            fontWeight: FontWeight.bold
          )),
        backgroundColor: setupThemeBar,
        centerTitle: true,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20),
        )
      ),
      backgroundColor: setupMainTheme,
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextFormField(
              controller: _emailController,
              style: TextStyle(color: Color.fromARGB(255, 214, 213, 213)),
              decoration: InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
                filled: true,
                hintStyle: TextStyle(color: Color.fromARGB(255, 197, 197, 197)),
                hintText: "Enter your email",
                fillColor: setupThemeBar
              ),
            ),

           SizedBox(height: 18),

           SizedBox(height: 25),

          SizedBox(
            height: 45,
            width: 500,
            child: ElevatedButton(
              style: 
                ElevatedButton.styleFrom(
                  primary: setupFontCol,
                  onPrimary: Colors.white,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10),
                  )
                ),
              onPressed: () {

                var _CustEmail = _emailController.text;

                if(_CustEmail.contains("@gmail.com") || _CustEmail.contains("@email.com")) {
                   if(_CustEmail != "") {
                          _CustEmailInit = _CustEmail;
                          loginContext = context;

                          _isFromLogin = true;
  
                          _fileValues.clear();
                          callData();

                    } else {
                      alertDialog("Please enter your email address",context);
                    }
                } else {
                    alertDialog("Email address is not valid",context);
                }
              },
              //color: setupFontCol,
              child: Text("Login",
                style: TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.normal,
                  fontSize: 14,
                ))
              ),
            ),
          ],
        ),
      ),      
    );
  }
}

UsernameGetter class (This class will retrieve user Username from database table based on their email from textfield input)

When I print _usernameDetected from UsernameGetter class it returns the updated version of the returned string but when I assign the _custUsername to UsernameValue from main.dart it does not update and sometimes it returns null value.

import 'package:mysql_client/mysql_client.dart';
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:signuppage/main.dart' as main;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class UsernameGetter {
  Future<String> Connection(String _custEmail) async {
    final conn = await MySQLConnection.createConnection(
      host: "",
      port: ,
      userName: "",
      password: "",
      databaseName: "",
    );

    await conn.connect();

    var _getUsernameQue = await conn.execute("SELECT CUST_USERNAME FROM information WHERE CUST_EMAIL = :email", 
    {
      "email": _custEmail
    });

    String? _usernameDetected;
    for(final _rowsOfEmail in _getUsernameQue.rows) {
      _usernameDetected = _rowsOfEmail.assoc()['CUST_USERNAME']!;
    }
    return await _usernameDetected!;
  }
}

NameGetter class (Retrieve user items names which are stored in text column)

For NameGetter class the same case happens as UsernameGetter. When I retrieve the values from main.dart and add the values into _fileValues list sometimes the values is empty and sometimes the value is not updated.

import 'package:mysql_client/mysql_client.dart';
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:signuppage/main.dart' as main;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class NameGetter {

  Future<List<String>> Connection(String _custUsername) async {
    final conn = await MySQLConnection.createConnection(
      host: "",
      port: ,
      userName: "",
      password: "",
      databaseName: "",
    );
    await conn.connect();

    var _getItemNames= await conn.execute("SELECT ITEM_NAMES FROM item_details WHERE CUST_USERNAME = :username",
      {
        "username": _custUsername
      });

    List<String>? _itemNamesValues= <String>[]; 
    for(var _rowsOfItemNames in _getItemNames.rows) {
      var _retrieveNames = await _rowsOfItemNames.assoc()['ITEM_NAMES']!;
      _itemNamesValues.add(_retrieveNames);
    }

    return await _itemNamesValues;
  }
}

I'm not sure where's my mistakes is and I'm pretty sure I made a lot of mistake and I need someone to help me figure it out. Especially on why the list and the variable are not updated/and is sometimes empty.

Refer links:

Flutter ListView not updating

my list in flutter is not updating the problem in my code

Why variable value is not refreshing in flutter?

  • Your code implies that you have null safety disabled. I strongly encourage you to run with null safety to catch issues before runtime. In any case, `_custUsername` is set from the result of a Future. However, you are using `_custUsername` potentially before it has been assigned, in the call to `NameGetter().Connection(_custUsername)`. You might consider refactoring `callData()` to use `async`/`await`. – Chuck Batson Feb 21 '23 at 03:09
  • So you mean I have to get rid of `then` statement and replace it with `async/await` instead? if yes then i've tried that but I gets lost and not sure how to properly implement it. Can you provide an example on Answer how to implement async/await for my case? – Pythonic User Feb 22 '23 at 10:05
  • You don't *have* to, but it will be easier to understand the flow of code if you do. Added an answer with an example refactor of `callData()` using `async`/`await`. – Chuck Batson Feb 22 '23 at 23:38

1 Answers1

1

The Problem

A stripped down version of your callData() method looks like this:

  var foo;

  void callData() {
    someAsyncFunction().then( ... foo = value ... );
    anotherAsyncFunction(foo).then( ... );
  }

As written, anotherAsyncFunction(foo) is called immediately following someAsyncFunction(), before the then() which assigns the value of foo.

You can observe this behavior for yourself by strategically inserting print() statements.

The Solution

Under the hood, Future.then() and async/await are the same thing. However, it is generally easier for folks to reason about the behavior of asynchronous code when using async/await, because then it reads like sequentially executed code.

Although you can use either, I suggest that you move to async/await to make the code easier to follow when reading.

Here's one example of how you might refactor callData():

  Future<void> callData() async {
    // @ Get username based on user email and assign it to _custUsername variable
    var custUsername = await UsernameGetter().Connection(_CustEmailInit!);
    
    // @ Get item names based on the user username (_custUsername) and add them into
    // _fileNameStores
    var namesValues = await NameGetter().Connection(custUsername);

    setState(() {
      _fileNameStores.addAll(namesValues);
      // @ Remove duplicates value and add them into main list (_fileValues)
      _fileValues.addAll(_fileNameStores.toSet());
    });
  }

Written this way, it is plainly clear that custUsername is assigned before the call to NameGetter().Connection(custUsername).

Be sure to read through Asynchronous programming: futures, async, await.

Chuck Batson
  • 2,165
  • 1
  • 17
  • 15