Bloc stream not updating properly

2,146

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');
                  } : () {},
                ),
              ),
            ),
          ],
        ),
      ],
    );
  }
}  
Share:
2,146
Bisclavret
Author by

Bisclavret

Updated on December 11, 2022

Comments

  • Bisclavret
    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
      nonybrighto almost 5 years
      Your 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
      Bisclavret almost 5 years
      Thank 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
    Bisclavret almost 5 years
    Oh 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
    nonybrighto almost 5 years
    You're welcome! Really happy it all worked out well! :-)