I am currently doing a mobile app development internship and working on a microfinance app. In my app, I am using Providers for state management.
I created a login function, but Flutter gave me the warning:
"Don’t use BuildContext in async gaps."
How can I resolve this issue and handle BuildContext correctly in asynchronous operations?
Thanks in advance for your help!
This is the method for loggin user in my provider
class UserProvider extends ChangeNotifier {
//Text Editing Controller
final TextEditingController _userNameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
//Getters For Text Eiting Controllers
TextEditingController get userNameController => _userNameController;
TextEditingController get passwordController => _passwordController;
final DBController _dbController = DBController();
final AuthController _authController = AuthController();
final ApiController _apiController = ApiController();
User? _loggedInUser;
bool _isLoading = false;
String? _errorMessage;
// Cached
List<String> _routesList = [];
List<BranchModel> _branchesList = [];
//Getter
List<String> get routesList => _routesList;
List<BranchModel> get branchesList => _branchesList;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
User? get loggedInUser => _loggedInUser;
//Method to Login
Future<bool> login({
required String? branch,
required BuildContext context,
}) async {
Logger().i("Login Method Called");
Logger().i(
"Username: ${userNameController.text}, Password: ${passwordController.text} Branch: $branch");
_isLoading = true;
_errorMessage = null;
notifyListeners();
// Show SpinKit loading dialog
CustomDialog.showLoadingDialog(context);
try {
final response = await _apiController.loginUser(
username: userNameController.text,
password: passwordController.text,
branch: branch);
if (response['USER_ST'] == "1") {
final List<dynamic> userDetails = response['USER_DET'];
final List<dynamic> companyDetails = response['company'];
if (userDetails.isNotEmpty && companyDetails.isNotEmpty) {
//Save User Data
for (var userJson in userDetails) {
User user = User.fromJson(
userJson, response['USER_ST'], companyDetails.first['code']);
await _dbController.insertUser(user);
}
//Save Company Data
for (var companyJson in companyDetails) {
await _dbController.insertCompany(companyJson);
}
// Save session data in SharedPreferences
final prefs = await SharedPreferences.getInstance();
prefs.setString('USER_CODE', userDetails.first['code']);
prefs.setString('USER_NAME', userDetails.first['user_name']);
prefs.setString('BRANCH', userDetails.first['branch']);
prefs.setString('IS_LOGIN', "1");
_isLoading = false;
notifyListeners();
return true;
} else {
_errorMessage = "No user or company data found.";
Logger().e(_errorMessage);
}
} else {
_errorMessage = "Invalid username or password.";
Logger().e(_errorMessage);
CustomDialog.toast(context, "Invalid username or password.",textColor: AppColors.background,backgroundColor: AppColors.warningsRed);
}
} catch (e) {
_errorMessage = "An error occurred: $e";
Logger().e(_errorMessage);
}
// Hide SpinKit loading dialog if login fails
CustomDialog.hideLoadingDialog(context);
_isLoading = false;
notifyListeners();
return false;
}
}
And this is the Method calling in my Login UI
CustomButton(
size: size,
buttonName: "Sign In",
icon: Icons.arrow_circle_right_outlined,
colors: const [
Color(0xFF007BFF),
Color(0xFF007BFF)
],
ontap: () {
if (_formKey.currentState!.validate()) {
// If all data are correct then save data to out variables
_formKey.currentState!.save();
// You can now call your provider or any API to submit the data
if (mounted) {
provider.login(
branch: branchCode,
context: context).then((success) {
if (success) {
// Navigate to the homepage if login is successful
CustomNavigator.goWithDisableBack(context, Homepage());
} else {
// Show an error message if login failed
Logger().i("Login failed");
}
},
);
}
}
},
),
Can anyone help me to solve this?
2
Answers
For your case here, I would not pass BuildContext in the login function. BuildContext is related to UI and its not a good pattern to pass it to your business logic part of your app, like you do in the login function.
My opinion here is that you need to show the loading dialog on the UI just before calling login, then login is called and should be awaited. After the login is done, navigate to home according to success value.
In general, just keep in mind that BuildContext should stay in the UI only.
As stated in the official document Don’t use BuildContext in async gaps rule is to inform developers that:
So to use
BuildContext
in your async function there’s some option:Option 1 – Check if its mounted or not
This option is what you use in your code. Check if the context is mounted or not.
Option 2 – Use GlobalKey
This option using globalkey to get where the current context is.
globals.dart
file in lib folder to store all global variable.navigatorKey
in your mainMaterialApp
widgetExample of
main.dart
navigatorKey
inside your Async Function withOption 3 – Ignore rules (!!!Not Recommended)
This option is very not recommended because it’s ignore the rules which it can caused the problem that stated in the official documentation.