How to properly use async function from onPress - flutter

I am trying to do some image processing using filters, and image package. I can make it work, but it hangs the UI.
class ImageScreen extends StatefulWidget {
static MaterialPageRoute route(String path) => MaterialPageRoute(
builder: (context) => ImageScreen(
imagePath: path,
));
final String imagePath;
const ImageScreen({Key key, this.imagePath}) : super(key: key);
#override
_ImageScreenState createState() => _ImageScreenState();
}
class _ImageScreenState extends State<ImageScreen> {
int value = 0;
Filter _filter;
final _random = Random();
_ImageScreenState();
Widget _animatedWidget = const Center(child: CircularProgressIndicator());
Future<Widget> _createImage(Filter filter) async {
value++;
_filter = filter;
var bytes = await File(widget.imagePath).readAsBytes();
var i = await image.decodeImage( bytes );
_filter.apply(i.getBytes(), i.width, i.height);
return Image(key: ValueKey<int>(_random.nextInt(200)) , image: MemoryImage(image.encodeJpg(i)));
}
void _initiate(Filter filter) {
debugPrint("initializing");
_createImage(filter).then(
(widget)=> setState(()=> _animatedWidget =widget));
debugPrint("done");
}
#override
void initState() {
super.initState();
_initiate(NoFilter());
}
#override
Widget build(BuildContext context) {
return Scaffold(body:
Stack(
children: [
AnimatedSwitcher(child: _animatedWidget, duration: Duration(milliseconds: 200),),
Align(
alignment: Alignment.bottomCenter,
child: Container(
padding: EdgeInsets.only(top: 10, bottom: 10),
color: Color.fromRGBO(200, 200, 200, 0.9),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FlatButton(
child: Text('Original'),
onPressed: () async {
_initiate(NoFilter());
},
),
FlatButton(
child: Text('B&W'),
onPressed: () {
_initiate(MoonFilter());
},
),
FlatButton(
child: Text('Dusty'),
onPressed: () {
_initiate(ReyesFilter());
},
)
],
),
)
)
],
));
}
}
What I want to happen is that the processing to be off main thread, and then see a nice transition. Unfortunately something is blocking the main thread.
The button gets stuck in depressed state, and then, after the process finished, page flickers and shows the new image.

you have to make this function to async
Future<void> _initiate(Filter filter)async {
debugPrint("initializing");
_createImage(filter).then(
(widget)=> setState(()=> _animatedWidget =widget));
debugPrint("done");
}
and if you want to call this function then you can call like
onPressed: () async {
await _initiate(NoFilter());
},

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 wifi EspTouch connection stream problem

I want to connect to an ESP32 with my Flutter app. To implement the connection via ESPTouch I use the package "esptouch_flutter".
Now there is the following problem: If I enter my password incorrectly, it tries to connect for a minute and aborts, which is what it is supposed to do. However, if I then enter the correct password, it also searches for a minute and aborts, although it should actually connect. If I enter the correct password without having entered anything wrong before, it works fine.
The actual connection is established via a stream.
stream = widget.task.execute();
I have a suspicion that even after dispose of the page, the stream continues to run and then does not restart properly. Can this be? Or maybe anyone else have an idea?
Full Code:
class wifiConnection extends StatefulWidget {
const wifiConnection({Key? key, required this.task}) : super(key: key);
final ESPTouchTask task;
#override
State<StatefulWidget> createState() => wifiConnectionState();
}
class wifiConnectionState extends State<wifiConnection> {
late final Stream<ESPTouchResult> stream;
late final StreamSubscription<ESPTouchResult> streamSubscription;
late final Timer timer;
final List<ESPTouchResult> results = [];
var connectionSuccessfull = true;
#override
void initState() {
stream = widget.task.execute();
streamSubscription = stream.listen(results.add);
final receiving = widget.task.taskParameter.waitUdpReceiving;
final sending = widget.task.taskParameter.waitUdpSending;
// final cancelLatestAfter = receiving + sending; // Default = 1 min
const testTimer = Duration(seconds: 15);
timer = Timer(
// cancelLatestAfter,
testTimer,
() {
streamSubscription.cancel();
if (results.isEmpty && mounted) {
setState(() {
connectionSuccessfull = false;
});
}
},
);
super.initState();
}
#override
dispose() {
timer.cancel();
streamSubscription.cancel();
super.dispose();
}
_dissolve() {
setState(() async {
await Future.delayed(const Duration(milliseconds: 1000));
setOnboardingEnum(Onboarding.wifiFinished);
Navigator.of(context).pushReplacement(createRoute(const StaticBellPage()));
});
}
getConnectionSite(AsyncSnapshot<ESPTouchResult> snapshot) {
if (snapshot.hasError) {
return connectionError();
}
if (!snapshot.hasData) {
return connectionPending();
}
switch (snapshot.connectionState) {
case ConnectionState.active:
return connectionActive();
case ConnectionState.none:
return connectionError();
case ConnectionState.done:
return connectionActive();
case ConnectionState.waiting:
return connectionPending();
}
}
connectionPending() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: CircularProgressIndicator()),
connectionPendingText,
MydoobeButton(
buttonText: "Zurück",
callback: () => Navigator.of(context)..pop(),
),
],
),
);
}
connectionActive() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: CheckArrow()),
connectionSuccessfullText,
MydoobeButton(buttonText: "Weiter", callback: _dissolve),
],
),
);
}
connectionError() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: ErrorCross()),
connectionErrorText,
MydoobeButton(
buttonText: "Zurück",
callback: () => Navigator.of(context)..pop(),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
StreamBuilder<ESPTouchResult>(
builder: (context, AsyncSnapshot<ESPTouchResult> snapshot) {
return AnimatedSwitcher(
duration: const Duration(seconds: 2),
child: connectionSuccessfull ? getConnectionSite(snapshot) : connectionError() as Widget);
},
stream: stream,
),
],
),
],
),
);
}
}
var connectionPendingText = RichText(...);
var connectionSuccessfullText = RichText(...);
var connectionErrorText = RichText(...);

