How Can I get a page transition programmatically after getting biometric authentication result true without any button press event in advance? - flutter

I am creating an app in flutter implemented local_auth plugin.
I would like this flow below.
1.background page(BioAuthScreen) is up
2.simultaneously authenticateWithBiometrics dialog is up.
3.if result is true, get the page transition to HomeScreen(folloing comment // 3),
if I pressed the button (the code following comment // 4),
It's work.(up the dialog, if the result is true, the page transition OK).
But I wouldn't like button in background page.
if I put the code following comment // 1 or // 2, the dialog is up, if the result is true,
but background page still there. there's no error.
This is the code.
class BioAuthScreen extends StatefulWidget {
BioAuthScreen({Key key, this.initFlag = false}) : super(key: key);
final bool initFlag;
#override
_BioAuthScreenState createState() => _BioAuthScreenState();
}
class _BioAuthScreenState extends State<BioAuthScreen>
with WidgetsBindingObserver {
final LocalAuthentication _localAuthentication = LocalAuthentication();
bool _isAuthenticated;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// 1
/* WidgetsBinding.instance.addPostFrameCallback((_) async {
await _getAuthenticated();
}); */
}
#override
void didChangeDependencies() async {
super.didChangeDependencies();
await Future.delayed(const Duration(milliseconds: 700));
// 2
// await _getAuthenticated();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.inactive) {
} else if (state == AppLifecycleState.paused) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => BioAuthScreen()));
} else if (state == AppLifecycleState.resumed) {}
}
Future<List<BiometricType>> _getListOfBiometricTypes() async {
List<BiometricType> listOfBiometrics;
try {
listOfBiometrics = await _localAuthentication.getAvailableBiometrics();
} on PlatformException catch (e) {
print(e);
}
print(listOfBiometrics);
return listOfBiometrics;
}
Future<void> _getAuthenticated() async {
var result = false;
var availableBiometricTypes = await _getListOfBiometricTypes();
try {
if (availableBiometricTypes.contains(BiometricType.face) ||
availableBiometricTypes.contains(BiometricType.fingerprint)) {
result = await _localAuthentication.authenticateWithBiometrics(
localizedReason: '生体認証',
useErrorDialogs: true,
stickyAuth: false,
);
}
} on PlatformException catch (e) {
print(e);
}
if (!mounted) return;
print('result: $result');
// 3
if (result) {
await Navigator.pushNamed(context, Constants.homeScreenRoute);
}
}
#override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
return SafeArea(
child: Scaffold(
body: FractionallySizedBox(
widthFactor: 1,
heightFactor: 1,
child: DecoratedBox(
decoration: const BoxDecoration(
color: CustomColors.midBlue,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
const Padding(
padding: EdgeInsets.all(40.0),
),
// Title
AuthTitleComponent(),
SizedBox(height: screenHeight * 0.28),
ButtonTheme(
minWidth:
screenWidth > 600 ? screenWidth * 0.6 : screenWidth * 0.7,
height: 45,
child: RaisedButton(
elevation: 3.0,
color: CustomColors.lightBlue,
// 4
onPressed: () async {
await _getAuthenticated();
},
child: Text(
'BioMetricAuthLogin',
style: Theme.of(context)
.textTheme
.headline2
.copyWith(color: Colors.white),
),
),
),
],
),
),
),
),
);
}
}

I was able to replicate the reported behavior using the code snippets you've provided. LocalAuthentication.authenticate catches PlatformException when biometric auth is called through didChangeAppLifecycleState.
This seems to be a bug and I've filed this as an issue here. I'm using Flutter version 2.0.0 stable and local_auth 1.1.0 for reference.

Related

Using a Stack containing a QR code scanner & a CameraPreview leads to preview freezing

