flutterdarthttpproviderstate-management

Flutter Bug Where I have to Click the Button Twice


I get this weird problem in Flutter. I'm using 'Provider' for state management, In the helper class everything is fine, I'm sending the request and I receive the response with no problems. The problem occurs in the button event handler. The first time I click the button literally nothing happens, when I click it the second time it talks to the API and gets the data. Another problem is: when I enter correct data and they are sent to the API, Immediately after that, I enter wrong credentials the app tells me that I've entered correct credentials as if I'm dealing with the same previous object. Here is my code for the front-end part:

class _LoginPageState extends State<LoginPage> {
  String? myError;
  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width;
    TextEditingController userNameController = TextEditingController();
    TextEditingController passwordController = TextEditingController();

    return Directionality(
      textDirection: TextDirection.rtl,
      child: Consumer<PicoProvider>(
        builder: (context, prov, child) => SafeArea(
          child: Scaffold(
            backgroundColor: CustomColors.scaffoldDark,
            body: Padding(
              padding: const EdgeInsets.all(100),
              child: SingleChildScrollView(
                child: Container(
                  width: screenWidth,
                  child: Form(
                    autovalidateMode: AutovalidateMode.onUserInteraction,
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        Container(
                          decoration: BoxDecoration(
                              color: Colors.black,
                              borderRadius: BorderRadius.circular(10),
                              boxShadow: [
                                BoxShadow(
                                  color: Colors.grey.withOpacity(0.5),
                                  spreadRadius: 5,
                                  blurRadius: 7,
                                  offset: const Offset(0, 3),
                                ),
                              ]),
                          child: Image.asset(
                            'assets/images/main_logo.jpeg',
                            height: 300,
                          ),
                        ),
                        const SizedBox(height: 20),
                        CTextField(
                            label: 'اسم المسخدم',
                            icon: Ionicons.person,
                            isObsecured: false,
                            controller: userNameController),
                        const SizedBox(height: 20),
                        CTextField(
                            label: 'كلمة المرور',
                            icon: Ionicons.lock_closed,
                            isObsecured: true,
                            controller: passwordController,
                            onSubmitted: () {
                              prov.websiteLogin(userNameController.text,
                                  passwordController.text);
                            }),
                        ElevatedButton(
                            onPressed: () {
                              prov.websiteLogin(userNameController.text,
                                  passwordController.text);

                              prov.errorText!.isNotEmpty
                                  ? giveMeDialog(
                                      context, 'hello', 'not found', 'error')
                                  : giveMeDialog(context, 'hello',
                                      'you are logged', 'success');
                            },
                            child: const Text('Click')),
                        const SizedBox(height: 20),
                        prov.isLoading
                            ? const CircularProgressIndicator()
                            : const SizedBox(),
                        Text('${prov.errorText}')
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  Future<void> giveMeDialog(
      BuildContext context, String title, String message, String icon) {
    return showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text(title),
          content: Directionality(
            textDirection: TextDirection.rtl,
            child: Column(
              children: [
                Text(
                  message,
                  style: const TextStyle(fontSize: 30),
                ),
                Lottie.asset('assets/animations/${icon}_animation.json'),
              ],
            ),
          ),
        );
      },
    );
  }
}

Here is my code for the back-end part:

class PicoProvider extends ChangeNotifier {
  String baseURL = 'http://111.11.11.111:1111';

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  User? _user = User();
  User? get user => _user;

  String? _errorText;
  String? get errorText => _errorText;

  Future<void> websiteLogin(String userName, String password) async {
    try {
      String endPoint =
          '$baseURL/PicoLogin?UserName=$userName&Password=$password';
      _isLoading = true;
      notifyListeners();

      var response = await get(Uri.parse(endPoint), headers: {
        "content-type": "application/json",
        "Access-Control-Allow-Origin": "*",
      });

      if (response.statusCode == 200) {
        _user = User.fromJson(jsonDecode(response.body));
        _errorText = '';
      } else {
        _errorText = 'User Not Found';
      }

      _isLoading = false;
      notifyListeners();
    } catch (e) {
      _errorText = e.toString();
      notifyListeners();
      print('Error: ${e.toString()}');
    }
  }
}

Solution

  • Your issue is here

                          ElevatedButton(
                            onPressed: () {
                              prov.websiteLogin(userNameController.text,
                                  passwordController.text);
    
                              prov.errorText!.isNotEmpty
                                  ? giveMeDialog(
                                      context, 'hello', 'not found', 'error')
                                  : giveMeDialog(context, 'hello',
                                      'you are logged', 'success');
                            },
    

    prov.websiteLogin(.. is an async function and the prov.errorText will be updated only after a while. So in your code, you are not awaiting for the websiteLogin to complete and trying to show dialog immediately. Hence it is always giving result from previous execution.

    Quick non recommended fix : add an await for websiteLogin in onPressed

                          ElevatedButton(
                            onPressed: () async {
                              await prov.websiteLogin(userNameController.text,
                                  passwordController.text);
    
                              prov.errorText!.isNotEmpty
                                  ? giveMeDialog(
    

    Proper solution will be to use a listener. You may use a field (enum) "status" which will give the states like loading, success, and failure, and listen for changes in status field and display a dialog accordingly. You can refer this to how to add listener https://stackoverflow.com/a/72168584/1582630