popAndPushNamed - Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct)

I briefly explain my problem, when I create and name "something" in my application, I let 760 milliseconds pass to go to another page. However the problem is that in that lapse of time, if I exit with the back button of my AppBar, I get the following error (the function that inidicates onTitleSelectionAtCreate is the one that I use the popAndPushNamed)
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
E/flutter (28951): Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
Here is my code :
class InitialRemarkInfo extends StatefulWidget {
final bool? isCreated;
const InitialRemarkInfo({
Key? key,
required this.isCreated,
}) : super(key: key);
#override
_InitialRemarkInfoState createState() => _InitialRemarkInfoState();
}
class _InitialRemarkInfoState extends State<InitialRemarkInfo>
with TickerProviderStateMixin {
double _height = 0;
double _width = 0;
bool _resized = false;
#override
void initState() {
super.initState();
if (!this.widget.isCreated!)
BlocProvider.of<SomethingBloc>(context).add(InitializeEvent());
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: this._onPopBack,
child: Scaffold(
// * APP BAR
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(this.localizedString('newSomething')!),
],
),
leading: IconButton(
splashRadius: 1,
icon: Icon(Icons.arrow_back),
onPressed: _onPopBack,
),
backgroundColor: DesignConstants.darkBlueBackground,
),
body: BlocConsumer<SomethingBloc, SomethingState>(
// * LISTENER
listener: (context, state) async {
[...]
},
builder: (context, state) {
// * LOADED
if (state is SomethingLoaded)
return Stack(
children: [
SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
children: [
// * GROWING SPACE (to simulate animation)
new AnimatedSize(
curve: Curves.easeIn,
child: new Container(
width: _width,
height: _height,
color: Colors.transparent,
),
duration: new Duration(milliseconds: 700),
),
[...]
// * TITLE
DataRo(
title: 'title',
icon: Icons.arrow_drop_down,
isCreation: true,
onTitleSelectionAtCreate: () =>
_onTitleSelectionAtCreate(), // Function
),
],
),
),
],
);
// * DEFAULT
return Container();
},
),
),
);
}
//============================================================================
// ON POP BACK
//
Future<bool> _onPopBack() async {
BlocProvider.of<SomethingBloc>(context).add(
ReloadList(isCreation: true, isSaving: false));
return false;
}
// ==========================================================================
// ON TITLE SELECTION AT CREATE
//
void _onTitleSelectionAtCreate() {
setState(() {
if (_resized) {
this._resized = false;
this._height = 0;
this._width = 0;
} else {
this._resized = true;
this._height = Device.screenHeight / 3;
this._width = Device.screenWidth;
}
}); // To animate something not relevant
Future.delayed(const Duration(milliseconds: 730), () {
Navigator.of(context).popAndPushNamed(
PageNames.something,
arguments: {
"info": null,
},
);
});
}
}
Use mounted getter to determine if the State is still active
Future.delayed(const Duration(milliseconds: 730), () {
if(mounted)
Navigator.of(context).popAndPushNamed(
PageNames.something,
arguments: {
"info": null,
},
);
});