I initially tried to find a library that would allow for a single camera component that can do both, qr code scanning & allowing the user to take a normal picture with it. I also came across this post that went unanswered (if you have any suggestions, feel free to comment them below), so I decided to use two different components in a stack and play with their visibility.
I'm using the mobile_scanner library for the QR code scanner and the official docs for the picture taking functionality.
The idea (until a widget that can do both shows up) is that initially, the user will see the CameraPreview() widget, and when toggling the switch to the ON state, they will see the MobileScanner() one. The issue that occurs, is that although the MobileScanner widget seems to handle its visibility changes normally (meaning, pausing & resuming the camera), the CameraPreview one cannot. If I toggle the switch on and then toggle it off again, the camera preview is frozen. I'm having a hard time understanding whether I should be manually calling _controller.resumePreview() and at what point exactly.
Here's the screen code:
class CameraComponent extends StatefulWidget {
const CameraComponent({Key? key}) : super(key: key);
#override
State<CameraComponent> createState() => _CameraComponentState();
}
class _CameraComponentState extends State<CameraComponent> {
CameraCubit get _cubit => context.read<CameraCubit>();
bool qrModeEnabled = false;
CameraController? _controller;
late Future<void> _initializeControllerFuture;
Photo? _artwork;
final ArtworkRepository _artworkRepository = getIt<ArtworkRepository>();
#override
void initState() {
super.initState();
_cubit.init();
}
#override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<CameraCubit, CameraState>(
listener: (BuildContext context, CameraState state) {
state.maybeWhen(
initCamera: (List<CameraDescription> cameras) {
_controller = CameraController(
cameras.firstWhere(
(CameraDescription camera) =>
camera.lensDirection == CameraLensDirection.back,
orElse: () => cameras.first,
),
ResolutionPreset.max,
);
_initializeControllerFuture = _controller!.initialize();
},
orElse: () {},
);
},
builder: (BuildContext context, CameraState state) {
return state.maybeWhen(
initCamera: (_) {
return FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Stack(
children: <Widget>[
Visibility(
visible: qrModeEnabled,
child: MobileScanner(
onDetect: (Barcode barcode,
MobileScannerArguments? args) async {
if (barcode.rawValue == null) {
debugPrint('Failed to scan qr code');
} else {
try {
final int artworkId = int.parse(
barcode.rawValue!.split('_')[1]);
debugPrint(
'QR code found! ${barcode.rawValue!}');
_artwork = _artworkRepository.savedArtworks
.firstWhereOrNull(
(Photo element) => element.id == artworkId,
);
_artwork ??= await _artworkRepository
.getArtworkDetails(id: artworkId);
NavigatorUtils.goToArtworkViewScreen(
context,
artwork: _artwork!,
);
} catch (e) {
rethrow;
}
}
},
),
),
Visibility(
visible: !qrModeEnabled,
child: CameraPreview(_controller!),
),
Align(
child: Image.asset('assets/icons/b_logo_grey.png'),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 144),
child: Text(
'Scan a QR code or take a photo of the artwork',
style: montserratRegular15.copyWith(
color: AppColors.white,
),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 80),
child: GestureDetector(
onTap: () async {
final XFile image =
await _controller!.takePicture();
final Uint8List bytes =
await image.readAsBytes();
final String base64Image = base64Encode(bytes);
_cubit.addBase64Image(base64Image: base64Image);
},
child: Image.asset(
'assets/icons/shutter_icon.png',
height: 48,
),
),
),
),
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(top: 10, right: 10),
child: Switch(
value: qrModeEnabled,
onChanged: (bool value) {
setState(() {
qrModeEnabled = !qrModeEnabled;
});
},
activeTrackColor: AppColors.red,
inactiveTrackColor: AppColors.white,
),
),
),
],
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
},
orElse: () => const SizedBox.shrink(),
);
},
),
);
}
}
and the CameraCubit.dart file:
class CameraCubit extends Cubit<CameraState> {
CameraCubit({
required ClarifaiRepository clarifaiRepository,
}) : _clarifaiRepository = clarifaiRepository,
super(const CameraState.initial());
final ClarifaiRepository _clarifaiRepository;
Future<void> init() async {
try {
final List<CameraDescription> cameras = await availableCameras();
if (cameras.isNotEmpty) {
emit(CameraState.initCamera(cameras: cameras));
}
} catch (e) {
emit(CameraState.error(e));
}
}
Future<void> addBase64Image({required String base64Image}) async {
emit(const CameraState.loading());
try {
await _clarifaiRepository.addBase64Image(base64Image: base64Image);
emit(const CameraState.success());
} catch (e) {
emit(CameraState.error(e));
}
}
}
I haven't used the Mobile Scanner library before. Instead, I have used the qr_code_scanner dependency for QR codes and it has a reassemble method like this:
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
} else if (Platform.isIOS) {
controller!.resumeCamera();
}
}
If this doesn't help, you can also manually pause/resume when the button is pressed with the method you mentioned in the end.

flutter - wait for camera access granted

