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?
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:
This option is what you use in your code. Check if the context is mounted or not.
Future<void> asyncFunction(BuildContext context) async {
// Implement await for asynchronous
await Future.delayed(const Duration(seconds: 2));
// -- Using conditional mounted example 1
//
// If the `context` is mounted then continue to use `context`
if (context.mounted) {
// Use `context` after await with check if it `mounted` or not
Navigator.pop(context);
}
// -- Using conditional mounted example 2
//
// If the `context` is not mounted then exit the function.
if (!context.mounted) return;
}
This option using globalkey to get where the current context is.
globals.dart
file in lib folder to store all global variable.final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
navigatorKey
in your main MaterialApp
widgetExample of main.dart
import 'package:clean_project/globals.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
navigatorKey: navigatorKey,
home: Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
);
}
}
navigatorKey
inside your Async Function withFuture<void> asyncFunction(BuildContext context) async {
// Implement await for asynchronous
await Future.delayed(const Duration(seconds: 2));
// Get the current context using `navigatorKey`.
//
// Remember `currentContext` is nullable, so you need to make sure that
// your `currentContext` is not null.
Navigator.pop(navigatorKey.currentContext!);
}
This option is very not recommended because it's ignore the rules which it can caused the problem that stated in the official documentation.
Future<void> asyncFunction(BuildContext context) async {
// Implement await for asynchronous
await Future.delayed(const Duration(seconds: 2));
// (!NOT RECOMMENDED, FOR INFORMATION ONLY)
// Use context in async function by ignoring rules. Just add
// `ignore: use_build_context_synchronously` as a comment above
// used context code.
//
// ignore: use_build_context_synchronously
Navigator.pop(context);
}