Bloc stream not updating properly
I've made some modifications with comments in the code. Can't test it to be sure it's working, so just try it out.
SinglePlayer.dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:tv_series_jokes/blocs/bloc_provider.dart';
import 'package:ultimate_mtg/dropdownmenu.dart';
import 'package:ultimate_mtg/model/colorBloc.dart';
class SinglePlayerMode extends StatefulWidget {
@override
SinglePlayerModeParentState createState() => SinglePlayerModeParentState();
}
class SinglePlayerModeParentState extends State<SinglePlayerMode> {
ColorBloc colorBloc = ColorBloc(); // our color bloc instance
@override
void initState() {
super.initState();
SystemChrome.setEnabledSystemUIOverlays([]);
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft,]);
Screen.keepOn(true);
}
@override
dispose() {
colorBloc.dispose();
super.dispose();
}
_changeColourButton() {
colorBloc.changeColor();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _exitApp(context),
child: Scaffold(
body: BlocProvider<ColorBloc>( // DropDownMenu can now access the bloc with this
bloc: colorBloc,
child: Container(
child: Row(
children: <Widget> [
FloatingActionButton(
backgroundColor: Colors.blue,
heroTag: null,
onPressed: _changeColourButton,
child: Text(
'change',
),
),
dropDownMenu(
singlePlayerCallbacks: callBacks,
),
],
),
),
),
),
);
}
}
DropDownMenu.dart
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:ultimate_mtg/model/colorBloc.dart';
import 'package:ultimate_mtg/model/blocprovider.dart';
// ignore: camel_case_types
class dropDownMenu extends StatefulWidget {
final Function() onPressed;
final String tooltip;
final IconData icon;
final _callback;
dropDownMenu({Key key, this.onPressed, this.tooltip, this.icon, @required void singlePlayerCallbacks(String callBackType), @required StatefulWidget styleMenu } ):
_callback = singlePlayerCallbacks;
@override
dropDownMenuState createState() => dropDownMenuState();
}
// ignore: camel_case_types
class dropDownMenuState extends State<dropDownMenu>
with SingleTickerProviderStateMixin {
bool isOpened = false;
AnimationController _animationController;
Animation<double> _translateButton;
Curve _curve = Curves.easeOut;
double _fabHeight = 58;
double menuButtonSize = 55;
Color menuButtonTheme;
ColorBloc colorBloc; // we no longer create the instance here.
@override
initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 600))
..addListener(() {
setState(() {});
});
_translateButton = Tween<double>(
begin: 0.0,
end: _fabHeight,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.0,
1.0,
curve: _curve,
),
));
colorBloc = BlocProvider.of<ColorBloc>(context); // Getting the color bloc from the widget tree
super.initState();
}
@override
dispose() {
_animationController.dispose();
super.dispose();
}
animate() {
if (!isOpened) {
_animationController.forward();
} else {
_animationController.reverse();
}
isOpened = !isOpened;
}
Widget backgroundColour() {
return StreamBuilder(
initialData: Colors.blue,
stream: colorBloc.colorStream,
builder: (BuildContext context, snapShot) => Container(
width: menuButtonSize,
height: menuButtonSize,
child: RawMaterialButton(
shape: CircleBorder(),
fillColor: Colors.black,
elevation: 5.0,
onPressed: (){},
child: Container(
height: menuButtonSize - 3,
width: menuButtonSize - 3,
decoration: BoxDecoration(
color: snapShot.data,
shape: BoxShape.circle,
),
child: Image.asset(
'lib/images/background_colour.png',
scale: 4,
),
),
),
),
);
}
Widget toggle() {
return Transform.rotate(
angle: _animationController.value * (pi * 2),
child: Container(
width: menuButtonSize,
height: menuButtonSize,
child: RawMaterialButton(
shape: CircleBorder(),
fillColor: Colors.black,
elevation: 5.0,
onPressed: animate,
child: SizedBox(
height: menuButtonSize - 3,
width: menuButtonSize - 3,
child: Image.asset('lib/images/ic_launcher.png'),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget> [
// Removed the BlocProvider widget here. It wasn't working anything and was creating a separate bloc instance
// I also see why you tried to make us of the blocprovider in the backgroundColour method and it gave null.Couldn't
// have worked from that context.
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Stack(
children: <Widget>[
Transform(
transform: Matrix4.translationValues(
0,
_translateButton.value,
0,
),
child: backgroundColour(),
),
toggle(),
],
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
height: menuButtonSize,
width: menuButtonSize,
child: Opacity(
opacity: 0.0,
child: FloatingActionButton(
heroTag: null,
onPressed: animate,
),
),
),
SizedBox(
height: 3.0,
),
Container(
height: menuButtonSize,
width: menuButtonSize,
child: Opacity(
opacity: 0.0,
child: FloatingActionButton(
heroTag: null,
onPressed: isOpened == true? (){
widget?._callback('background');
} : () {},
),
),
),
],
),
],
);
}
}
Bisclavret
Updated on December 11, 2022Comments
-
Bisclavret over 1 year
This is a direct follow on from: Bloc architecture "the getter x was called on null." which was resolved by @nonybrighto
The issue now is that although I am getting no error from the application any more, the logic is failing somewhere as the colours are not updating, they just remain blue. If I call: colorBloc.changeColor(); from either a callback to the child (dropdownmenu) itself, or directly from the parent, it just doesn't actually update the colours of these widget buttons. They are always blue.
Is there something additional I have to do to get my button widgets to actually update?
Is any additional information required?
Edit: The parent and child classes, and how I am trying to use the bloc.
dropdownmenu.dart
import 'package:flutter/material.dart'; import 'dart:math'; import 'package:ultimate_mtg/model/colorBloc.dart'; import 'package:ultimate_mtg/model/blocprovider.dart'; // ignore: camel_case_types class dropDownMenu extends StatefulWidget { final Function() onPressed; final String tooltip; final IconData icon; final _callback; dropDownMenu({Key key, this.onPressed, this.tooltip, this.icon, @required void singlePlayerCallbacks(String callBackType), @required StatefulWidget styleMenu } ): _callback = singlePlayerCallbacks; @override dropDownMenuState createState() => dropDownMenuState(); } // ignore: camel_case_types class dropDownMenuState extends State<dropDownMenu> with SingleTickerProviderStateMixin { bool isOpened = false; AnimationController _animationController; Animation<double> _translateButton; Curve _curve = Curves.easeOut; double _fabHeight = 58; double menuButtonSize = 55; Color menuButtonTheme; ColorBloc colorBloc = ColorBloc(); @override initState() { _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 600)) ..addListener(() { setState(() {}); }); _translateButton = Tween<double>( begin: 0.0, end: _fabHeight, ).animate(CurvedAnimation( parent: _animationController, curve: Interval( 0.0, 1.0, curve: _curve, ), )); super.initState(); } @override dispose() { _animationController.dispose(); colorBloc.dispose(); super.dispose(); } animate() { if (!isOpened) { _animationController.forward(); } else { _animationController.reverse(); } isOpened = !isOpened; } Widget backgroundColour() { return StreamBuilder( initialData: Colors.blue, stream: colorBloc.colorStream, builder: (BuildContext context, snapShot) => Container( width: menuButtonSize, height: menuButtonSize, child: RawMaterialButton( shape: CircleBorder(), fillColor: Colors.black, elevation: 5.0, onPressed: (){}, child: Container( height: menuButtonSize - 3, width: menuButtonSize - 3, decoration: BoxDecoration( color: snapShot.data, shape: BoxShape.circle, ), child: Image.asset( 'lib/images/background_colour.png', scale: 4, ), ), ), ), ); } Widget toggle() { return Transform.rotate( angle: _animationController.value * (pi * 2), child: Container( width: menuButtonSize, height: menuButtonSize, child: RawMaterialButton( shape: CircleBorder(), fillColor: Colors.black, elevation: 5.0, onPressed: animate, child: SizedBox( height: menuButtonSize - 3, width: menuButtonSize - 3, child: Image.asset('lib/images/ic_launcher.png'), ), ), ), ); } @override Widget build(BuildContext context) { return Stack( children: <Widget> [ BlocProvider( bloc: ColorBloc(), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Stack( children: <Widget>[ Transform( transform: Matrix4.translationValues( 0, _translateButton.value, 0, ), child: backgroundColour(), ), toggle(), ], ), ], ), ), Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Container( height: menuButtonSize, width: menuButtonSize, child: Opacity( opacity: 0.0, child: FloatingActionButton( heroTag: null, onPressed: animate, ), ), ), SizedBox( height: 3.0, ), Container( height: menuButtonSize, width: menuButtonSize, child: Opacity( opacity: 0.0, child: FloatingActionButton( heroTag: null, onPressed: isOpened == true? (){ widget?._callback('background'); } : () {}, ), ), ), ], ), ], ); } }
singleplayer.dart
import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:ultimate_mtg/dropdownmenu.dart'; import 'package:ultimate_mtg/model/colorBloc.dart'; class SinglePlayerMode extends StatefulWidget { @override SinglePlayerModeParentState createState() => SinglePlayerModeParentState(); } class SinglePlayerModeParentState extends State<SinglePlayerMode> { ColorBloc colorBloc = ColorBloc(); @override void initState() { super.initState(); SystemChrome.setEnabledSystemUIOverlays([]); SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft,]); Screen.keepOn(true); } @override dispose() { colorBloc.dispose(); super.dispose(); } _changeColourButton() { colorBloc.changeColor(); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () => _exitApp(context), child: Scaffold( body: Container( child: Row( children: <Widget> [ FloatingActionButton( backgroundColor: Colors.blue, heroTag: null, onPressed: _changeColourButton, child: Text( 'change', ), ), dropDownMenu( singlePlayerCallbacks: callBacks, ), ], ), ), ), ); } }
That's basically how I am trying to do it.
-
nonybrighto almost 5 yearsYour
dropDownMenu
class gets rebuilt when setState is called or any other action that can cause a rebuild form its parent. so your ColorBloc gets recreated too and will lose any previous change. ColorBloc should be in the parent's initState and accessed through BlocProvider -
Bisclavret almost 5 yearsThank you @nonybrighto, that makes sense! Unfortunately, I have tried to resolve it and I cannot. When you say I should put it in the parent's initState, what do I put here? I currently have nothing about any stream in any of my initStates. I'll post the code I am using as an edit to this question but it isn't much different to my original as I just don't understand how to change it to what you are suggesting.
-
-
Bisclavret almost 5 yearsOh my god, this works! You beautiful man. Thank you so much. You have saved me so much time on this, I don't know how I can thank you enough. Absolute legend. I can now set states on this animated menu not only from the parent but from other child widgets. Again, thank you. I hope this helps other people having issues with bloc architecture.
-
nonybrighto almost 5 yearsYou're welcome! Really happy it all worked out well! :-)