Flutter using Mixin correctly - flutter

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

Related

setState() or markNeedsBuild() called during build. Flutter Provider Package

homepage.dart
#override
void initState() {
super.initState();
_userProfile = Provider.of<UserDataProvider>(context,listen: false).userData;
if (_userProfile['isPaid'] == false) {
Navigator.of(context).pushNamed(PaymentScreen.routeName);
}
}
Error
setState() or markNeedsBuild() called during build.
This error is bugging me. Please help me resolve this.
Thanks.
You can use your code as below:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, (){
_userProfile = Provider.of<UserDataProvider>(context,listen: false).userData;
if (_userProfile['isPaid'] == false) {
Navigator.of(context).pushNamed(PaymentScreen.routeName);
}
});
}

Flutter: Provider shows Exception but app run fine while using default listen:true

I'm Using provider in initState() to call the api but if I use listen:false then it does not update UI and it always shows me loader but if I use listen:true then app works fine but in the terminal it shows me exception and tells me write listen:false.
My UI,
class ChopperNewsCard extends StatefulWidget {
#override
_ChopperNewsCardState createState() => _ChopperNewsCardState();
}
class _ChopperNewsCardState extends State<ChopperNewsCard> {
ScrollController scrollController = ScrollController();
int currentPage = 5;
ChopperApiStore _apiStore = ChopperApiStore();
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_apiStore = Provider.of<ChopperApiStore>(context,);//<--- here it tells me to write listen:false
});
_apiStore.getResponse(currentPage);
scrollController.addListener(() {
if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent) {
if (currentPage < 20) {
currentPage = currentPage + 5;
_apiStore.getResponse(currentPage);
}
}
});
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Observer(builder: (context) {
return Container(
height: height * 0.37,
width: double.infinity,
child: _apiStore.res.articles == null
? CircularProgressIndicator()
: ListView.builder(...),
);
});
}
}
api calling class,
class ChopperApiStore extends _ChopperApiStore with _$ChopperApiStore{}
abstract class _ChopperApiStore with Store{
ApiCall apiCall = ApiCall();
#observable
ChopperNews res = ChopperNews();
#action
Future<void> getResponse(int page) async {
var data = await apiCall.getNews(page);
res = data;
}
}
the error I'm getting,
======== Exception caught by scheduler library =====================================================
The following assertion was thrown during a scheduler callback:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix, write:
Provider.of<ChopperApiStore>(context, listen: false);
It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.
The context used was: ChopperNewsCard(dependencies: [MediaQuery], state: _ChopperNewsCardState#8f6cd)
'package:provider/src/provider.dart':
Failed assertion: line 262 pos 7: 'context.owner.debugBuilding ||
listen == false ||
debugIsInInheritedProviderUpdate'
When the exception was thrown, this was the stack:
#2 Provider.of (package:provider/src/provider.dart:262:7)
#3 _ChopperNewsCardState.initState.<anonymous closure> (package:fruitley/week-5/bonus/chopper/widgets/chopper_news_card.dart:32:28)
#4 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1117:15)
#5 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1063:9)
#6 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:971:5)
...
I think if you want to use listen:true to have the build method called you are suppose to override didChangeDependencies rather then initState Checkout this article it might help https://medium.com/swlh/flutter-provider-and-didchangedependencies-15678f502262
ok I'm dumb. I didn't even need to use addPostFrameCallback.
I just removed it and if I want to use provider outside of widget tree that I must use listen:false as it was showing in the exception so now everything makes sense.

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

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.

setState() or markNeedsBuild() called during build exception prevents me from executing callback

I'm trying to execute a callback function once a timer ends in a particular widget but I keep getting this exception:
I/flutter (16413): Another exception was thrown: setState() or markNeedsBuild() called during build.
So I have this widget called countdown:
class Countdown extends StatefulWidget {
final VoidCallback onCountdownExpire;
Countdown(this.onCountdownExpire);
#override
CountdownState createState() => CountdownState();
}
class CountdownState extends State<Countdown> with TickerProviderStateMixin {
AnimationController controller;
String get timerString {
Duration duration = controller.duration * controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString()}';
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..addStatusListener((AnimationStatus status){
if (status == AnimationStatus.completed)
widget.onCountdownExpire();
});
controller.reverse(from: 1.0);
}
...
...
... // omitted code
}
So once the animation is completed it will call the callback function:
class _QuizPageState extends State<QuizPage> {
... // omitted code
#override
void initState() {
... // omitted code
}
void onCountdownExpire() {
setState(() {
_topContentImage = AssetImage(questions[questionNum++].imagePath);
});
}
... // omitted code
}
I've tried to follow a solution but it does not work and gives me the same exception:
void onCountdownExpire() =>
setState(() {
_topContentImage = AssetImage(questions[questionNum++].imagePath);
});
I also tried this but to no avail:
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..addStatusListener((AnimationStatus status) =>
(status == AnimationStatus.completed) ?
widget.onCountdownExpire():null
);
controller.reverse(from: 1.0);
}
maybe try including 'dart:async':
import 'dart:async';
then try wrapping your call of the onCountdownExpire function in a short-lived Timer():
...
Timer(
Duration(milliseconds:50),
() {
if (status == AnimationStatus.completed)
widget.onCountdownExpire();
},
);
...
this will make the setState() happen outside the build phase of the last frame of your animation.
The error occurs most likely because the Countdown() is being redrawn as part of the redraw of the QuizPage() widget. Adding the Timer() will force the update to happen outside of the build() scope, in an async fashion, and will still achieve the same results without the error.

