Flutter: How to synchronize synchron static method with asynchron non-static method? - flutter

When I start the app, it should check if it is possible to use biometric authentication (fingerprint/face id). I have a class that checks this and the login page need the result of it. I have the following code:
class LocalAuthenticationUtil with ChangeNotifier {
static LocalAuthentication _auth = LocalAuthentication();
static List<BiometricType> biometricTypes;
static bool haveBiometrics = true;
bool _biometricAuthenticated = true;
static LocalAuthenticationUtil _instance;
static LocalAuthenticationUtil getInstance() {
if (_instance == null) {
_instance = LocalAuthenticationUtil();
print("GetInstance CanCheckBiometrics before");
_instance._canCheckBiometrics();
print("GetInstance CanCheckBiometrics after");
if (haveBiometrics) {
_instance.addListener(() {
_instance.authenticate();
});
_instance.authenticate();
}
}
return _instance;
}
Future<void> _canCheckBiometrics() async {
print("CanCheckBiometrics before");
haveBiometrics = await _auth.canCheckBiometrics;
print("CanCheckBiometrics after");
if (haveBiometrics) {
biometricTypes = await _auth.getAvailableBiometrics();
}
}
set biometricAuthenticated(bool value) {
if (_biometricAuthenticated != value) {
_biometricAuthenticated = value;
notifyListeners();
}
}
When the code runs this is the result:
I/flutter (23495): GetInstance CanCheckBiometrics before
I/flutter (23495): CanCheckBiometrics before
I/flutter (23495): GetInstance CanCheckBiometrics after
I/flutter (23495): CanCheckBiometrics after
While the order that I want to happen is:
I/flutter (23495): GetInstance CanCheckBiometrics before
I/flutter (23495): CanCheckBiometrics before
I/flutter (23495): CanCheckBiometrics after
I/flutter (23495): GetInstance CanCheckBiometrics after

You're not awaiting _instance._canCheckBiometrics();
Dart executes synchonously until it hits an await, at which point the function immediately returns, but "remembers where it was", so it can continue where it left off when the awaited Future completes:
Here, when you call _instance._canCheckBiometrics(), it immediately runs the first print statement, then hits the await _auth.canCheckBiometrics and immediately returns a Future representing the result of _instance._canCheckBiometrics().
Simply replace _instance._canCheckBiometrics() with await _instance._canCheckBiometrics() and it should work.
BTW, you can create an analysis_options.yaml file to customise your linter warnings for your project. One in particular, called unawaited_futures warns you when you have a Future-returning function in an async context that doesn't have an await. This is usually an error, but you can suppress it manually if you're certain. This rule often helps catch bugs like this.
To use the linter, check out: https://dart.dev/guides/language/analysis-options#enabling-linter-rules

cameron1024 is right.
What you need to do is to create a StatefulWidget which will redirect the user once the check is completed.
class AuthWidget extends StatefulWidget {
AuthWidget({Key key}) : super(key: key);
#override
_AuthWidgetState createState() => _AuthWidgetState();
}
class _AuthWidgetState extends State<AuthWidget> {
#override
void initState() {
super.initState();
checkBiometrics(); // perform the check asynchronously and then use Navigator.of(context).push/replace
}
#override
Widget build(BuildContext context) {
// Display a loader while checking
return CircularProgressIndicator();
}
}

Related

state is always null in flutter_bloc

I am facing this weird issue, where though I have set the state, but the value when accessed in init state is showing null !!
user_cubit.dart
UserCubit() : super(UserInitialState()) {
emit(UserMainLoadingState());
_firestore
.collection("users")
.doc(_currentUser?.uid)
.snapshots()
.listen((event) {
event.exists
? {
emit(UserExists(
userModel: UserModel.fromjson(event.data()!, event.id)))
}
: {print("There is no such user"), emit(UserNotExists())};
});
}
user_state.dart
class UserState extends Equatable {
final UserModel? userModel;
const UserState({this.userModel});
#override
List<Object?> get props => [userModel];
}
class UserExists extends UserState {
UserExists({required UserModel userModel}) : super(userModel: userModel) { // Here the state is received
print("I am inside the UserExists state and the user is :$userModel");
}
}
myWidget.dart
#override
void initState() {
_userState = const UserState();
print("I am inside the initState The value of userstate is ${_userState.userModel}"); // This prints null , though i have set the state and console logs the state and it is confirmed that state exists why isn't this not null
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
super.initState();
}
Console log:
I/flutter ( 5029): I am inside the UserExists state and the user is :avatar boy
I/flutter ( 5029): fullName krrrt
I/flutter ( 5029): dob 19/1/2022
I/flutter ( 5029): email rd#xddf.co
I/flutter ( 5029): phone 12222222255
I/flutter ( 5029): I am inside the initState The value of userstate is null
Though the userState's userModel has value, why can't i access that in the `initState.
My tries :
I have used BlocListener, still the state remains null, I'm hell confused.
body: BlocListener<UserCubit, UserState>(
listener: (context, state) {
print("I am inside the listener"); // this line is never printed
if (state.userModel != null) {
print("State is ${state.runtimeType}"); // But if the state has some value, why isn't this being printed !!!??
}
},
child: <My widget>
It seems that the issue is that you are initializing the _userState variable in the initState method with const UserState(), which sets the userModel value to null. This is happening before the UserExists state is emitted from the UserCubit, which sets the userModel value to a valid UserModel object.
You should instead initialize _userState with the current state of the UserCubit by calling the state getter.
#override
void initState() {
_userCubit = context.bloc<UserCubit>();
_userState = _userCubit.state;
print("I am inside the initState The value of userstate is ${_userState.userModel}");
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
super.initState();
}
It would also be best practice to unsubscribe the listener after the widget is disposed, in the dispose method of your stateful widget.
#override
void dispose() {
_userCubit.close();
super.dispose();
}
Note that the above code assumes that you are using the bloc package to manage the state of your app.

How to access the state outside BlocConsumer and BlocCubit?

I am facing this weird issue, where though I have set the state, but the value when accessed in init state is showing null !!
user_cubit.dart
UserCubit() : super(UserInitialState()) {
emit(UserMainLoadingState());
_firestore
.collection("users")
.doc(_currentUser?.uid)
.snapshots()
.listen((event) {
event.exists
? {
emit(UserExists(
userModel: UserModel.fromjson(event.data()!, event.id)))
}
: {print("There is no such user"), emit(UserNotExists())};
});
}
user_state.dart
class UserState extends Equatable {
final UserModel? userModel;
const UserState({this.userModel});
#override
List<Object?> get props => [userModel];
}
class UserExists extends UserState {
UserExists({required UserModel userModel}) : super(userModel: userModel) {
print("I am inside the UserExists state and the user is :$userModel");
}
}
myWidget.dart
#override
void initState() {
_userState = const UserState();
print("I am inside the initState The value of userstate is ${_userState.userModel}"); // This prints null , but why ?
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
super.initState();
}
Console log:
I/flutter ( 5029): I am inside the UserExists state and the user is :avatar boy
I/flutter ( 5029): fullName krrrt
I/flutter ( 5029): dob 19/1/2022
I/flutter ( 5029): email rd#xddf.co
I/flutter ( 5029): phone 12222222255
I/flutter ( 5029): I am inside the initState The value of userstate is null
Though the userState's userModel has value, why can't i access that in the initState.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
To access the state of a BLoC or Cubit in Flutter, you can use the context.read<Bloc>(). This method returns a Bloc instance that you can use to access the current state.
initState() {
super.initState();
_userState = const context.read<UserCubit>().state();
print("I am inside the initState The value of userstate is ${_userState.userModel}"); // This prints null , but why ?
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
}

Getting the error, "Internal error: The Interpreter has already been closed." after calling dispose() to free up memory

I'm using flutter platform channels to use tflite in java. The interpreters are memory heavy so when I exit the current screen/page I dispose the interpreters, but when I navigate back to the page I get the error:
E/flutter ( 7960): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)]
Unhandled Exception: PlatformException(error, Internal error: The
Interpreter has already been closed., null,
java.lang.IllegalStateException: Internal error: The Interpreter has
already been closed.
Here is a minimal example of what I am doing in flutter
class StyleTransfer extends StatefulWidget {
final selectedImage;
const StyleTransfer({
Key key,
this.selectedImage,
}) : super(key: key);
#override
_StyleTransferState createState() => _StyleTransferState();
}
class _StyleTransferState extends State<StyleTransfer> {
static const platform = const MethodChannel("com.name.package/tflite_java");
#override
Future<void> dispose() async{
Future.delayed(Duration.zero, () async{
await platform.invokeMethod("DisposeInterpreter").then((value) => print(value));
});
super.dispose();
}
#override
Widget build(BuildContext context) {
...
...
}
}
and this is the dispose function in java
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.name.package/tflite_java";
protected Interpreter predictInterpreter;
protected Interpreter transformInterpreter;
protected Interpreter.Options interpreterOptions = new Interpreter.Options();
private ImageSegmenter imageSegmenter;
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
try {
interpreterOptions.setNumThreads(4);
predictInterpreter = new Interpreter(loadModelFile("prediction_1.tflite"), interpreterOptions);
transformInterpreter = new Interpreter(loadModelFile("transfer_1.tflite"), interpreterOptions);
} catch (Exception e) {
e.printStackTrace();
}
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL).setMethodCallHandler((call, result) -> {
if (call.method.equals("StylePredict")){
// irrelevant code removed for minimal example;
}
else if (call.method.equals("DisposeInterpreter")){
String response = DisposeInterpreter();
result.success(response);
}
// Note: this method is invoked on the main thread.
});
}
private String DisposeInterpreter() {
predictInterpreter.close();
transformInterpreter.close();
return "All Models closed";
}
}
When I press back from the StyleTransfer page, I get the All Models closed message, so that is working, but when I go back to this page with a new image I get the above error:
E/MethodChannel#com.name.package/tflite_java( 7960): Failed to handle
method call E/MethodChannel#com.name.package/tflite_java( 7960):
java.lang.IllegalStateException: Internal error: The Interpreter has
already been closed.

Flutter: Unhandled Exception: 'package:provider/src/provider.dart': Failed assertion: line 240 pos 12: 'context != null': is not true

I get the exception above when I navigate from this page (shoe_box_page.dart) to another page and the content from this page (shoe_box_page.dart) is not completely loaded yet.
The error message I get
class ShoeBoxPage extends StatefulWidget {
#override
_ShoeBoxPageState createState() => _ShoeBoxPageState();
}
class _ShoeBoxPageState extends State<ShoeBoxPage> {
final _scrollController = ScrollController();
#override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 1)).then((_) {
_checkRequestLoad();
});
}
bool get _canScroll {
if (!_scrollController.hasClients) return false;
final x = _scrollController.position.maxScrollExtent;
final deviceHeight = MediaQuery.of(context).size.height;
return x - _progressIndicatorHeight > deviceHeight;
}
void _checkRequestLoad() {
final bloc = context.bloc<ShoeBoxBloc>();
if (bloc.state.billsAvailable && !_canScroll) {
context
.bloc<ShoeBoxBloc>()
.add(ShoeBoxEvent.scrollingOverUnloadedScope());
Future.delayed(Duration(seconds: 1)).then((_) {
_checkRequestLoad();
});
}
}
#override
Widget build(BuildContext context) {
return BlocBuilder<ShoeBoxBloc, ShoeBoxState>(
...
I hope someone of you can help me :)
Best,
Alex
I think the problem is: context inside the function is null.
So you either need to define those functions inside the build method where you can get the context, or pass the context as the function argument while calling those functions.
When the future in _checkRequestLoad completes, the state might not be used anymore. So before doing something after an asynchronous gap, you should check to see if the element (that the state belongs to) is still mounted:
void _checkRequestLoad() {
final bloc = context.bloc<ShoeBoxBloc>();
if (bloc.state.billsAvailable && !_canScroll) {
context
.bloc<ShoeBoxBloc>()
.add(ShoeBoxEvent.scrollingOverUnloadedScope());
Future.delayed(Duration(seconds: 1)).then((_) {
if (mounted) // <---
_checkRequestLoad();
});
}
}
I'm not sure this is causing the exception you see, but it is a bug.

How to catch error in ChangeNotifier after widget deactivated?

I have code in Model for execution. I provide Model with Provider. But if Model is dispose before finish execution I get error:
E/flutter (26180): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)]
Unhandled Exception: A Model was used after being disposed. E/flutter
(26180): Once you have called dispose() on a Model, it can no longer
be used.
For example Model is dispose if user press back button so Navigator.pop(). This because Model is only scope to this Widget.
But that mean I cannot catch error in Model?
My code:
class Model extends ChangeNotifier {
bool error = false;
func() {
try {
await execute();
error = false
} catch {
error = true;
print(e.toString());
}
}
}
class ExampleWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
builder: (context) => Model(),
child: Consumer<Model>(builder: (context, model, _) {
return FloatingActionButton(
child: model.error ? Icon(Icons.error) : Icon(Icons.check),
onPressed: () {
model.func();
}
);
…
How I can catch error in Model after dispose?
I just had the same problem.
The error occurs because you use one of the ChangeNotifier methods, usually notifyListeners() (which I'm assuming you're calling, but left out of the pasted code) after dispose() has been called. By the way, it's an assert error, so only in debug builds.
To get rid of the error, you could check if the object has been disposed before calling notifyListeners() with your own flag:
class Model extends ChangeNotifier {
bool error = false;
bool isDisposed = false;
func() {
try {
await execute();
error = false
} catch {
error = true;
print(e.toString());
}
if (!isDisposed) {
notifyListeners();
}
}
#override
void dispose() {
isDisposed = true;
super.dispose();
}
}