I have a flutter app that opens the camera preview with a FutureBuilder. When the app opens for the first time, the app asks for permission. (This is triggered automatically). When I press ok I get a red screen:
I need to wait for the response, and why is dispose called?
I checked this: https://pub.dev/packages/permission_handler
and tested this:
if (await Permission.contacts.request().isGranted) {
// Either the permission was already granted before or the user just granted it.
}
But I got false and was never asked to approve anything. In contrast to the description.
Any ideas?
Here is the code:
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:name/Model/FileIO/file_io.dart';
import 'package:name/Views/main_view.dart';
import 'package:name/main.dart';
class CameraView extends StatefulWidget {
const CameraView({Key? key}) : super(key: key);
#override
State<CameraView> createState() {
return _CameraViewState();
}
}
class _CameraViewState extends State<CameraView> with WidgetsBindingObserver {
CameraController? controller;
late Future<void> _initializeController;
List<CameraDescription> rearCameras = List.empty(growable: true);
int _currentCameraIndex = 0;
#override
void initState() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
DeviceOrientation.portraitUp
]);
_ambiguate(WidgetsBinding.instance)?.addObserver(this);
for (var description in cameras) {
if (description.lensDirection == CameraLensDirection.back) {
rearCameras.add(description);
}
}
onNewCameraSelected(rearCameras[_currentCameraIndex]);
super.initState();
}
#override
void dispose() {
_ambiguate(WidgetsBinding.instance)?.removeObserver(this);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
final CameraController? cameraController = controller;
if (cameraController == null || cameraController.value.isInitialized) {
return;
}
if (state == AppLifecycleState.inactive) {
cameraController.dispose();
}
if (state == AppLifecycleState.resumed) {
onNewCameraSelected(rearCameras[_currentCameraIndex]);
}
}
Future<void> onNewCameraSelected(CameraDescription description) async {
if (controller != null) {
await controller!.dispose();
}
final CameraController cameraController = CameraController(
description,
ResolutionPreset.max,
enableAudio: false,
imageFormatGroup: ImageFormatGroup.jpeg,
);
controller = cameraController;
cameraController.addListener(() {
if (mounted) {
setState(() {});
}
if (cameraController.value.hasError) {
showInSnackbar(
"Camera error: ${cameraController.value.errorDescription}");
}
});
try {
_initializeController = cameraController.initialize();
} on CameraException catch (e) {
print('Error initializing camera: $e');
}
if (mounted) {
setState(() {});
}
}
Future<void> _onCameraSwitch() async {
if (controller == null) {
return;
}
_currentCameraIndex = (_currentCameraIndex + 1) % rearCameras.length;
final CameraDescription cameraDescription =
rearCameras[_currentCameraIndex];
onNewCameraSelected(cameraDescription);
}
void showInSnackbar(String message) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message)));
}
bool isRecording() {
return controller != null && controller!.value.isRecordingVideo;
}
Widget _cameraPreviewWidget(DeviceOrientation orientation) {
if (controller != null) {
final size = MediaQuery.of(context).size;
final deviceRatio = size.width / size.height;
controller!.lockCaptureOrientation(orientation);
return Center(
child: AspectRatio(
aspectRatio: deviceRatio,
child: CameraPreview(controller!),
),
);
}
return const Text("Camera not ready or available");
}
Widget _captureRowWidget() {
final CameraController? cameraController = controller;
return Container(
color: const Color.fromARGB(50, 255, 255, 255),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.videocam),
color: Colors.blue,
onPressed: cameraController != null &&
cameraController.value.isInitialized &&
!cameraController.value.isRecordingVideo
? onVideoRecordButtonPressed
: null,
),
IconButton(
onPressed: isRecording() ? onStopRecordingButtonPressed : null,
icon: const Icon(Icons.stop),
),
TextButton(
onPressed: _onCameraSwitch, child: const Text("Switch Camera"))
],
),
);
}
void onVideoRecordButtonPressed() {
startVideoRecording().then((_) => {
if (mounted) {setState(() {})}
});
}
void onStopRecordingButtonPressed() {
stopVideoRecording().then((video) async {
if (video != null) {
final path = await localPath;
final file = File('$path/video/${timestamp() + ".mp4"}');
await file.create(recursive: true);
video.saveTo(file.path);
}
});
}
Future<void> startVideoRecording() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
showInSnackbar("Error: Camera unavailable");
return;
}
if (cameraController.value.isRecordingVideo) {
return;
}
try {
await cameraController.startVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return;
}
}
Future<XFile?> stopVideoRecording() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return null;
}
try {
return cameraController.stopVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
OrientationBuilder getBody() {
return OrientationBuilder(
builder: (context, orientation) {
var deviceOrientation = DeviceOrientation.landscapeLeft;
if (orientation == Orientation.portrait) {
deviceOrientation = DeviceOrientation.portraitUp;
}
return Stack(
children: [
_cameraPreviewWidget(deviceOrientation),
Positioned(
bottom: 2,
left: 2,
right: 2,
child: _captureRowWidget(),
),
],
);
},
);
}
void _showCameraException(CameraException e) {
_logError(e.code, e.description);
showInSnackbar('Error: ${e.code}\n${e.description}');
}
void _navigateBack() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) {
return const MainView();
},
),
(Route<dynamic> route) => false,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Video Recorder"),
leading: GestureDetector(
onTap: () {
_navigateBack();
},
child: const Icon(
Icons.arrow_back, // add custom icons also
),
),
),
body: SafeArea(
child: FutureBuilder<void>(
future: _initializeController,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return getBody();
} else {
return const Center(child: CircularProgressIndicator());
}
}),
),
);
}
T? _ambiguate<T>(T? value) => value;
void _logError(String code, String? message) {
if (message != null) {
print('Error: $code\nError Message: $message');
} else {
print('Error: $code');
}
}
String timestamp() {
return DateTime.now().microsecondsSinceEpoch.toString();
}
}
I do double check after grant permission, and if it still not granted it will show a dialog to open setting menu
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:permission_handler/permission_handler.dart';
enum CaptureMode { IDENTITY, SELFIE }
class CameraCapture extends StatefulWidget {
Function onCaptured;
CaptureMode captureMode;
CameraCapture({required this.onCaptured, required this.captureMode});
#override
State<CameraCapture> createState() => _CameraCaptureState();
}
class _CameraCaptureState extends State<CameraCapture> {
List<CameraDescription>? cameras;
CameraController? cameraController;
bool isPopupPermissionShow = false;
_openSettingDialog(BuildContext context) => AlertDialog(
title: const Text("Camera permission not granted"),
content: SingleChildScrollView(
child: ListBody(
children: [
GestureDetector(
child: const Text("Open Setting"),
onTap: () async {
Navigator.pop(context, null);
cameraController?.dispose();
await openAppSettings();
setState(() {});
},
),
const Padding(padding: EdgeInsets.all(10)),
GestureDetector(
child: const Text("Cancel"),
onTap: () async {
Navigator.pop(context, null);
return;
},
),
],
),
),
);
checkPermission(BuildContext context) async {
try {
bool isCameraGranted = await Permission.camera.request().isGranted;
if (!isCameraGranted) {
if (!isPopupPermissionShow) {
isPopupPermissionShow = true;
await showDialog(
context: context,
builder: (BuildContext context) {
return _openSettingDialog(context);
});
}
isPopupPermissionShow = false;
}
} catch (e) {
debugPrint("camera error: " + e.toString());
}
}
Future<bool> setupCamera(BuildContext context) async {
if (cameras == null) {
cameras = await availableCameras();
}
if (cameraController == null || !(cameraController!.value.isInitialized)) {
if (cameras != null && cameras!.length > 0) {
cameraController = CameraController(
(widget.captureMode == CaptureMode.IDENTITY)
? cameras!.first
: cameras![1],
ResolutionPreset.medium);
try {
await cameraController?.initialize();
} catch (e) {
bool isCameraGranted = await Permission.camera.request().isGranted;
if (!isCameraGranted) {
checkPermission(context);
} else {
debugPrint("camera error " + (e.toString()));
}
}
} else {
bool isCameraGranted = await Permission.camera.request().isGranted;
if (!isCameraGranted) {
checkPermission(context);
} else {
debugPrint("camera not available");
}
}
}
return false;
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: null,
child: Scaffold(
backgroundColor: Colors.black,
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.close, color: Colors.black),
onPressed: () {
cameraController?.dispose();
widget.onCaptured("cancel");
}),
),
body: Stack(
children: [
FutureBuilder(
future: setupCamera(context),
builder: (_, snapshot) {
return (snapshot.connectionState == ConnectionState.done)
? CameraPreview(
cameraController!,
)
: Container(
// child: LoadingProgress(),
width: double.infinity,
height: double.infinity,
color: Colors.black,
);
}),
Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
//put camera mask here
children: [
Container(
padding: EdgeInsets.only(
top: 16, left: 32, right: 32, bottom: 32),
child: _shutterButton(() async {
final image = await cameraController?.takePicture();
String imgPath = (image?.path ?? "");
cameraController?.dispose();
}),
),
],
),
),
],
),
),
);
}
_shutterButton(Function onShot) => Stack(
alignment: Alignment.center,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40), color: Colors.white),
),
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40), color: Colors.black),
),
InkWell(
onTap: () {
onShot();
},
borderRadius: BorderRadius.circular(40),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40), color: Colors.white),
),
),
],
);
}
I found the error, it was an incorrect if-statement.
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) <-- the ! is missing in the OP {
return;
}
if (state == AppLifecycleState.inactive) {
cameraController.dispose();
}
if (state == AppLifecycleState.resumed) {
onNewCameraSelected(rearCameras[_currentCameraIndex]);
}
}

