setState() or markNeedsBuild() called during build Exception using Chewie Controller - flutter

I have a video playing on the screen using the code below. When I hit the fullscreen icon on the controllers, I get the exception setState() or markNeedsBuild() called during build. The screen should go to fullscreen in the landscape when I hit the icon. It goes but then comes to portrait again (i.e. device orientation).
The following assertion was thrown while dispatching notifications for VideoPlayerController:
setState() or markNeedsBuild() called during build.
Debug console says:
This MaterialControls widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: MaterialControls
dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#8836f], _ChewieControllerProvider]
state: _MaterialControlsState#84529
The widget which was currently being built when the offending call was made was: VideoApp
dirty
state: VideoAppState#8eaa6
When the exception was thrown, this was the stack
Element.markNeedsBuild.<anonymous closure>
Element.markNeedsBuild
State.setState
_MaterialControlsState._updateState
ChangeNotifier.notifyListeners
...
The VideoPlayerController sending notification was: VideoPlayerController#cc7a6(VideoPlayerValue(duration: 0:11:43.069000, size: Size(640.0, 360.0), position: 0:00:03.609000, buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:10.543000)], isPlaying: true, isLooping: false, isBuffering: falsevolume: 1.0, errorDescription: null))
Here is the code which I am using currently.
import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart';
typedef void EndOfVideo();
class VideoApp extends StatefulWidget {
final VideoAppState _videoAppState = VideoAppState();
final String videoUrl;
final EndOfVideo endOfVideo;
final bool autoPlay;
VideoApp({
this.videoUrl,
this.endOfVideo,
this.autoPlay
});
#override
State<StatefulWidget> createState() => _videoAppState;
}
class VideoAppState extends State<VideoApp> {
bool _eovReached = false;
// bool wasLandscape = false;
// bool leaveFullscreen = false;
VideoPlayerController _videoPlayerController;
ChewieController _chewieController;
VoidCallback listener;
VideoAppState() {
listener = () {
if(_videoPlayerController.value.initialized) {
Duration duration = _videoPlayerController.value.duration;
Duration position = _videoPlayerController.value.position;
if (duration.inSeconds - position.inSeconds < 3) {
if(!_eovReached) {
_eovReached = true;
widget.endOfVideo();
}
}
}
};
}
initialize(){
if(_videoPlayerController != null && _videoPlayerController.value.isPlaying) {
_videoPlayerController.pause();
}
_videoPlayerController = VideoPlayerController.network(
widget.videoUrl
);
if(_chewieController != null) {
_chewieController.dispose();
}
_chewieController = ChewieController(
allowedScreenSleep: false,
allowFullScreen: true,
// uncomment line below to make video fullscreen when play button is hit
// fullScreenByDefault : true,
deviceOrientationsAfterFullScreen: [
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
],
videoPlayerController: _videoPlayerController,
aspectRatio: 16 / 9,
autoPlay: false,
looping: false,
autoInitialize: false,
);
_videoPlayerController.addListener(listener);
_videoPlayerController.initialize();
}
#override
void initState() {
super.initState();
try {
this.initialize();
}catch(e){}
}
#override
void didUpdateWidget(VideoApp oldWidget) {
super.didUpdateWidget(oldWidget);
if (this.mounted){
if(oldWidget.videoUrl != widget.videoUrl) {
try {
this.initialize();
}catch(e){
}
}
}
}
#override
void dispose() {
_videoPlayerController.dispose();
_chewieController.dispose();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);
super.dispose();
}
#override
Widget build(BuildContext context) {
if(widget.autoPlay) {
_videoPlayerController.play();
}
return new Container(
child: new Center(
child: new Chewie(
controller: _chewieController,
)
),
);
}
}
My question is what could be causing this error and how to fix this. I also want to make the video player go fullscreen when the orientation of the device is landscapeleft or landscaperight.
Let me know if I should add anything else in here.
Thank you.
Edit:
I have fixed the exception. Thanks to Ibrahim Karahan! I need help with making the video player go full screen when the device is turned landscape. Thanks again.

Related

Flutter: Video player not initialized

