Elodie
Elodie

Reputation: 13

Change state of widget outside State class

I'm a beginner on Flutter development and I have some problems. I will try to explain me.

I have a slider bar (custom widget stateful). I want to integrate this widget on my main view, but I have to change the value of the slider (this value can also change with a tap gesture detector).

I tried several things after research on the net.
For example, with a getter for the VerticalSliderState and access VerticalSliderState method to change the value.
This worked only one time after I got a the state is null error, I think I miss a flutter concept with the setState().
Any explanation? Thanks :P

This is my code:

The Custom Widget Stateful:

import 'package:flutter/material.dart';

class VerticalSlider extends StatefulWidget {
  ....
  final double value;
  .....
  final void Function(double) onValueChanged;

   VerticalSlider({
    Key key,
    @required this.height,
    @required this.width,
    this.onValueChanged,
    this.value,
    ....
  }) : super(key: key);


  VerticalSliderState state;

  @override
  VerticalSliderState createState(){
    state = new VerticalSliderState();
    return state ;
  }
}

class VerticalSliderState extends State<Vertical Slider>{
  .....
double _value;

  double _currentHeight;
  Widget _movingDecoratedBox;
  Widget _fixedDecoratedBox;

  void setValue(double value){
    _setValue(value, false, widget.height);
  }

  initState() {
    super.initState();
    _value = widget.value ?? 5.0;
    _currentHeight = _convertValueToHeight();
    _movingDecoratedBox = widget.movingBox ?? DecoratedBox(
        decoration: BoxDecoration(color: Colors.red)
    );
    _fixedDecoratedBox = widget.fixedBox ?? DecoratedBox(
      decoration: BoxDecoration(color: Colors.grey),
    );
  }

  .......... 

  void _onTapUp(TapUpDetails tapDetails) {
    RenderBox renderBox = context.findRenderObject();
    var newHeight = widget.height - renderBox.globalToLocal(tapDetails.globalPosition).dy;
    var newValue = _convertHeightToValue(newHeight);
    setState(() {
      _currentHeight = (widget.height/10.5) * (newValue);
      _setValue(newValue, true, widget.height);

    });

  }

  void _setValue(double newValue, bool userRequest, double height) {
    _value = newValue;
    if(userRequest){
      widget.onValueChanged(_value);
    }
    else{
      setState(() {
        _currentHeight = (height/10.5) * (newValue);
      });
    }
  }

Widget _buildMovingBox() {
    return Align(
      alignment: Alignment.bottomCenter,
      child: SizedBox(
        width: widget.width,
        height: _currentHeight,
        child: _movingDecoratedBox,
      ),
    );
  }
  .......... 

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapUp: _onTapUp,
      child: Stack(
        alignment: AlignmentDirectional.bottomCenter,
        children: <Widget>[
          _buildFixedBox(),
          _buildMovingBox(),
        ],
      ),
    );
  }

My main view:

seekBar1 = new VerticalSlider(
      height: mediaQueryData.size.height /  2.7,
      width: 40.0,
      max: 11.0,
      min: 0.0,
      value: 5.5,
      movingBox: new Container(color: Colors.teal),
      fixedBox: new Container(color: Colors.grey[200]),
      onValueChanged: onValueChanged1,
    );

void onValueChanged1(double newValue) {
      seekBar2.state.setValue(10-newValue);
      print(newValue);
  }

Upvotes: 0

Views: 3265

Answers (2)

chemamolins
chemamolins

Reputation: 20558

One solution would be the one in the full example below. The slider widget updates the value in the main page and it is used to set the value in the other slider.