stop closing showDialogue itself after 15 seconds

I am trying to display an information dialog when starting an application. After closing, another window appears asking for permission. I call it all in the initState function. It works, but I noticed that this first info dialog also closes on its own when 15 seconds have elapsed. How do I fix this? So that while the dialog is not closed by the user, the application will not be loaded further?
class _MyAppState extends State<MyApp> {
final keyIsFirstLoaded = 'is_first_loaded';
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final context = MyApp.navKey.currentState.overlay.context;
await showDialogIfFirstLoaded(context);
await initPlatformState();
});
}
showDialogIfFirstLoaded(BuildContext context, prefs) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstLoaded = prefs.getBool(keyIsFirstLoaded);
if (isFirstLoaded == null) {
return showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return new AlertDialog(
// title: new Text("title"),
content: new Text("//"),
actions: <Widget>[
new FlatButton(
child: new Text(".."),
onPressed: () {
Navigator.of(context).pop();
prefs.setBool(keyIsFirstLoaded, false);
},
),
],
);
},
);
}
}
initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
print('Initialization done');
final _isRunning = await BackgroundLocator.isRegisterLocationUpdate();
setState(() {
isRunning = _isRunning;
});
onStart();
print('Running ${isRunning.toString()}');
}
#override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
// ... app-specific localization delegate[s] here
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
navigatorKey:MyApp.navKey,
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics),
],
debugShowCheckedModeBanner: false,
title: '',
theme: ThemeData(),
home: new SplashScreen(),}
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => new _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
Timer _timer;
bool _visible = true;
startTime() async {
_timer = Timer(new Duration(seconds: 5), navigationPage);
}
void navigationPage() {
Navigator.of(context).pushReplacementNamed('/home');
}
#override
void initState() {
_timer = Timer(Duration(seconds: 4),
() => setState(
() {
_visible = !_visible;
},
),
);
startTime();
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
Container(
width: double.infinity,
child: Image.asset('images/bg.jpg',
fit: BoxFit.cover,
height: 1200,
),
),
Container(
width: double.infinity,
height: 1200,
color: Color.fromRGBO(0, 0, 0, 0.8),
),
Container(
alignment: Alignment.center,
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
child: Text(''),
),
),
],
),
),
],
);
}
}
This code displays an Alert dialogue if the user is new and after the button click, it will direct him to another dialogue.
I have tested the code and it doesn't close after 15 seconds. I'm still not sure what you're trying to accomplish but I hope this helps.
Init State
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await dialog1(context);
//await initPlatformState();
});
super.initState();
}
Alert Dialog 1
dialog1(BuildContext context)async{
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstLoaded = prefs.getBool("keyIsFirstLoaded")??true;
if (isFirstLoaded) {
showDialog(
barrierDismissible: false, //disables user from dismissing the dialog by clicking out of the dialog
context: context, builder: (ctx) {
return AlertDialog(
title: Text("dialog 1"), content: Text("Content"), actions: [
TextButton(
child: new Text(".."),
onPressed: () async{
Navigator.pop(ctx);
await dialog2(context);
prefs.setBool("keyIsFirstLoaded", false);
},
),],);
},);
}else{
//not first time
}
}
Alert Dialog 2
void dialog2(BuildContext context)async{
print("dialog 2");
showDialog(context: context, builder: (context) {
return AlertDialog(title: Text("Dialog 2"),content: Text("permissions"),actions: [
TextButton(
child: new Text("close"),
onPressed: () async{
Navigator.pop(context);
//await dialog1(context); //uncomment if you want to go back to dialoge 1
},
),],);
},);
}
You can return a value from Navigator in the first dialog
Navigator.of(context).pop(true);
prefs.setBool(keyIsFirstLoaded, false);
Once it receive true, then only call the second method.
var value = await showDialogIfFirstLoaded(context);
if(value == true) {
await initPlatformState();
}

