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);
}
}
Related
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]);
}
}
I'm trying to capture a video and upload it to firebase storage.
The problem is, the recorded video format is .mov and this format seems to be incompatible with browsers.
How could I convert the recorded file to .mp4?
Below is the code I use to record and upload my videos.
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:path_provider/path_provider.dart';
class CameraTestPage extends StatefulWidget {
const CameraTestPage({Key? key}) : super(key: key);
#override
_CameraTestPageState createState() => _CameraTestPageState();
}
class _CameraTestPageState extends State<CameraTestPage> {
List<CameraDescription>? cameras;
CameraController? controller;
bool frontCamera = false;
XFile? videoFile;
VideoPlayerController? videoController;
VoidCallback? videoPlayerListener;
#override
void initState() {
initCamera();
super.initState();
}
initCamera() async {
if (cameras == null) {
cameras = await availableCameras();
}
if (controller?.value.isInitialized ?? false) {
controller!.dispose();
}
controller = CameraController(
cameras!.firstWhere(
(element) => element.lensDirection == (frontCamera ? CameraLensDirection.front : CameraLensDirection.back),
),
ResolutionPreset.medium,
enableAudio: false,
);
controller!.initialize().then((value) {
setState(() {});
});
}
void onVideoRecordButtonPressed() {
startVideoRecording().then((_) {
if (mounted) setState(() {});
});
}
void onStopButtonPressed() {
stopVideoRecording().then((file) {
if (mounted) setState(() {});
if (file != null) {
print('Video recorded to ${file.path}');
videoFile = file;
_startVideoPlayer();
}
});
}
Future<void> _startVideoPlayer() async {
if (videoFile == null) {
return;
}
final VideoPlayerController vController = VideoPlayerController.file(File(videoFile!.path));
videoPlayerListener = () {
if (videoController != null && videoController!.value.size != null) {
// Refreshing the state to update video player with the correct ratio.
if (mounted) setState(() {});
videoController!.removeListener(videoPlayerListener!);
}
};
vController.addListener(videoPlayerListener!);
await vController.setLooping(true);
await vController.initialize();
await videoController?.dispose();
if (mounted) {
setState(() {
videoController = vController;
});
}
await vController.play();
}
Future<void> startVideoRecording() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
print('Error: select a camera first.');
return;
}
if (cameraController.value.isRecordingVideo) {
// A recording is already started, do nothing.
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.isRecordingVideo) {
return null;
}
try {
return cameraController.stopVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
void _showCameraException(CameraException e) {
print('Error: ${e.code}\n${e.description}');
}
_uploadVideo() async {
Reference ref = FirebaseStorage.instance.ref("test/test.mov");
UploadTask uploadTask = ref.putFile(File(videoFile!.path), SettableMetadata(contentType: 'video/mov'));
print("uploading");
uploadTask.whenComplete(() async {
String downloadUrl = await ref.getDownloadURL();
print("download url: $downloadUrl");
});
}
Widget _thumbnailWidget() {
final VideoPlayerController? localVideoController = videoController;
return Expanded(
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
child: (localVideoController == null)
? Center(
child: Container(
child: Text("none"),
),
)
: Container(
child: AspectRatio(
aspectRatio: localVideoController.value.aspectRatio,
child: VideoPlayer(localVideoController),
),
decoration: BoxDecoration(
border: Border.all(color: Colors.pink),
),
),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Camera Test")),
body: Column(
children: [
if (controller != null)
Container(
child: CameraPreview(controller!),
height: 400,
),
Row(
children: [
ElevatedButton(
onPressed: () {
frontCamera = !frontCamera;
initCamera();
},
child: Text("Switch"),
),
ElevatedButton(
onPressed: () {
controller?.value.isRecordingVideo ?? false ? onStopButtonPressed() : onVideoRecordButtonPressed();
},
child: Text(controller?.value.isRecordingVideo ?? false ? "Stop" : "Record"),
),
ElevatedButton(
onPressed: videoFile != null ? _uploadVideo : null,
child: Text("Upload video"),
),
],
),
_thumbnailWidget(),
],
),
);
}
}
You can use the video_compress package to convert the video to mp4.
Here is your _uploadVideo() method updated to include this:
_uploadVideo() async {
Reference ref = FirebaseStorage.instance.ref("test/test.mp4");
MediaInfo? mediaInfo = await VideoCompress.compressVideo(
videoFile!.path,
quality: VideoQuality.DefaultQuality,
deleteOrigin: false, // It's false by default
);
UploadTask uploadTask = ref.putFile(
File(mediaInfo!.path!), SettableMetadata(contentType: 'video/mp4'));
print("uploading");
uploadTask.whenComplete(() async {
String downloadUrl = await ref.getDownloadURL();
print("download url: $downloadUrl");
});
}
Ensure you import the package by including this line:
import 'package:video_compress/video_compress.dart';
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.
I am trying to add an option to take images and pictures and save them to local device. I have managed to add the Camera and video. whenever I click on the take picture or video button I end up getting the error
I/flutter ( 4827): NoSuchMethodError: The getter 'name' was called on
null. Receiver: null.Tried
calling: name . NoSuchMethodError: The getter 'name'
was called on null. Receiver: null Tried calling: name
Am using the flutter camera library. What am I doing wrong? Thank you in advance
my camera code
List<CameraDescription> cameras = [];
void logError(String code, String message) =>
print('Error: $code\nError Message: $message');
Future<void> main() async {
// Fetch the available cameras before initializing the app.
try {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
} on CameraException catch (e) {
logError(e.code, e.description);
}
final camera = cameras.first;
runApp(TakePicturePage(camera: camera));
}
class TakePicturePage extends StatefulWidget {
final CameraDescription camera;
TakePicturePage({#required this.camera});
#override
_TakePicturePageState createState() => _TakePicturePageState();
}
class _TakePicturePageState extends State<TakePicturePage> {
final fileName = DateTime.now().millisecondsSinceEpoch.toString();
var vidPath;
CameraController _cameraController;
Future<void> _initializeCameraControllerFuture;
int _selectedIndex = 0;
bool _start = false;
bool _isRec = false;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
if (_selectedIndex == 1) {
_start = !_start;
}
});
}
#override
void initState() {
super.initState();
_initApp();
_cameraController =
CameraController(widget.camera, ResolutionPreset.medium);
_initializeCameraControllerFuture = _cameraController.initialize();
_fileInit();
}
_initApp() async {
final cameras = await availableCameras();
final camera = cameras.first;
_cameraController = CameraController(
camera, ResolutionPreset.max,
);
}
void _fileInit() async {
vidPath = join((await getTemporaryDirectory()).path, '${fileName}.mp4');
}
void _takePicture(BuildContext context) async {
try {
await _initializeCameraControllerFuture;
if (_selectedIndex == 0) {
final imgPath =
join((await getTemporaryDirectory()).path, '${fileName}.png');
await _cameraController.takePicture(imgPath);
Navigator.pop(context, imgPath);
} else {
if (_start) {
await _cameraController.startVideoRecording(vidPath);
setState(() {
_start = !_start;
_isRec = !_isRec;
});
} else {
_cameraController.stopVideoRecording();
setState(() {
_isRec = !_isRec;
});
Navigator.pop(context, vidPath);
}
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
FutureBuilder(
future: _initializeCameraControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return CameraPreview(_cameraController);
} else {
return Center(child: CircularProgressIndicator(backgroundColor: Colors.green,));
}
},
),
SafeArea(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
backgroundColor: Colors.green,
child: _selectedIndex == 1 ? _isRec == true?Icon(Icons.pause, color: Colors.white):Icon(Icons.play_arrow, color: Colors.white) : Icon(Icons.camera, color: Colors.white),
onPressed: () {
_takePicture(context);
},
),
),
),
),
_isRec == true
? SafeArea(
child: Container(
height: 30,
// alignment: Alignment.topLeft,
decoration: BoxDecoration(
color: Color(0xFFEE4400),
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"REC",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Color(0xFFFAFAFA)),
),
),
),
)
: SizedBox(
height: 0,
)
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.camera),
title: Text('Picture'),
),
BottomNavigationBarItem(
icon: Icon(Icons.videocam),
title: Text('Video'),
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.green,
onTap: _onItemTapped,
),
);
}
#override
void dispose() {
_cameraController.dispose();
super.dispose();
}
}
the camera is not being opened
I wanted to Download a Image with its progress and message. I wanted to show it in a dialog. When ever I click Download Button the Image gets downloaded and the Container pops up, but it does not Show any value.
The below code uses Image_downloader package. Raised button downloads the image and display the Blank Container without any value;
import 'dart:async';
import 'dart:io';
import 'Download.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_downloader/image_downloader.dart';
void main() => runApp(HomePage());
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
String message = "";
String path = "";
int _progress = 0;
#override
void initState() {
super.initState();
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
_progress = progress;
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: () {
_downloadImage(
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
);
showDialog(
context: context,
builder: (_) => FunkyOverlay(progress: _progress, message: message,),
);
},
child: Text("default destination"),
),
],
),
),
),
);
}
Future<void> _downloadImage(String url,
{AndroidDestinationType destination, bool whenError = false}) async {
String fileName;
String path;
try {
String imageId;
if (whenError) {
imageId = await ImageDownloader.downloadImage(url).catchError((error) {
if (error is PlatformException) {
var path = "";
if (error.code == "404") {
print("Not Found Error.");
} else if (error.code == "unsupported_file") {
print("UnSupported FIle Error.");
path = error.details["unsupported_file_path"];
}
setState(() {
message = error.toString();
path = path;
});
}
print(error);
}).timeout(Duration(seconds: 10), onTimeout: () {
print("timeout");
});
} else {
if (destination == null) {
imageId = await ImageDownloader.downloadImage(url);
} else {
imageId = await ImageDownloader.downloadImage(
url,
destination: destination,
);
}
}
if (imageId == null) {
return;
}
fileName = await ImageDownloader.findName(imageId);
path = await ImageDownloader.findPath(imageId);
} on PlatformException catch (error) {
setState(() {
message = error.message;
});
return;
}
if (!mounted) return;
setState(() {
message = 'Image Downloaded';
});
}
}
This is the Pop up Container Part
import 'package:flutter/material.dart';
class FunkyOverlay extends StatefulWidget {
String message;
int progress;
FunkyOverlay({#required this.message, #required this.progress});
#override
State<StatefulWidget> createState() => FunkyOverlayState(message, progress);
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
String message;
int progress;
FunkyOverlayState(this.message, this.progress);
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Downloaded: $progress'),
Text(message)
],
),
),
),
),
),
);
}
}
You can copy paste run full code below
You can use StreamBuilder to receive progress from onProgressUpdate
class HomePageState extends State<HomePage> {
...
#override
void initState() {
super.initState();
_events = new StreamController<int>.broadcast();;
_events.add(0);
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
print("progress $progress");
_progress = progress;
_events.add(progress);
});
return StreamBuilder<int>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Center(
...
children: <Widget>[
Text('Downloaded: ${snapshot.data.toString()}'),
Text(message)
working demo
full code
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:image_downloader/image_downloader.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
StreamController<int> _events;
class HomePageState extends State<HomePage> {
String message = "";
String path = "";
int _progress = 0;
#override
void initState() {
super.initState();
_events = new StreamController<int>.broadcast();
;
_events.add(0);
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
print("progress $progress");
_progress = progress;
_events.add(progress);
if (progress == 100) {
Navigator.pop(context);
}
});
});
}
#override
void dispose() {
// TODO: implement dispose
_events.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: () {
_events.add(0);
_downloadImage(
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
);
showDialog(
context: context,
builder: (_) => FunkyOverlay(
progress: _progress,
message: message,
),
);
},
child: Text("default destination"),
),
],
),
),
);
}
Future<void> _downloadImage(String url,
{AndroidDestinationType destination, bool whenError = false}) async {
String fileName;
String path;
try {
String imageId;
if (whenError) {
imageId = await ImageDownloader.downloadImage(url).catchError((error) {
if (error is PlatformException) {
var path = "";
if (error.code == "404") {
print("Not Found Error.");
} else if (error.code == "unsupported_file") {
print("UnSupported FIle Error.");
path = error.details["unsupported_file_path"];
}
setState(() {
message = error.toString();
path = path;
});
}
print(error);
}).timeout(Duration(seconds: 10), onTimeout: () {
print("timeout");
});
} else {
if (destination == null) {
imageId = await ImageDownloader.downloadImage(url);
} else {
imageId = await ImageDownloader.downloadImage(
url,
destination: destination,
);
}
}
if (imageId == null) {
print("imageId is null");
return;
}
fileName = await ImageDownloader.findName(imageId);
path = await ImageDownloader.findPath(imageId);
} on PlatformException catch (error) {
setState(() {
message = error.message;
});
return;
}
if (!mounted) return;
setState(() {
message = 'Image Downloaded';
});
}
}
class FunkyOverlay extends StatefulWidget {
String message;
int progress;
FunkyOverlay({#required this.message, #required this.progress});
#override
State<StatefulWidget> createState() => FunkyOverlayState(message, progress);
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
String message;
int progress;
FunkyOverlayState(this.message, this.progress);
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
print("StreamBuilder build");
return StreamBuilder<int>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print("snapshot.data ${snapshot.data.toString()}");
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Downloaded: ${snapshot.data.toString()}'),
Text(message)
],
),
),
),
),
),
);
});
}
}