I have made up some of the code since your snippet was not complete.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  double _value = 9;

  void onValueChanged(double newValue) {
    setState(() {
      _value = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Test"),
      ),
      body: new Center(
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            VerticalSlider(
              height: MediaQuery.of(context).size.height / 2.7,
              width: 40.0,
              max: 11.0,
              min: 0.0,
              value: _value,
              movingBox: new Container(color: Colors.teal),
              fixedBox: new Container(color: Colors.grey[200]),
              onValueChanged: onValueChanged,
            ),
            SizedBox(
              width: 50.0,
            ),
            VerticalSlider(
              height: MediaQuery.of(context).size.height / 2.7,
              width: 40.0,
              max: 11.0,
              min: 0.0,
              value: _value,
              movingBox: new Container(color: Colors.teal),
              fixedBox: new Container(color: Colors.grey[200]),
              onValueChanged: onValueChanged,
            ),
          ],
        ),
      ),
    );
  }
}

class VerticalSlider extends StatefulWidget {
  final double height;
  final double width;
  final double max;
  final double min;
  final double value;
  final Widget movingBox;
  final Widget fixedBox;
  final void Function(double) onValueChanged;

  VerticalSlider({
    Key key,
    @required this.height,
    @required this.width,
    this.onValueChanged,
    this.value,
    this.max,
    this.min,
    this.movingBox,
    this.fixedBox,
  }) : super(key: key);

  @override
  VerticalSliderState createState() {
    return VerticalSliderState();
  }
}

class VerticalSliderState extends State<VerticalSlider> {
  double _value;
  double _currentHeight;

  @override
  void didUpdateWidget(VerticalSlider oldWidget) {
    super.didUpdateWidget(oldWidget);
    _init();
  }

  initState() {
    super.initState();
    _init();
  }

  void _init() {
    _value = widget.value ?? widget.max / 2;
    _currentHeight = _convertValueToHeight();
  }

  double _convertValueToHeight() {
    return _value * widget.height / widget.max;
  }

  double _convertHeightToValue(double height) {
    return height * widget.max / widget.height;
  }

  void _onTapUp(TapUpDetails tapDetails) {
    RenderBox renderBox = context.findRenderObject();
    var newHeight =
        widget.height - renderBox.globalToLocal(tapDetails.globalPosition).dy;
    var newValue = _convertHeightToValue(newHeight);
    widget.onValueChanged(newValue);
    setState(() {
      _currentHeight = newHeight;
    });
  }

  Widget _buildMovingBox() {
    return Align(
      alignment: Alignment.bottomCenter,
      child: SizedBox(
        width: widget.width,
        height: _currentHeight,
        child: widget.movingBox ??
            DecoratedBox(decoration: BoxDecoration(color: Colors.red)),
      ),
    );
  }

  Widget _buildFixedBox() {
    return Align(
      alignment: Alignment.bottomCenter,
      child: SizedBox(
        width: widget.width / 2,
        height: double.infinity,
        child: widget.fixedBox ??
            DecoratedBox(
              decoration: BoxDecoration(color: Colors.grey),
            ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapUp: _onTapUp,
      child: SizedBox(
        width: widget.width,
        height: widget.height,
        child: Stack(
          alignment: AlignmentDirectional.bottomCenter,
          children: <Widget>[
            _buildFixedBox(),
            _buildMovingBox(),
          ],
        ),
      ),
    );
  }
}

Upvotes: 3

Miguel Ruivo
Miguel Ruivo

Reputation: 17756

There are a few things you show keep in mind:

  • You don't want to have a state variable in your Stateful Widget, neither you'll be wanting to access it by myWidget.state. Let the widget itself handle its own state.

  • You are getting null because when you do setState in your VerticalSliderState object, it will rebuild your tree, thus, generating a new updated VerticalSliderState. That means that your state reference will now be null.

  • If you want to access some data within your state, you can take advantage of final callbacks (like your onValueChanged) or you could use Streams.

  • Also, use methods to return Widgets instead of variables, that's just more correct.

  • For example, where you have seekBar2.state.setValue(10-newValue); you'd want to just create a new seekBar2 = VerticalSlider(updated properties) with updated value instead of doing it this way.

Upvotes: 0

Related Questions