Flutter I am getting a was used after disposed error when using the Chewie Video package

I am using the Chewie package to display videos: https://pub.dev/packages/chewie
All works pretty well. However when I am trying to dispose the controllers when someone backs out of the video session I am getting errors.
First here is the code I am using, its controlled by a flatbutton with a back arrow on it. Idea is to close and dispose the video screen.
void userExited() async {
_chewieController.dispose();
//dispose();
print('USER EXITED!!');
Navigator.pop(context);
}
However whenever I am pressing the button, I am getting a:
A ChewieController was used after being disposed.
Error message. I should point out that at no other point in my code am I calling dispose().
My question is what do i need to do to remove this error. But furthermore how to correctly dispose of the chewie video controller and close the screen without errors.
Here's the whole code:
class VideoScreen extends StatefulWidget {
final MediaItem mediaItem;
VideoScreen(this.mediaItem);
#override
_VideoScreenState createState() => _VideoScreenState();
}
class _VideoScreenState extends State<VideoScreen> {
ChewieController _chewieController;
VideoPlayerController _videoPlayerController;
void initState() {
super.initState();
_videoPlayerController = VideoPlayerController.network(
widget.mediaItem.itemUrl,
);
// Wrapper on top of the videoPlayerController
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
//aspectRatio: 16 / 9,
aspectRatio: _videoPlayerController.value.aspectRatio,
showControlsOnInitialize: false,
fullScreenByDefault: false,
// Prepare the video to be played and display the first frame
autoInitialize: true,
looping: false,
autoPlay: true,
// Errors can occur for example when trying to play a video
// from a non-existent URL
errorBuilder: (context, errorMessage) {
return Center(
child: Text(
errorMessage,
style: TextStyle(color: Colors.white),
),
);
},
);
_videoPlayerController.addListener(() {
if (_videoPlayerController.value.position ==
_videoPlayerController.value.duration) {
print('video Ended');
closeScreen();
Navigator.pop(context);
}
});
}
void closeScreen() async {
final _media = Provider.of<Media>(context, listen: false);
_media.increasePlayCount(widget.mediaItem.id);
final _auth = Provider.of<Auth>(context, listen: false);
_media.createMediaHistory(_auth.user.uid, widget.mediaItem.id);
dispose();
print('VIDEO FINISHED ... CLOSE!!');
Navigator.pop(context);
}
void userExited() async {
_chewieController.dispose();
//dispose();
print('USER EXITED!!');
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Chewie(
controller: _chewieController,
),
),
Positioned(
left: screenWidth / 20,
top: screenWidth / 20,
child: FloatingActionButton(
onPressed: () {
userExited();
},
elevation: 0.0,
child: Icon(Icons.arrow_back_ios),
// backgroundColor: Color(0xFF1976D2),
backgroundColor: Colors.blue,
),
),
],
),
),
);
}
}
Any advice on this would be really appreciated
Thanks so much
dispose the controllers
#override
void dispose() {
_chewieController.dispose();
_videoPlayerController.dispose();
super.dispose();
}
Try the following code:
void userExited() async {
_chewieController?.dispose();
//dispose();
print('USER EXITED!!');
Navigator.pop(context);
}
or more proper way is writing dispose():
#override
void dispose() {
_chewieController?.dispose();
super.dispose();
}

