I'm new to both Flutter and Dart, and I'm trying to use the Camera Plugin to understand how things work. All examples I find have this part:
List<CameraDescription> cameras;
Future<Null> main() async {
cameras = await availableCameras();
runApp(new CameraApp());
}
Is there some way I could do this inside the initState() method? I guess this is also a more general question regarding async work required before the initState-method is run. (As the initState-method cannot be async).
My goal is to create a StatefulWidget containing a feed from the camera, that is used from another file. Here's what I have so far. Any help appreciated!
List<CameraDescription> cameras;
#override
void initState() {
super.initState();
getCameras();
controller = new CameraController(cameras[0], ResolutionPreset.medium);
controller.initialize().then( (_) {
if (!mounted) {
return;
}
setState(() {});
});
}
Future<Null> getCameras() async {
cameras = await availableCameras();
}
You can't do async work in initState, but you can kick-off async work done in other functions, and then signal when you are done with a setState call. Using await you can make sure the cameras and controller are setup in the correct order. calling setState at the end will make sure the widget gets rebuilt at the end, where you can pass your initialized camera controller wherever you want.
class _CameraState extends State<CameraWidget> {
List<CameraDescription> cameras;
CameraController controller;
bool _isReady = false;
#override
void initState() {
super.initState();
_setupCameras();
}
Future<void> _setupCameras() async {
try {
// initialize cameras.
cameras = await availableCameras();
// initialize camera controllers.
controller = new CameraController(cameras[0], ResolutionPreset.medium);
await controller.initialize();
} on CameraException catch (_) {
// do something on error.
}
if (!mounted) return;
setState(() {
_isReady = true;
});
}
Widget build(BuildContext context) {
if (!_isReady) return new Container();
return ...
}
}
You also want to make sure you handle any errors, the package includes a CameraException which is thrown when the platform specific code fails.
Related
I am trying to figure out how can i watch a StateNotifierProvider and trigger some methods (defined in the class subclassing StateNotifier) on this provider after having done some async computations in another Provider watching the StateNotifierProvider.
Loking at the example below
i need to perform a reset from the RandomAdderNotifierobject provided by the randomAdderProvider if the doneProvider return true.
I try to reset from the doReset Provider. However the provider has nothing to provide.
The point is that both the doneProvider and the doreset provider are not rebuild on state changes of AdderProvider.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:equatable/equatable.dart';
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
final randomProvider = Provider<Random>((ref) {
return Random(1234);
});
//immutable state
class RandomAdder extends Equatable {
final int sum;
const RandomAdder(this.sum);
#override
List<Object> get props => [sum];
}
//State notifier extension
class RandomAdderNotifier extends StateNotifier<RandomAdder> {
RandomAdderNotifier(this.ref) : super(const RandomAdder(0));
final Ref ref;
void randomIncrement() {
state = RandomAdder(state.sum + ref.read(randomProvider).nextInt(5));
}
void reset() {
state = RandomAdder(0);
}
}
/// Providers are declared globally and specify how to create a state
final randomAdderProvider =
StateNotifierProvider<RandomAdderNotifier, RandomAdder>(
(ref) {
return RandomAdderNotifier(ref);
},
);
Future<bool> delayedRandomDecision(ref) async {
int delay = ref.read(randomProvider).nextInt(5);
await Future.delayed(Duration(seconds: delay));
print("You waited $delay seconds for a decision.");
return delay > 4;
}
final doneProvider = FutureProvider<bool>(
(ref) async {
ref.watch(randomAdderProvider);
bool decision = await delayedRandomDecision(ref);
print("the decision is $decision");
return decision;
},
);
final doreset = Provider((ref) {
if (ref.watch(doneProvider).value!) {
ref.read(randomAdderProvider.notifier).reset();
}
});
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
// Consumer is a widget that allows you reading providers.
child: Consumer(builder: (context, ref, _) {
final count = ref.watch(randomAdderProvider);
return Text('$count');
}),
),
floatingActionButton: FloatingActionButton(
// The read method is a utility to read a provider without listening to it
onPressed: () =>
ref.read(randomAdderProvider.notifier).randomIncrement(),
child: const Icon(Icons.add),
),
);
}
}
I think ref.listen is more appropriate for usage within the doreset function than ref.watch.
Similarly to ref.watch, it is possible to use ref.listen to observe a provider.
The main difference between them is that, rather than rebuilding the widget/provider if the listened to provider changes, using ref.listen will instead call a custom function.
As per the Riverpod documentation
For ref.listen we need an additional argument - the callback function that we wish to execute on state changes - Source
The ref.listen method needs 2 positional arguments, the first one is the Provider and the second one is the callback function that we want to execute when the state changes.
The callback function when called will be passed 2 values, the value of the previous State and the value of the new State.
&
We will need to handle an AsyncValue - Source
As you can see, listening to a FutureProvider inside a widget returns an AsyncValue – which allows handling the error/loading states.
In Practice
doreset function
I chose to handle the AsyncValue by only handling the data case with state.whenData()
final doReset = Provider<void>(
(ref) {
final done = ref.listen<AsyncValue<bool>>(doneProvider, (previousState, state) {
state.whenData((value) {
if (value) {ref.read(randomAdderProvider.notifier).reset();}
});
});
},
);
Don't forget to watch either doReset/doneProvider in your Home Widget's build method. Without that neither will kick off (Don't have an explanation for this behaviour)
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
ref.watch(doReset);
...
Lastly, your random function will never meet the condition for true that you have setup as delay>4, as the max possible delay is 4. Try instead using delay>3 or delay=4.
Also perhaps disable the button to prevent clicks while awaiting updates
and in a case where you are using ChangeNotifier You can pass ref in you provider and use the ref same as we can use in ConsumerWidget.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class YourProvider extends ChangeNotifier {
Ref ref;
YourProvider(this.ref) : super();
callOtherProviderFromThisProvider() {
ref.read(otherProvider).someMethodINeedToTrigger();
}
}
final yourProvider = ChangeNotifierProvider<YourProvider>(
(ref) => YourProvider(ref));
I have problem, i have 3 dart files,
home.dart contain button with onclick:
final cartEmiter = CartEmitter();
cartEmiter.emitCart("add_cart");
cart.dart contain:
class CartEmitter {
StreamController _controller = StreamController.broadcast();
void emitCart(action) {
_controller.add(action);
// print(action);
}
Stream get cartAction => _controller.stream;
}
and in main.dart I have this code to change the cart badge.
StreamSubscription _cartCountSubscribtion;
int _cartCount = 0;
#override
void initState() {
_cartCountSubscribtion = CartEmitter().cartAction.listen((action) {
print(action);
setState(() {
_cartCount++;
});
});
super.initState();
}
#override
void dispose() {
_cartCountSubscribtion.cancel();
super.dispose();
}
But it doesn't work, no error, no output printed.
Is my code wrong or how to listen to change?
You create a new CartEmitter in the initState function, and another one in the onclick code. Those two are not connected in any way, so the event you emit with the emitCart call is emitted on a different CartEmitter than the one you listen to.
You need to share the same CartEmitter instance between the initState and onclick code.
Alternatively, if you know that you will only ever need one CartEmitter, you can make the _controller static, so the same controller (and stream) is shared between all instances of CartEmitter.
In that case, you can make emitCart and cartAction static too, and never create any CartEmitter instance at all.
I'm a looking for a way to load async data on InitState method, I need some data before build method runs. I'm using a GoogleAuth code, and I need to execute build method 'till a Stream runs.
My initState method is:
#override
void initState () {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
});
_googleSignIn.signInSilently();
}
I will appreciate any feedback.
You can create an async method and call it inside your initState
#override
void initState () {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
_asyncMethod();
});
}
_asyncMethod() async {
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
});
_googleSignIn.signInSilently();
}
As of now using .then notation seems to work:
// ...
#override
initState() {
super.initState();
myAsyncFunction
// as suggested in the comment
// .whenComplete() {
// or
.then((result) {
print("result: $result");
setState(() {});
});
}
//...
Method 1 : You can use StreamBuilder to do this. This will run the builder method whenever the data in stream changes.
Below is a code snippet from one of my sample projects:
StreamBuilder<List<Content>> _getContentsList(BuildContext context) {
final BlocProvider blocProvider = BlocProvider.of(context);
int page = 1;
return StreamBuilder<List<Content>>(
stream: blocProvider.contentBloc.contents,
initialData: [],
builder: (context, snapshot) {
if (snapshot.data.isNotEmpty) {
return ListView.builder(itemBuilder: (context, index) {
if (index < snapshot.data.length) {
return ContentBox(content: snapshot.data.elementAt(index));
} else if (index / 5 == page) {
page++;
blocProvider.contentBloc.index.add(index);
}
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
});
}
In the above code StreamBuilder listens for any change in contents, initially its an empty array and shows the CircularProgressIndicator. Once I make API call the data fetched is added to contents array, which will run the builder method.
When the user scrolls down, more content is fetched and added to contents array which will again run builder method.
In your case only initial loading will be required. But this provides you an option to display something else on the screen till the data is fetched.
Hope this is helpful.
EDIT:
In your case I am guessing it will look something like shown below:
StreamBuilder<List<Content>>(
stream: account, // stream data to listen for change
builder: (context, snapshot) {
if(account != null) {
return _googleSignIn.signInSilently();
} else {
// show loader or animation
}
});
Method 2: Another method would be to create an async method and call it from you initState() method like shown below:
#override
void initState() {
super.initState();
asyncMethod();
}
void asyncMethod() async {
await asyncCall1();
await asyncCall2();
// ....
}
Create anonymous function inside initState like this:
#override
void initState() {
super.initState();
// Create anonymous function:
() async {
await _performYourTask();
setState(() {
// Update your UI with the desired changes.
});
} ();
}
#override
void initState() {
super.initState();
asyncInitState(); // async is not allowed on initState() directly
}
void asyncInitState() async {
await yourAsyncCalls();
}
Previous Answer!!
You can set a Boolean value like loaded and set it to true in your listen function and make your build function return your data when loaded is set to true otherwise just throw a CircularProgressIndicator
Edited --
I would not suggest calling setState in a method you call in initState. If the widget is not mounted while the setState is called (as the async operation completes) an error will be reported. I suggest you use a package after_layout
Take a look at this answer for better understanding setState in initState : https://stackoverflow.com/a/53373017/9206337
This post will give you an idea to know when the app finishes the build method. So that you can wait for your async method to setState after widget is mounted : https://stackoverflow.com/a/51273797/9206337
You can create an async method and call it inside your initState
#override
void initState() {
super.initState();
asyncMethod(); ///initiate your method here
}
Future<void> asyncMethod async{
await ///write your method body here
}
Per documentation at https://pub.dev/packages/provider
initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>(context).fetchSomething(someValue);
);
}
Sample code:
#override
void initState() {
super.initState();
asyncOperation().then((val) {
setState(() {});
print("success");
}).catchError((error, stackTrace) {
print("outer: $error");
});
//or
asyncOperation().whenComplete(() {
setState(() {});
print("success");
}).catchError((error, stackTrace) {
print("outer: $error");
});
}
Future<void> asyncOperation() async {
await ... ;
}
As loading or waiting for initial state is a (generally) aone off event FutureBuilder would seem to be a good option as it blocks once on an async method; where the async method could be the loading of json config, login etc. There is an post on it [here] in stack.(Flutter StreamBuilder vs FutureBuilder)
How about this?
#override
void initState() {
//you are not allowed to add async modifier to initState
Future.delayed(Duration.zero,() async {
//your async 'await' codes goes here
});
super.initState();
}
initState() and build cannot be async; but in these, you can call a function that is async without waiting for that function.
#override
void initState() {
super.initState();
_userStorage.getCurrentUser().then((user) {
setState(() {
if (user.isAuthenticated) {
Timer.run(() {
redirectTo();
});
}
});
});
}
void redirectTo() {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) => new ShopOrders()));
}
I would strongly suggest using a FutureBuilder. It takes care of executing the async function and building the widget according to the result!
Here's a link to a short intro video and the documentation.
Code Example:
Future<void> initControllers() async {
for (var filePath in widget.videoFilePaths) {
var val = VideoPlayerController.file(File(filePath));
await val.initialize();
controllers.add(val);
}
}
#override
Widget build(BuildContext context) {
FutureBuilder(
future: initControllers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return YourWidget();
} else {
return const ProgressIndicator();
}
},
));}
Tried all suggestions, none would keep my build from starting after the async method that I need in initState() finish, except one: the trick of having a a bool variable in the State class (let's call it _isDataLoaded) that is initialized to false upon definition, set to true inside a setState() that is invoked when the async function finishes inside initState(). In the build, condition a CircleProcessIndicator() or your Widget depending on the value of this variable.
I know it's dirty because it could break the build, but honestly nothing else that would make more sense -such as running super.initState() upon completion of the async function- has worked for me.
I came here because I needed to fetch some files from FTP on program start. My project is a flutter desktop application. The main thread download the last file added to the FTP server, decrypts it and displays the encrypted content, this method is called from initState(). I wanted to have all the other files downloaded in background after the GUI shows up.
None of the above mentioned methods worked. Constructing an Isolate is relatively complex.
The easy way was to use the "compute" method:
move the method downloading all files from the FTP out of the class.
make it an int function with an int parameter (I do not use the int parameter or the result)
call it from the initState() method
In that way, the GUI shows and the program downloads the files in background.
void initState() {
super.initState();
_retrieveFileList(); // this gets the first file and displays it
compute(_backgroundDownloader, 0); // this gets all the other files so that they are available in the local directory
}
int _backgroundDownloader(int value) {
var i = 0;
new Directory('data').createSync();
FTPClient ftpClient = FTPClient('www.guckguck.de',
user: 'maxmusterman', pass: 'maxmusterpasswort');
try {
ftpClient.connect();
var directoryContent = ftpClient.listDirectoryContent();
// .. here, fileNames list is reconstructed from the directoryContent
for (i = 0; i < fileNames.length; i++) {
var dirName = "";
if (Platform.isLinux)
dirName = 'data/';
else
dirName = r'data\';
var filePath = dirName + fileNames[i];
var myDataFile = new File(filePath);
if (!myDataFile.existsSync())
ftpClient.downloadFile(fileNames[i], File(filePath));
}
} catch (err) {
throw (err);
} finally {
ftpClient.disconnect();
}
return i;
I have used timer in initState
Timer timer;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 1), (Timer timer) async {
await this.getUserVerificationInfo();
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
getUserVerificationInfo() async {
await someAsyncFunc();
timer.cancle();
}
I don't want to show notification when the app is in foreground.
How can I check live state of my app?
In your State<...> class you need to implement WidgetsBindingObserver interface and listen for widget state changes. Something like this:
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
AppLifecycleState? _notification;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_notification = state;
});
}
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
...
}
#override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
}
Then when you want to know what is the state, check
_notification.index property. _notification == null => no state changes happened,
0 - resumed,
1 - inactive,
2 - paused.
To extend on #CopsOnRoad's answer, you can use a switch statement to make it nice and neat:
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
print("app in resumed");
break;
case AppLifecycleState.inactive:
print("app in inactive");
break;
case AppLifecycleState.paused:
print("app in paused");
break;
case AppLifecycleState.detached:
print("app in detached");
break;
}
}
Simply create a bool variable which will keep track of all your background/foreground stuff.
Full code:
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
// This variable will tell you whether the application is in foreground or not.
bool _isInForeground = true;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
_isInForeground = state == AppLifecycleState.resumed;
}
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
#override
Widget build(BuildContext context) => Scaffold();
}
Also there is a package named flutter_fgbg for this.
Example:
FGBGNotifier(
onEvent: (event) {
print(event); // FGBGType.foreground or FGBGType.background
},
child: ...,
)
Or:
subscription = FGBGEvents.stream.listen((event) {
print(event); // FGBGType.foreground or FGBGType.background
});
// in dispose
subscription.cancel();
Why:
Flutter has WidgetsBindingObserver to get notified when app changes
its state from active to inactive states and back. But it actually
includes the state changes of the embedding Activity/ViewController as
well. So if you have a plugin that opens a new activity/view
controller(eg: image picker) or in iOS if you start a FaceID prompt
then WidgetsBindingObserver will report as the app is
inactive/resumed.
This plugin on the other hand reports the events only at app level.
Since most apps need only background/foreground events this plugin is
implemented with just those events. In iOS, plugin reports
didEnterBackgroundNotification and willEnterForegroundNotification
notifications and in Android, plugin reports these using
androidx.lifecycle:lifecycle-process package.
Checkout example/ project to see the differences in action.
Example link.
class YourClassState extends State<YourClass> with WidgetsBindingObserver{
#override
void initState(){
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
print("app in resumed");
break;
case AppLifecycleState.inactive:
print("app in inactive");
break;
case AppLifecycleState.paused:
print("app in paused");
break;
case AppLifecycleState.detached:
print("app in detached");
break;
}
}
}
People have already posted the answer but this answer is for developers using Getx architecture. You will be able to use the same approach but instead of using it on our stateless widget use it in the controller page. This method helps you to manage foreground and background activities when using Getx state management architecture
class QuotesController extends GetxController with WidgetsBindingObserver{
#override
void onInit() async{
super.onInit();
WidgetsBinding.instance?.addObserver(this);
}
#override
void onClose() {
WidgetsBinding.instance?.removeObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async{
switch(state){
case AppLifecycleState.resumed:
await player.play();
break;
case AppLifecycleState.inactive:
await player.stop();
break;
case AppLifecycleState.paused:
await player.stop();
break;
case AppLifecycleState.detached:
await player.stop();
// TODO: Handle this case.
break;
}
}
}
You can use a global variable to save the state for easy to use.
For example:
1. Create global variable:
AppLifecycleState appLifecycleState = AppLifecycleState.detached;
2. AddObserver in your AppState class:
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
3. Save state:
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
appLifecycleState = state;
}
4. Now you can use it easily everywhere when you need:
if (appLifecycleState == AppLifecycleState.paused) {
// in background
}
I was looking for an easy way to implement that solution and here is the one that works for me:
1. Create a LifecycleEventHandler Class
In this class add two callbacks as attributes, the suspendingCallBack will be invoked when the app goes to the background, and the resumeCallBack will be invoked when the app returns to the foreground.
class LifecycleEventHandler extends WidgetsBindingObserver {
final AsyncCallback resumeCallBack;
final AsyncCallback suspendingCallBack;
LifecycleEventHandler({
required this.resumeCallBack,
required this.suspendingCallBack,
});
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
print('state >>>>>>>>>>>>>>>>>>>>>> : ${state}');
switch (state) {
case AppLifecycleState.resumed:
if (resumeCallBack != null) {
await resumeCallBack();
}
break;
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
if (suspendingCallBack != null) {
await suspendingCallBack();
}
break;
}
}
}
2. Listen to the State change.
In the main page of our app we can create a variable to save the lifecycle state, just when the app goes into the background we set the value to true, otherwise the value will be false.
class MainPageState extends State<MainPage> {
bool isAppInactive = false; // if the app is Inactive do some thing
#override
initState(){
super.initState();
WidgetsBinding.instance.addObserver(
LifecycleEventHandler(
resumeCallBack: () async => setState(() {
// The app is now resumed, so let's change the value to false
setState(() {isAppInactive = false; });
}), suspendingCallBack: () async {
// The app is now inactive, so let's change the value to true
setState(() {isAppInactive = true; });
})
);
}
}
And then you can use that variable value to do what you want
if(isAppInactive){
// then do some thing
}
If you need it on app start, e.g. to differentiate between normal app launch and push notification, you can read directly from this:
WidgetsBinding.instance?.lifecycleState
It will be detached for push (that is when a push message is received in a callback) and resumed for normal app launch.
Am new to Dart-flutter, and I need to write an app that can pick user location in the background after say an hour.
I have tried to work things around the geolocation plugin and Timer class, which worked fine but only picks the location once.
I need to know if there is a service workaround or the best way to go about this.
thank you
class HomePage extends StatefulWidget{
static const timeout = const Duration(seconds: 5);
#override
State createState() => new HomePageState();
HomePage(){
startTimeout();
}
startTimeout()async {
final GeolocationResult result = await
Geolocation.isLocationOperational();
if(result.isSuccessful) {
return new Timer(timeout, handleTimeout);
}
else {
debugPrint(result.error.toString());
GeolocationResult res = await
Geolocation.requestLocationPermission(const LocationPermission(
android: LocationPermissionAndroid.fine,
ios: LocationPermissionIOS.always,
));
if(res.isSuccessful) {
new Timer.periodic(timeout,(Timer t)=> handleTimeout);
} else {
debugPrint(res.error.toString());
}
}
}
void handleTimeout(){
debugPrint("Print after 10 seconds");
Geolocation.currentLocation(accuracy:
LocationAccuracy.best).listen((result) {
if(result.isSuccessful) {
var posts = new postservice.Post();
var userid=100;
posts.sendLocation(result.location.latitude,result.location.longitude,userid);
}
});
}
}