Got NoSuchMethodError (NoSuchMethodError: The method 'call' was called on null. error after setState in flutter

I have Form screen that contains a form widget.
After changing state (now with bloc but I tested with setState, no different) I got following error:
The following NoSuchMethodError was thrown while handling a gesture:
The method 'call' was called on null.
Receiver: null
Tried calling: call()
This only happened when I change state (if I don't yield new state, or setState it works without error).
but after changing state and probably rebuilding widget I got error:
This is main screen:
class _AuthScreenState extends State<AuthScreen> {
final AuthRepository repository = AuthRepository();
PageController controller;
Bloc _bloc;
#override
void initState() {
controller = PageController(initialPage: widget.page);
super.initState();
}
void changePage(int page) {
controller.animateToPage(
page,
curve: Curves.ease,
duration: Duration(milliseconds: 300),
);
}
void onSubmit(AuthType authType, AuthReq req) {
if (authType == AuthType.LOGIN) {
_bloc.add(LoginEvent(req: req));
} else {
_bloc.add(RegisterEvent(req: req));
}
}
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (ctx) => AuthBloc(repository: repository),
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
_bloc = context.bloc<AuthBloc>();
return ScreenContainer(
loading: state is LoadingState,
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
children: [
Expanded(
child: PageView.builder(
controller: controller,
physics: NeverScrollableScrollPhysics(),
itemCount: 2,
itemBuilder: (context, position) {
return position == 0
? LoginPage(
onPageChange: () => changePage(1),
onSubmit: (req) => onSubmit(AuthType.LOGIN, req),
)
: RegisterPage(
onPageChange: () => changePage(0),
onSubmit: (req) => onSubmit(AuthType.REGISTER, req),
);
},
),
),
],
),
),
);
},
),
);
}
}
class LoginPage extends StatelessWidget {
final VoidCallback onPageChange;
final void Function(AuthReq req) onSubmit;
final FormController controller = FormController();
LoginPage({
#required this.onPageChange,
#required this.onSubmit,
});
void submit() {
var values = controller?.submit();
if (values.isNull) {
return;
}
onSubmit(AuthReq(password: values['password'], username: values['email']));
}
#override
Widget build(BuildContext context) {
var authType = AuthType.LOGIN;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: hP),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FormWrapper(
inputs: loginFields,
controller: controller,
),
submitButton(context, authType, submit),
],
),
),
],
),
),
),
],
);
}
}
class FormController {
Map Function() submit;
}
class FormWrapper extends StatefulWidget {
final List<InputProps> inputs;
final FormController controller;
FormWrapper({
#required this.inputs,
this.controller,
});
#override
_FormWrapperState createState() => _FormWrapperState(controller);
}
class _FormWrapperState extends State<FormWrapper> {
final _formKey = GlobalKey<FormState>();
_FormWrapperState(FormController _controller) {
_controller.submit = submit;
}
bool _autoValidation = false;
Map values = {};
void setValue(String key, dynamic value) {
values[key] = value;
}
Map submit() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
return values;
} else {
setState(() {
_autoValidation = true;
});
return null;
}
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Form(
autovalidate: _autoValidation,
key: _formKey,
child: Column(
children: widget.inputs
.map(
(e) => Container(
margin: EdgeInsets.only(bottom: e.isLast ? 0 : 24.0 - 7.0),
child: RoundedInput(
inputProps: e,
onChange: (value) => setValue(e.label, value),
),
),
)
.toList(),
),
),
);
}
}
I found solution, I post answer instead of deleting question for may help others in future :)
I override didUpdateWidget to reinitialize variable in _FormWrapperState:
#override
void didUpdateWidget(oldWidget) {
widget.controller.submit = submit;
super.didUpdateWidget(oldWidget);
}

Flutter-codec does not support config priority (err -2147483648) while playing video

I am working in paint project were, I need to display the list of videos, which works fine. But when the video is playing it is showing something codec does not support config priority (err -2147483648) in console,
Here I am using ImagePicker to record video from the camera and passing to VideoPlayerController.
class VideoScaling extends StatefulWidget {
#override
_VideoScalingState createState() => new _VideoScalingState();
}
class _VideoScalingState extends State<VideoScaling> {
VideoPlayerController videoController;
bool _isPlaying = false;
var _videoPath;
String _videoP;
VoidCallback videoPlayerListener;
List<VideoPlayerController> _listOfVideos = [];
void _video() async {
_videoPath =
await ImagePicker.pickVideo(source: ImageSource.camera).then((p) {
_videoP = p.path;
});
_startVideoPlayer();
}
VideoPlayerController vcontroller;
Future<void> _startVideoPlayer() async {
// print('path video: ${_videoP}');
vcontroller = new VideoPlayerController.file(new File(_videoP));
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;
_listOfVideos.add(videoController);
});
}
await vcontroller.play();
}
void _pause() async {
await vcontroller.play();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
videoController != null
? Column(
children: _listOfVideos
.map((p) => InkWell(
onTap: () {},
child: Padding(
padding: EdgeInsets.all(10.0),
child: Container(
height: 200.0,
width: 200.0,
child: VideoPlayer(p)),
),
))
.toList(growable: false),
)
: Container(
color: Colors.red,
),
_recordVideo(context),
],
),
),
);
}
Widget _recordVideo(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () {
_startVideoPlayer();
},
icon: Icon(Icons.play_arrow),
),
IconButton(
onPressed: () {
setState(() {
videoController = null;
});
_video();
},
icon: Icon(Icons.add_circle),
),
IconButton(
onPressed: () {
_pause();
},
icon: Icon(Icons.stop),
),
],
);
}
}
Add '/' correctly in video paths
Then :
Tools->Flutter->Flutter Clean