Flutter - The getter 'value' was called on null

I'm trying to save to SharedPreferences the current camera selected. When the app restarts, I would like the same camera to be used initially. If the user toggles to another camera, that camera gets saved.
When I have the following code in the initState, I get the "The getter 'value' was called on null." error message. Also the red/yellow error messages show up and disappear quickly before the camera starts working.
#override
void initState() {
try {
SharedPreferencesHelper prefs = SharedPreferencesHelper();
prefs.getCameraSelected().then((String answer) {
if (answer != null) {
if (answer == 'back') {
onCameraSelected(widget.cameras[0]);
} else {
onCameraSelected(widget.cameras[1]);
}
} else {
print('answer is null');
onCameraSelected(widget.cameras[1]);
}
});
} catch (e) {
print(e.toString());
}
super.initState();
}
If I replace the initState back to the original state, everything works as expected
#override
void initState() {
try {
onCameraSelected(widget.cameras[0]);
} catch (e) {
print(e.toString());
}
super.initState();
}
Below is the full code for the CameraHomeScreen.dart file:
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class CameraHomeScreen extends StatefulWidget {
List<CameraDescription> cameras;
CameraHomeScreen(this.cameras);
#override
State<StatefulWidget> createState() {
return _CameraHomeScreenState();
}
}
class _CameraHomeScreenState extends State<CameraHomeScreen> {
String imagePath;
bool _toggleCamera = false;
String _currentCamera = 'back';
CameraController controller;
#override
void initState() {
try {
SharedPreferencesHelper prefs = SharedPreferencesHelper();
prefs.getCameraSelected().then((String answer) {
if (answer != null) {
print('here');
print(answer);
if (answer == 'back') {
onCameraSelected(widget.cameras[0]);
} else {
onCameraSelected(widget.cameras[1]);
}
} else {
print('answer is null');
onCameraSelected(widget.cameras[1]);
}
});
} catch (e) {
print(e.toString());
}
super.initState();
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.cameras.isEmpty) {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Text(
'No Camera Found',
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
),
),
);
}
if (!controller.value.isInitialized) {
return Container();
}
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: Container(
child: Stack(
children: <Widget>[
CameraPreview(controller),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
height: 120.0,
padding: EdgeInsets.all(20.0),
color: Color.fromRGBO(00, 00, 00, 0.7),
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.center,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
onTap: () {
_captureImage();
},
child: Container(
padding: EdgeInsets.all(4.0),
child: Image.asset(
'assets/images/ic_shutter_1.png',
width: 72.0,
height: 72.0,
),
),
),
),
),
Align(
alignment: Alignment.centerRight,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
onTap: () {
if (!_toggleCamera) {
SharedPreferencesHelper prefs =
SharedPreferencesHelper();
prefs.setCameraSelected('front');
onCameraSelected(widget.cameras[1]);
setState(() {
_toggleCamera = true;
});
} else {
SharedPreferencesHelper prefs =
SharedPreferencesHelper();
prefs.setCameraSelected('back');
onCameraSelected(widget.cameras[0]);
setState(() {
_toggleCamera = false;
});
}
},
child: Container(
padding: EdgeInsets.all(4.0),
child: Image.asset(
'assets/images/ic_switch_camera_3.png',
color: Colors.grey[200],
width: 42.0,
height: 42.0,
),
),
),
),
),
],
),
),
),
],
),
),
);
}
void onCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) await controller.dispose();
controller = CameraController(cameraDescription, ResolutionPreset.medium);
controller.addListener(() {
if (mounted) setState(() {});
if (controller.value.hasError) {
showMessage('Camera Error: ${controller.value.errorDescription}');
}
});
try {
await controller.initialize();
} on CameraException catch (e) {
showException(e);
}
if (mounted) setState(() {});
}
String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();
void _captureImage() {
takePicture().then((String filePath) {
if (mounted) {
setState(() {
imagePath = filePath;
});
if (filePath != null) {
showMessage('Picture saved to $filePath');
setCameraResult();
}
}
});
}
void setCameraResult() {
Navigator.pop(context, imagePath);
}
Future<String> takePicture() async {
if (!controller.value.isInitialized) {
showMessage('Error: select a camera first.');
return null;
}
final Directory extDir = await getApplicationDocumentsDirectory();
final String dirPath = '${extDir.path}/FlutterDevs/Camera/Images';
await new Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.jpg';
if (controller.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
await controller.takePicture(filePath);
} on CameraException catch (e) {
showException(e);
return null;
}
return filePath;
}
void showException(CameraException e) {
logError(e.code, e.description);
showMessage('Error: ${e.code}\n${e.description}');
}
void showMessage(String message) {
print(message);
}
void logError(String code, String message) =>
print('Error: $code\nMessage: $message');
}
class SharedPreferencesHelper {
///
/// Instantiation of the SharedPreferences library
///
final String _nameKey = "cameraSelected";
/// ------------------------------------------------------------
/// Method that returns the user decision on sorting order
/// ------------------------------------------------------------
Future<String> getCameraSelected() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(_nameKey) ?? 'name';
}
/// ----------------------------------------------------------
/// Method that saves the user decision on sorting order
/// ----------------------------------------------------------
Future<bool> setCameraSelected(String value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setString(_nameKey, value);
}
}
You can copy paste run full code below
Use SharedPreferences before runApp(MyApp());
and class CameraHomeScreen add a parameter for initCamera
and use it like this CameraHomeScreen(cameras, initCamera),
code snippet
String initCamera;
List<CameraDescription> cameras;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
SharedPreferences pre = await SharedPreferences.getInstance();
//await prefs.setString("cameraSelected","front");
initCamera = await pre.getString("cameraSelected");
print('initCamera ${initCamera}');
runApp(MyApp());
}
...
class CameraHomeScreen extends StatefulWidget {
List<CameraDescription> cameras;
String initCamera;
CameraHomeScreen(this.cameras, this.initCamera);
...
void initState() {
print( 'widget.initCamera ${widget.initCamera}' );
if (widget.initCamera == 'back') {
onCameraSelected(widget.cameras[0]);
} else {
onCameraSelected(widget.cameras[1]);
}
super.initState();
}
demo
full code
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
String initCamera;
List<CameraDescription> cameras;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
SharedPreferences pre = await SharedPreferences.getInstance();
//await prefs.setString("cameraSelected","front");
initCamera = await pre.getString("cameraSelected");
print('initCamera ${initCamera}');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: CameraHomeScreen(cameras, initCamera),
);
}
}
class CameraHomeScreen extends StatefulWidget {
List<CameraDescription> cameras;
String initCamera;
CameraHomeScreen(this.cameras, this.initCamera);
#override
State<StatefulWidget> createState() {
return _CameraHomeScreenState();
}
}
class _CameraHomeScreenState extends State<CameraHomeScreen> {
String imagePath;
bool _toggleCamera = false;
String _currentCamera = 'back';
CameraController controller;
#override
void initState() {
print( 'widget.initCamera ${widget.initCamera}' );
if (widget.initCamera == 'back') {
onCameraSelected(widget.cameras[0]);
} else {
onCameraSelected(widget.cameras[1]);
}
super.initState();
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.cameras.isEmpty) {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Text(
'No Camera Found',
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
),
),
);
}
if (!controller.value.isInitialized) {
return Container();
}
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: Container(
child: Stack(
children: <Widget>[
CameraPreview(controller),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
height: 120.0,
padding: EdgeInsets.all(20.0),
color: Color.fromRGBO(00, 00, 00, 0.7),
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.center,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
onTap: () {
_captureImage();
},
child: Container(
padding: EdgeInsets.all(4.0),
child: Image.asset(
'assets/images/ic_shutter_1.png',
width: 72.0,
height: 72.0,
),
),
),
),
),
Align(
alignment: Alignment.centerRight,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
onTap: () {
if (!_toggleCamera) {
SharedPreferencesHelper prefs =
SharedPreferencesHelper();
prefs.setCameraSelected('front');
print("front");
onCameraSelected(widget.cameras[1]);
setState(() {
_toggleCamera = true;
});
} else {
SharedPreferencesHelper prefs =
SharedPreferencesHelper();
prefs.setCameraSelected('back');
print("back");
onCameraSelected(widget.cameras[0]);
setState(() {
_toggleCamera = false;
});
}
},
child: Container(
padding: EdgeInsets.all(4.0),
child: Image.asset(
'assets/images/ic_switch_camera_3.png',
color: Colors.grey[200],
width: 42.0,
height: 42.0,
),
),
),
),
),
],
),
),
),
],
),
),
);
}
void onCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) await controller.dispose();
controller = CameraController(cameraDescription, ResolutionPreset.medium);
controller.addListener(() {
if (mounted) setState(() {});
if (controller.value.hasError) {
showMessage('Camera Error: ${controller.value.errorDescription}');
}
});
try {
await controller.initialize();
} on CameraException catch (e) {
showException(e);
}
if (mounted) setState(() {});
}
String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();
void _captureImage() {
takePicture().then((String filePath) {
if (mounted) {
setState(() {
imagePath = filePath;
});
if (filePath != null) {
showMessage('Picture saved to $filePath');
setCameraResult();
}
}
});
}
void setCameraResult() {
Navigator.pop(context, imagePath);
}
Future<String> takePicture() async {
if (!controller.value.isInitialized) {
showMessage('Error: select a camera first.');
return null;
}
final Directory extDir = await getApplicationDocumentsDirectory();
final String dirPath = '${extDir.path}/FlutterDevs/Camera/Images';
await new Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.jpg';
if (controller.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
await controller.takePicture(filePath);
} on CameraException catch (e) {
showException(e);
return null;
}
return filePath;
}
void showException(CameraException e) {
logError(e.code, e.description);
showMessage('Error: ${e.code}\n${e.description}');
}
void showMessage(String message) {
print(message);
}
void logError(String code, String message) =>
print('Error: $code\nMessage: $message');
}
class SharedPreferencesHelper {
///
/// Instantiation of the SharedPreferences library
///
final String _nameKey = "cameraSelected";
/// ------------------------------------------------------------
/// Method that returns the user decision on sorting order
/// ------------------------------------------------------------
Future<String> getCameraSelected() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(_nameKey) ?? 'name';
}
/// ----------------------------------------------------------
/// Method that saves the user decision on sorting order
/// ----------------------------------------------------------
Future<bool> setCameraSelected(String value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setString(_nameKey, value);
}
}