I have a video preview widget that takes either a string url or a video file. If the parameter is a String, it downloads the file from online/the cache. With this is mind, my implementation is as follows:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hero/helpers/cache_manager/cache_manager.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
class WaveVideoPreview extends StatefulWidget {
final File? videoFile;
final String? videoUrl;
WaveVideoPreview({this.videoFile, this.videoUrl});
#override
_WaveVideoPreviewState createState() => _WaveVideoPreviewState();
}
class _WaveVideoPreviewState extends State<WaveVideoPreview> {
late VideoPlayerController? _controller;
late ChewieController _chewieController;
void initState() {
super.initState();
_initAsync();
}
void _initAsync() async {
File? _videoFile = widget.videoFile;
if (_videoFile == null) {
_videoFile = await getVideo(_videoFile);
}
_controller = VideoPlayerController.file(_videoFile!)
..initialize().then((_) {
setState(() {
_chewieController = ChewieController(
videoPlayerController: _controller!,
aspectRatio: _controller!.value.aspectRatio,
autoPlay: false,
looping: true,
allowFullScreen: false,
);
});
});
}
Future<File?> getVideo(File? _videoFile) async {
_videoFile = await MyCache.getVideo(widget.videoUrl!);
return _videoFile;
}
#override
Widget build(BuildContext context) {
return Container(
height: 200.0,
child: (_controller?.value.isInitialized ?? false)
? Chewie(
controller: _chewieController,
)
: SizedBox.shrink(),
);
}
#override
void dispose() {
_controller!.dispose();
_chewieController.dispose();
super.dispose();
}
}
with
static Future<File?> getVideo(String url) async {
DefaultCacheManager _cacheManager = DefaultCacheManager();
File? file = await _cacheManager.getSingleFile(url);
return file;
}
Which is throwing the error:
════════ Exception caught by widgets library ═══════════════════════════════════
The following LateError was thrown building WaveVideoPreview(dirty, state: _WaveVideoPreviewState#659ef):
LateInitializationError: Field '_controller#1875314998' has not been initialized.
The relevant error-causing widget was
WaveVideoPreview
lib/…/widget/wave_tile.dart:84
When the exception was thrown, this was the stack
#0 _WaveVideoPreviewState._controller (package:hero/screens/home/home_screens/views/waves/widget/video/wave_video_preview.dart)
package:hero/…/video/wave_video_preview.dart:1
#1 _WaveVideoPreviewState.build
package:hero/…/video/wave_video_preview.dart:57
Anyone know whats going on? I've tried changing the video player between nullable and non nullable, but still to no avail. Also as you can see i have a null check, but still nothing.
_initAsync is using async and it will take some frame to initialize the controllers.
It would be better to use FutureBuilder for this. or make those nullable and the do a null check while using it.
VideoPlayerController? _controller;
ChewieController? _chewieController;

Error: Bad state: Cannot add new events after calling close

I am using '''youtube_player_iframe: ^2.1.0''' package for displaying YouTube video in Flutter web app. The video is playing absolutely fine but it gives this error whenever i tap on full screen to play video in full screen also it do not make the video to come on full screen and return to its previous size
ERROR
Error: Bad state: Cannot add new events after calling close
at Object.throw_ [as throw] (http://localhost:42339/dart_sdk.js:5041:11)
at _AsyncBroadcastStreamController.new.add (http://localhost:42339/dart_sdk.js:31586:44)
at controller.YoutubePlayerController.new.add (http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:894:32)
at http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:469:29
at Object._checkAndCall (http://localhost:42339/dart_sdk.js:5246:16)
at Object.dcall (http://localhost:42339/dart_sdk.js:5251:17)
at http://localhost:42339/dart_sdk.js:100646:100
Error: Bad state: Cannot add new events after calling close
at Object.throw_ [as throw] (http://localhost:42339/dart_sdk.js:5041:11)
at _AsyncBroadcastStreamController.new.add (http://localhost:42339/dart_sdk.js:31586:44)
at controller.YoutubePlayerController.new.add (http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:894:32)
at http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:469:29
at Object._checkAndCall (http://localhost:42339/dart_sdk.js:5246:16)
at Object.dcall (http://localhost:42339/dart_sdk.js:5251:17)
at http://localhost:42339/dart_sdk.js:100646:100
CODE :
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
class YoutubePlayerWeb extends StatefulWidget {
final String url;
const YoutubePlayerWeb({Key? key, required this.url}) : super(key: key);
#override
_YoutubePlayerState createState() => _YoutubePlayerState();
}
class _YoutubePlayerState extends State<YoutubePlayerWeb> {
late YoutubePlayerController _controller;
void runYoutubePlay()
{
_controller = YoutubePlayerController(
initialVideoId: YoutubePlayerController.convertUrlToId(widget.url).toString(),
params: const YoutubePlayerParams(
showControls: true,
desktopMode: true,
showFullscreenButton: true,
privacyEnhanced: true,
showVideoAnnotations: true ,
autoPlay: false,
enableCaption: true,
color: 'red',
)
);
}
void youtubePlayerFullScreen()
{
_controller.onEnterFullscreen = ()
{
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
print("ENTERED FULLSCREEN");
};
_controller.onExitFullscreen = ()
{
print("EXITED FULLSCREEN");
};
}
#override
void initState() {
runYoutubePlay();
youtubePlayerFullScreen();
super.initState();
}
#override
void dispose() {
_controller.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
const player = YoutubePlayerIFrame();
return YoutubePlayerControllerProvider(controller: _controller, child: player);
}
}
Please Help me and please tell where am i going wrong ?
The cause of the issue is that an event is being tried to be used on YoutubePlayerController after it has been closed on dispose(). A checker can be used to see if _controller is still open.
if (!_controller.isClosed){
// Add events
}

SetState called during build()

I wrote logic with edit mode which allows user to make changes in input field, but when edit mode button is clicked again then input need back to value before editing. And there is a problem with that, because everything works fine but console is showing me this error:
════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for TextEditingController:
setState() or markNeedsBuild() called during build.
This Form widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: Form-[LabeledGlobalKey<FormState>#bcaba]
state: FormState#65267
The widget which was currently being built when the offending call was made was: ProfileInput
dirty
state: _ProfileInputState#32ea5
I know what this error means, but I can't find a place responsible for this. Could someone explain it to me?
class Profile extends StatefulWidget {
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
User _user = User(
username: "name",
);
String? _tmpUsername;
bool _editMode = false;
void _createTemporaryData() {
_tmpUsername = _user.username;
}
void _restoreData() {
_user.username = _tmpUsername!;
}
void _changeMode() {
if (_editMode)
_restoreData();
else
_createTemporaryData();
setState(() {
_editMode = !_editMode;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () => _changeMode(), child: Text("change mode")),
Form(
key: _formKey,
child: ProfileInput(
editMode: _editMode,
user: _user,
onChangeName: (value) {
_user.username = value;
},
),
),
],
),
);
}
}
class ProfileInput extends StatefulWidget {
final bool editMode;
final User user;
final void Function(String value)? onChangeName;
ProfileInput({
required this.editMode,
required this.user,
required this.onChangeName,
});
#override
_ProfileInputState createState() => _ProfileInputState();
}
class _ProfileInputState extends State<ProfileInput> {
TextEditingController _nameController = TextEditingController();
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
_nameController.text = widget.user.username;
return TextFormField(
onChanged: widget.onChangeName,
controller: _nameController,
enabled: widget.editMode,
);
}
}
Put the following line in the initState or use addPostFrameCallback.
_nameController.text = widget.user.username; // goes into initState
initState
#override
void initState() {
super.initState();
_nameController.text = widget.user.username;
}
addPostFrameCallback
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_nameController.text = widget.user.username;
}); // 1
SchedulerBinding.instance.addPostFrameCallback((_) {
_nameController.text = widget.user.username;
}); // 2
// use either 1 or 2.
// rest of the code, return statement.
}
Calling text setter on _nameController would notify all the listener and it's called inside the build method during an ongoing build that causes setState() or markNeedsBuild() called during build.
From Documentation:
Setting this will notify all the listeners of this TextEditingController that they need to update (it calls notifyListeners). For this reason, this value should only be set between frames, e.g. in response to user actions, not during the build, layout, or paint phases.

Flutter using Mixin correctly

I am looking to create two widgets that are similar and therefore should share some code.
I have made.a mixin that looks like this
import 'package:flutter/material.dart';
mixin WidgetShared <T extends StatefulWidget> on State<T>, TickerProviderStateMixin<T>{
AnimationController controlsAnimationController;
Animation<double> controlsAnimation;
bool controlsOpen = false;
#override
void initState() {
super.initState();
print("This was the init");
controlsAnimationController = AnimationController(
duration: const Duration(milliseconds: 400), vsync: this);
controlsAnimation = CurvedAnimation(
parent: controlsAnimationController, curve: Curves.easeInOut);
}
#override
void dispose() {
controlsAnimationController.dispose();
super.dispose();
}
void toggleControls(bool disableClicks) {
if (disableClicks == false) {
if (controlsAnimationController.value > 0) {
setState(() {
controlsOpen = false;
});
controlsAnimationController.reverse();
} else {
setState(() {
controlsOpen = true;
});
controlsAnimationController.forward();
}
}
}
void closeControls() {
if (controlsAnimationController.value > 0) {
setState(() {
controlsOpen = false;
});
controlsAnimationController.reverse();
}
}
}
Which both of the widgets use e.g.
class _Widget1State extends State<MediaViewer>
with TickerProviderStateMixin, SuperMediaViewer{
I can then reference the controlsOpen, controlsOpen and toggleControls() in both widgets easily.
However if i keep changing switching pages on the app I get this error which originates in one of my Provider State objects.
I/flutter (31161): ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
I/flutter (31161): The following assertion was thrown while dispatching notifications for NewUserDetailsState:
I/flutter (31161): setState() or markNeedsBuild() called when widget tree was locked.
I/flutter (31161): This _DefaultInheritedProviderScope<NewUserDetailsState> widget cannot be marked as needing to build
I/flutter (31161): because the framework is locked.
I/flutter (31161): The widget on which setState() or markNeedsBuild() was called was:
I am clearly not doing something right but I'm unsure what. I would be very grateful if someone could point me in the right direction.
Thanks

Android onResume() method equivalent in Flutter

I am working on a Flutter app and need to pop the screen. I tried initState() method but no luck. initState() gets called when I open a class for the first time.
Do we have an equivalent of Android onResume() method in Flutter?
Any ideas?
You can use the WidgetsBindingObserver and check the AppLifeCycleState like this example:
class YourWidgetState extends State<YourWidget> 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) {
if (state == AppLifecycleState.resumed) {
//do your stuff
}
}
}
Take in mind that It will called every time you open the app or go the background and return to the app. (if your widget is active)
If you just want a listener when your Widget is loaded for first time, you can listen using addPostFrameCallback, like this example:
class YourWidgetState extends State<YourWidget> {
_onLayoutDone(_) {
//do your stuff
}
#override
void initState() {
WidgetsBinding.instance?.addPostFrameCallback(_onLayoutDone);
super.initState();
}
}
Info : https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html
Update: Null safety compliance
If you go to another page, then is called when you comeback
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
).then((value) {
_refreshFirstPage();
});
You can accomplish this by registering a didChangeAppLifecycleState observer:
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(final AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
setState(() {
// ...your code goes here...
});
}
}
#override
Widget build(final BuildContext context) {
// ...your code goes here...
}
}
See WidgetsBindingObserver for more information.
Use focus_detector more information can see visibility_detector
Get notified every time your widget appears or disappears from the screen.
Similar to onResume()/onPause() on Android and viewDidAppear()/viewDidDisappear() on iOS.
Focus Detector fires callbacks for you whenever something happens to take or give your widget focus. Such an event might be, for instance, the user:
Navigating to/from another screen;
Turning the device’s screen on/off while your widget is visible;
Switching to/from another app while your widget is visible;
Scrolling your widget in/out the screen;
#override
Widget build(BuildContext context) =>
FocusDetector(
onFocusLost: () {
logger.i(
'Focus Lost.'
'\nTriggered when either [onVisibilityLost] or [onForegroundLost] '
'is called.'
'\nEquivalent to onPause() on Android or viewDidDisappear() on iOS.',
);
},
onFocusGained: () {
logger.i(
'Focus Gained.'
'\nTriggered when either [onVisibilityGained] or [onForegroundGained] '
'is called.'
'\nEquivalent to onResume() on Android or viewDidAppear() on iOS.',
);
},
onVisibilityLost: () {
logger.i(
'Visibility Lost.'
'\nIt means the widget is no longer visible within your app.',
);
},
onVisibilityGained: () {
logger.i(
'Visibility Gained.'
'\nIt means the widget is now visible within your app.',
);
},
onForegroundLost: () {
logger.i(
'Foreground Lost.'
'\nIt means, for example, that the user sent your app to the background by opening '
'another app or turned off the device\'s screen while your '
'widget was visible.',
);
},
onForegroundGained: () {
logger.i(
'Foreground Gained.'
'\nIt means, for example, that the user switched back to your app or turned the '
'device\'s screen back on while your widget was visible.',
);
},
child: Container(),
);