I'm trying to use the Flutter camera plugin to display a live video of a user's camera.
class Page extends StatefulWidget {
const Page({Key? key}) : super(key: key);
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
late List<CameraDescription> cameras;
late CameraController _controller;
Future<void> _setupCameras() async {
try {
cameras = await availableCameras();
_controller =
new CameraController(cameras[0], ResolutionPreset.medium);
await _controller.initialize();
}catch(e){
throw e;
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _setupCameras(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: CameraPreview(_controller)
);
}
);
}
}
I keep receiving the error:
type 'Null' is not a subtype of type 'CameraController' of 'function result'
I have tried null checking the cameras and _controller variables with List<CameraDescription>? cameras;, CameraController? _controller instead of using late, but with that I get the error: Null check operator used on a null value.
The code provided on the camera plugin page appears to be outdated.
Any suggestions? Thanks
I believe this may be because in the FutureBuilder you create the future by calling _setupCameras() within the build function. The FutureBuilder documentation explicitly states that the future must be initialized outside of the build function:
If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
You can fix this by adding a field setupDone = _setupCameras() to the initState method of your widget, and then referring to that field in your FutureBuilder
Related
The problem I have is that I want to run a function before the buildcontext is rendered.
What the function does is read the storage to get the token. The function works with Async Await.
Why do I want to do this?
Because in the build context I have a FutureBuilder and in this I need token data.
class ProfileScreen extends StatefulWidget {
ProfileScreen({Key? key}) : super(key: key);
#override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
dynamic token;
getToken()async{
token= await decodeJwt();
print("first this");
print(token);
return token;
}
#override
void initState() {
getToken();
super.initState();
}
#override
Widget build(BuildContext context) {
print("after this");
final profileService=Provider.of<ProfileService>(context);
final listDropDownService=Provider.of<ListDropDown>(context);
return FutureBuilder(
future: Future.wait([profileService.getSpecificWalker(token["id"]), listDropDownService.getDropDown("services")]),
builder: (context,dynamic snapshot){
as you can see i used some print also to see what the sequence was like.
But first it prints "then this" and then "first this"
My strongest suggestion would be to use some state management solution, such as flutter_bloc, that is not directly dependent on the UI components.
If not, then why don't you just try and wrap the getToken() method and the profileService.getSpecificWalker(token["id"]) call in a single async method and call that from the FutureBuilder.
What you said shows that you need state manager, because in one state you want to get token then when token get available, call the futureBuilder. I recommended bloC.
First I have initialized my camera controller (camera: ^0.9.4+11) like this and it works:
class TakePictureScreen extends StatefulWidget {
final CameraDescription camera;
const TakePictureScreen({required Key key, required this.camera})
: super(key: key);
#override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
#override
void initState() {
super.initState();
_controller = CameraController(
widget.camera,
ResolutionPreset.max, // TODO: this should come from SharedPreferences
);
_initializeControllerFuture = _controller.initialize();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
return (snapshot.connectionState == ConnectionState.done)
? CameraPreview(_controller)
: Text("");
}
),
);
}
}
But now I want to load the ResolutionPreset dynamically from SharedPreferences (shared_preferences: ^2.0.13).
What's a good way to do this?
I failed when trying it like this (adding some variables and changing initState method):
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
late Future<int> _resolutionIndex;
List<ResolutionPreset> resolutions = ResolutionPreset.values.toList(growable: false);
static const String sharedPrefResolution = "resolution";
#override
void initState() {
super.initState();
_resolutionIndex = _prefs.then((SharedPreferences prefs) {
int resolutionIndex = prefs.getInt(sharedPrefResolution) ?? (resolutions.length - 1);
_controller = CameraController(
widget.camera,
resolutions[resolutionIndex],
);
_initializeControllerFuture = _controller.initialize();
return resolutionIndex;
});
}
Getting the error: LateInitializationError: Field '_initializeControllerFuture#19039262' has not been initialized.
initState can't be an async method, and getting a value from SharedPreferences is an async function. You can't use await (or then) in initState, I mean you can use but the code execution will not wait for this to complete. So what happens here is that your build method will run earlier than the future getting the value from SharedPreferences completes. And as I presume your _initializeControllerFuture is marked as late, so when your build tries to use it, it is still null, and that will get you this error.
The common way to solve this issue is to use a FutureBuilder. Get the values from SharedPreferences with FutureBuilder, display a progress indicator while it is being loaded (it will be quick so if you think you can skip this part), and then when you get the value from it, build your widget using the value coming from SharedPreferences, and initialize CameraController only after this.
I'm trying to access userEmail in shared preferences, inside my build method. Here's some of the code for context:
Widget build(BuildContext context) {
final prefs = await SharedPreferences.getInstance();
final userEmail = prefs.getString('userEmail') ?? '';
...
Return Scaffold(
body: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: StreamBuilder<QuerySnapshot>(
stream: _firestore.collection(userEmail).orderBy('time', descending: false).snapshots(),
...
The issue I have is, an error comes up highlighting the await. When i hover over it with my cursor for more info, it say The await expression can only be used in an async function. Try marking the function body with either 'async' or 'async*'.
There is then an option to add 'async' modifier. So i clicked that, which transformed code into this:
Future<Widget> build(BuildContext context) async {
...
This causes another error message: '_HomeScreenState.build' ('Future<Widget> Function(BuildContext)') isn't a valid override of 'State.build' ('Widget Function(BuildContext)').
Any ideas how to solve this issue? I've tried saving the userEmail using the Provider package. This works perfectly when the user first signs in or registers, but if you hot reload, the stream doesn't work.
You can use WidgetsBinding.instance.addPostFrameCallback, this helps you to run a callback during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed). If a frame is in progress and post-frame callbacks haven't been executed yet, then the registered callback is still executed during the frame. Otherwise, the registered callback is executed during the next frame.
In code, you can use it something like this.
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final prefs = await SharedPreferences.getInstance();
final userEmail = prefs.getString('userEmail') ?? '';
});
Hope this answers your question.
Long Story short you should not perform any side effects inside your build method . See here
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final prefs ;
#override
void initState() async{
super.initState();
prefs = await SharedPreferences.getInstance();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: prefs,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.hasData){}else if (snapshot.hasError)
return Center(child: CircularProgressIndicator());
},);
}
}
As your build method can be called multiple times you should not perform network calls or call complex methods because as the docs say. This method can and will be called multiple times.
In your case I used a FutureBuilder to handle the future's state and awaited it in the initState insida a stateful widget.
Check this article for more info
As #croxx5f and #AhmetKAYGISIZ suggested, I ended up using FutureBuilder to solve this problem. Thank you both so much for your help with this.
Here's the final code for anyone else who is stuck on this problem:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var prefs;
#override
void initState() {
super.initState();
getUserEmailFromSharedPrefs();
}
Future<String> getUserEmailFromSharedPrefs() async {
prefs = await SharedPreferences.getInstance();
final userEmail = prefs.getString('userEmail') ?? '';
return userEmail;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getUserEmailFromSharedPrefs(),
builder: (context, AsyncSnapshot<String> snapshot) {
if(snapshot.hasData) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: StreamBuilder<QuerySnapshot>(
stream: _firestore.collection(snapshot.data).orderBy('time', descending: false).snapshots(),
...
So in summary, I wrapped my streambuilder in a futurebuilder.
I kept getting an error from the camera.dart that "name" was being called on a null object.
After some time, I realized that the problem was the that the build method is called before the async code in my initstate finished (I'm actually slightly proud that I understood the problem at least :))
I tried many different ways to initialize my camera properly, but I could not.
This is the last iteration of my code.
What's the idiomatic way of handling this future?
class _PicturePreviewState extends State<PicturePreview> {
List<CameraDescription> cameras;
CameraDescription camera;
CameraController cameraController;
Future<void> initializeController;
Future<void> getCameras() async {
try {
cameras = await availableCameras();
} catch(e) {print(e);}
camera = cameras.last;
print(camera);
}
#override
void initState() {
super.initState();
// getCameras();
availableCameras().then((availableCameras) {
cameras = availableCameras;
camera = cameras.first;
cameraController = CameraController(
camera,
ResolutionPreset.low,
);
initializeController = cameraController.initialize();
print(cameraController.value.isInitialized);
});
// cameraController = CameraController(
// camera,
// ResolutionPreset.low,
// );
// initializeController = cameraController.initialize();
// print(cameraController.value.isInitialized);
}
#override
void dispose() {
cameraController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<void>(
future: initializeController,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(cameraController);
}
else {
// Otherwise, display a loading indicator.
print(snapshot.connectionState);
return Center(child: CircularProgressIndicator());
}
},
),
I have been relying on this page to use the camera package, but I could not use it verbatim because I can't keep passing down the camera object down my widget tree.
I fixed it.
I put the initializing of the camera object in the in the parent of the widget.
class _TakeReceiptPictureState extends State<TakeReceiptPicture> {
List<CameraDescription> cameras;
CameraDescription camera;
#override
void initState() {
super.initState();
availableCameras().then((availableCameras) {
cameras = availableCameras;
camera = cameras.first;
});
}
Then made the widget that takes the picture have a parameter of type CameraDescription.
class PicturePreview extends StatefulWidget {
final CameraDescription camera;
const PicturePreview(this.camera, {Key key}) : super(key: key);
#override
_PicturePreviewState createState() => _PicturePreviewState();
}
Then passed the camera initialized in the parent to picture widget
onTap: () {
Navigator.of(context).push(
PageTransition(
type: PageTransitionType.transferRight,
child: PicturePreview(camera)),
);
}),
by the time the child widget's build method runs, the camera object is already initialized and ready to go.
Now the state of the child have only two variables, the camera controller and the initialize controller future.
CameraController cameraController;
Future<void> initializeController;
#override
void initState() {
super.initState();
cameraController = CameraController(
widget.camera,
ResolutionPreset.low,
);
initializeController = cameraController.initialize();
}
TLDR: let the initialization of the camera object be the responsibility of the parent of the widget.
I would like to be able to get to a network image within a single microtask if the image is already loaded. However, with the current API available in NetworkImage and FutureBuilder, this does not seem to be possible.
This is how we typically wire the two:
NetworkImage imageProvider = getSomeNetworkImage(id);
Completer<ui.Image> completer = Completer<ui.Image>();
imageProvider.resolve(ImageConfiguration()).addListener(
(ImageInfo info, _) => completer.complete(info.image));
return FutureBuilder<ui.Image>(
future: completer.future,
builder: (BuildContext futureBuilderContext, AsyncSnapshot<ui.Image> snapshot) {
if (!snapshot.hasData) {
return _buildPlaceholder();
} else {
return _buildActual(context, snapshot.data, imageProvider);
}
},
);
addListener() immediately calls completer.complete() if the image is already there. However, FutureBuilder is based off of completer.future which does not complete until the next microtask. So even when the image is available, placeholder is displayed momentarily.
What is the best way to avoid this? Perhaps, imageProvider should expose a Future that prevents us from piping this through a completer?
Instead of using a FutureBuilder, I would take advantage of the syncCall argument passed to the listener of the ImageStream. This will tell you if the image resolved immediately, meaning it is already cached. Otherwise you can call setState and trigger a rebuild when it does complete.
class Example extends StatefulWidget {
const Example({Key key, this.image, this.child}): super(key: key);
final ImageProvider image;
final Widget child;
#override
State createState() => new ExampleState();
}
class ExampleState extends State<Example> {
bool _isImageLoaded = false;
#override
void initState() {
widget.image
.resolve(const ImageConfiguration)
.addListener(_handleResolve);
super.initState();
}
#override
Widget build(BuildContext context) {
// if syncCall = true, then _handleResolve will have already been called.
if (_isImageLoaded)
return new Image(widget.image);
return widget.child;
}
void _handleResolve(ImageInfo info, bool syncCall) {
_isImageLoaded = true;
if (!syncCall) {
// we didn't finished loading immediately, call setState to trigger frame
setState(() { });
}
}
}