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()}');
}
}
}
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