Flutter - Running animation from scoped model

My app contains a couple of pages. In the appbar I have a selfmade stateful widget with a badge that shows the number of new messages. When I swipe to refresh data the badge will run a small animation if the badge value is changed.
The problem is that the badge value comes from a scoped model. How do I run the animation from the scoped model class. I tried to let the scoped model class hold the animationController as well as a function. It works on the first and second screen. But when I am navigating back to the first page again and pull to refresh. It is like the animationController is in bad state.
Code in the scoped model:
Function _runNotificationAnimation;
set runNotificationAnimation(Function fun) => _runNotificationAnimation = fun;
void _setNotificationCount(int count) {
_notificationCount = count;
if (count > 0 && _runNotificationAnimation != null) {
_runNotificationAnimation();
}
notifyListeners();
}
function that runs the animation
runAnim() {
setState(() {
controller.reset();
controller.forward(from: 0.0);
});
}
Error from flutter:
[VERBOSE-2:shell.cc(184)] Dart Error: Unhandled exception:
NoSuchMethodError: The method 'stop' was called on null.
Receiver: null
Tried calling: stop(canceled: true)
0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
1 AnimationController.stop (package:flutter/src/animation/animation_controller.dart:650:13)
2 AnimationController.value= (package:flutter/src/animation/animation_controller.dart:349:5)
3 AnimationController.reset (package:flutter/src/animation/animation_controller.dart:370:5)
4 NotificationIconState.runAnim (package:volvopenta/widgets/notificaton_icon.dart:38:16)
5 SettingsModel._setNotificationCount (package:volvopenta/scoped-models/settings-model.dart:57:7)
6 SettingsModel.updateAppData (package:volvopenta/scoped-models/settings-model.dart:185:5)
7 MyMachines.build... (package:volvopenta/pages/fleet.dart:83:27)
8<…>
Since your animation will be built in a stateful Widget, it is better to leave the animationController in that stateful Widget and move only the animation (Tween) in the model class. It is essential that you place the notifyListener(); in the controller.addListerner() and not at the end of the function.
class MyModel extends Model{
Animation animation;
runAnimation(controller) {
animation = Tween(begin: 0,0, end:
400).animate(controller);
controller.forward();
controller.addListener((){
notifyListeners();
});
}
}
You can call this function in your stateful widget as follows:
class _MyScreenState extends State<MyScreen> with
SingleTickerProviderStateMixin{
AnimationController controller;
MyModel myModel = MyModel();
#overide
void initState(){
super.initState();
controller = AnimationController(duration: Duration(seconds: 2), vsync:
this);
myModel.runAnimation(controller);
}
//dispose here
#override
Widget build(Buildcontext context){
return ScopedModel<MyModel>(
model: myModel,
child: Scaffold(
body: Text("Hello", style: TextStyle(fontSize: 13 *
controller.value)),
),
);
}
}