Ad

Slider(created In Custom Class) Not Moving Or Updating

- 1 answer

I have just started out android app development using flutter and I am learning along the way.

Details about current project: I am creating a dice simulator, in which user can tap on the drawn dice to roll the dice. I have added vibration effect, toast also. Now I want to let user modify the size of the dice using a slider.

I have found a slider class in documentation. I have also consulted this thread in stackoverflow.Still, I can't grasp the concept completely. I would appreciate any help.

Problem:

Said slider does render how it is supposed to be but I can't seem to be able to drag it.

main.dart

import 'package:flutter/material.dart';
import 'package:do_dice/dice2D.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';

void showToastText(int a) {
  Fluttertoast
      .cancel(); //to clear all scheduled toast call ; this is to make the toast cal seem instant responsive.
  Fluttertoast.showToast(
    msg: '$a',
    toastLength: Toast.LENGTH_SHORT,
    gravity: ToastGravity.BOTTOM,
    backgroundColor: Colors.black87,
    textColor: Colors.yellow,
    fontSize: 14.0,
  );
}

void main() {
  SystemChrome.setSystemUIOverlayStyle(
    SystemUiOverlayStyle(
      statusBarColor: Color.fromRGBO(0, 0, 0, 0.0), //status bar is transparent
    ),
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ROLL THE DICE',
      theme: ThemeData(
        brightness: Brightness.dark,
      ),
      home: MyHomePage(title: 'ROLL THE DICE'),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  Dice2D dice1 = new Dice2D(
    size: 300.0,
    borderWidth: 5.0,
    displayInt: 2,
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Center(
            child: Text(
          widget.title,
        )),
      ),
      body: Column(
        children: <Widget>[
          SliderDiceSize(
            minValue: 100.0,
            maxValue: 300.0,
            title: "Set the size of dice:",
            dice: dice1,
            titleColor: Colors.yellow,
          ),
          SliderBorderWidth(titleColor: Colors.yellow,title: "Set the border width of dice:",dice: dice1,minValue: 1.0,maxValue: 10.0,),
          Expanded(child: Center(child: dice1)),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          //function to display the number on dice as a toast
          showToastText(dice1.getDisplay());
        },
        backgroundColor: Colors.yellow,
        tooltip: "Show the number.",
        child: Icon(Icons.message),
      ),
    );
  }
}

dice2D.dart

    import 'package:flutter/material.dart';
import 'dart:math';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:vibration/vibration.dart';

void vibrateDiceRolling() {
  Vibration.cancel();
  Vibration.vibrate(duration: 50); //default is 500 ms
}

void showToastRolling() {
  Fluttertoast
      .cancel(); //to clear all scheduled toast call ; this is to make the toast call seem instant responsive.
  Fluttertoast.showToast(
    msg: "Roger that!",
    toastLength: Toast.LENGTH_SHORT,
    gravity: ToastGravity.BOTTOM,
    backgroundColor: Colors.black87,
    textColor: Colors.white70,
    fontSize: 14.0,
  );
}

class paintDice2D extends StatelessWidget {
  @override
  @required
  final int display;
  @required
  final double borderWidth;
  @required
  final Color diceColor;
  @required
  final double diceSize;

  paintDice2D(
      {Key key, this.diceSize, this.display, this.borderWidth, this.diceColor})
      : super(key: key);

  Widget drawEmptyBox() {
    return Center(
      child: Container(),
    );
  }

  Widget drawCircleDot() {
    return Center(
      child: Container(
        decoration: BoxDecoration(shape: BoxShape.circle, color: diceColor),
      ),
    );
  }

  Widget build(BuildContext context) {
    double divSize = (diceSize - 2 * borderWidth) / 3;
    return Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(10.0)),
        border: Border.all(
            color: diceColor, width: borderWidth, style: BorderStyle.solid),
      ),
      height: diceSize,
      width: diceSize,
      child: Row(
        children: <Widget>[
          Column(
            children: <Widget>[
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 3 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(), // condition == true ?  {code for true } : {code for false}
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child:
                    (display == 6) == true ? drawCircleDot() : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 2 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 1 || display == 3 || display == 5) == true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: drawEmptyBox(),
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 2 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child:
                    (display == 6) == true ? drawCircleDot() : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 3 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class Dice2D extends StatefulWidget {
  @override
  @required
  double size;
  @required
  double borderWidth;
  @required
  int displayInt;

  Dice2D({
    Key key,
    this.size,
    this.borderWidth,
    this.displayInt,
  }) : super(key: key);

  int getDisplay() {
    return this.displayInt;
  }

  Dice2DState createState() {
    return new Dice2DState();
  }
}

class Dice2DState extends State<Dice2D> {
  @override
  Widget build(BuildContext context) {
    int nextDisplay = Random().nextInt(6) + 1;
        void rollDice() {
            setState(() {
                widget.displayInt = nextDisplay;
                nextDisplay = Random().nextInt(6) + 1;
                showToastRolling();
                vibrateDiceRolling();
            });
        }

        return FlatButton(
            onPressed: () {
                rollDice();
            },
            padding: EdgeInsets.all(0.0),
            child: paintDice2D(
                display: widget.displayInt,
                borderWidth: widget.borderWidth,
                diceSize: widget.size,
                diceColor: Colors.yellow,
            ),
        );
    }
  }


class SliderDiceSize extends StatefulWidget {
  @required final String title;
  @required Dice2D dice;
  @required final double minValue;
  @required final double maxValue;
  @required final Color titleColor;

  SliderDiceSize({Key key, this.title,  this.dice, this.titleColor, this.maxValue, this.minValue}):super(key:key);

  @override
  SliderDiceSizeState createState() {
    return new SliderDiceSizeState();
  }
}

class SliderDiceSizeState extends State<SliderDiceSize> {

  void setDiceSize(double a) {
    setState(() {
      int diceint = widget.dice.getDisplay(); //**
      widget.dice.displayInt = diceint; //**
      widget.dice.size = a; //**
    });
  }

  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: new Text(
            widget.title,
            style: TextStyle(color: widget.titleColor, fontSize: 16.0),
          ),
        ),
        Container(
          child: new Slider(
            value: widget.dice.size, //**
            onChanged: (double value) => setDiceSize(value), //**
            max: widget.maxValue,
            min: widget.minValue,
            activeColor: Colors.grey,
            inactiveColor: Colors.white12,
          ),
        ),
      ],
    );
  }
}

class SliderBorderWidth extends StatefulWidget {
  @required
  final String title;
  @required
  Dice2D dice;
  @required final double minValue;
  @required final double maxValue;
  @required final Color titleColor;

  SliderBorderWidth(
      {Key key,
      this.dice,
      this.title,
      this.minValue,
      this.maxValue,
      this.titleColor});

  @override
  SliderBorderWidthState createState() {
    return new SliderBorderWidthState();
  }
}

class SliderBorderWidthState extends State<SliderBorderWidth> {
    void setBorderWidth(double a) {
    setState(() {
        int diceint = widget.dice.getDisplay();  //**
        widget.dice.borderWidth = a;  //**
    });}

  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: new Text(
          widget.title,
            style: TextStyle(color: widget.titleColor, fontSize: 16.0),
          ),
        ),
        Container(
          child: new Slider(
            value: widget.dice.borderWidth,  //**
            onChanged: (double value) => setBorderWidth(value),  //**
            max: widget.maxValue,
            min: widget.minValue,
            activeColor: Colors.grey,
            inactiveColor: Colors.white12,
          ),
        ),
      ],
    );
  }
}

EDIT

Below is new full code using callback function. I have marked updated lines of code using ~~ . Earlier problem of slider not being able to drag has popped up again. My suspicion of lines which are causing problems are marked by comment in the code.

main.dart

import 'package:flutter/material.dart';

import 'package:do_dice/dice2D.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';

void showToastText(int a) {
  Fluttertoast
      .cancel(); //to clear all scheduled toast call ; this is to make the toast cal seem instant responsive.
  Fluttertoast.showToast(
    msg: '$a',
    toastLength: Toast.LENGTH_SHORT,
    gravity: ToastGravity.BOTTOM,
    backgroundColor: Colors.black87,
    textColor: Colors.yellow,
    fontSize: 14.0,
  );
}

void main() {
  SystemChrome.setSystemUIOverlayStyle(
    SystemUiOverlayStyle(
      statusBarColor: Color.fromRGBO(0, 0, 0, 0.0), //status bar is transparent
    ),
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ROLL THE DICE',
      theme: ThemeData(
        brightness: Brightness.dark,
      ),
      home: MyHomePage(title: 'ROLL THE DICE'),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  Dice2D dice1 = new Dice2D(
    size: 300.0,
    borderWidth: 5.0,
    displayInt: 2,
  );

//~~ added this function to serve as callback
updateDiceSize(Dice2D dice,double size){
    setState((){  
        dice.size = size;
    });
 }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Center(
            child: Text(
          widget.title,
        )),
      ),
      body: Column(
        children: <Widget>[
          SliderDiceSize(updateDiceSizeCallback: updateDiceSize(dice1,dice1.size),  //~~passing the callback
            minValue: 100.0,
            maxValue: 300.0,
            title: "Set the size of dice:",
            dice: dice1,
            titleColor: Colors.yellow,
          ),
          Expanded(child: Center(child: dice1)),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          //function to display the number on dice as a toast
          showToastText(dice1.getDisplay());
        },
        backgroundColor: Colors.yellow,
        tooltip: "Show the number.",
        child: Icon(Icons.message),
      ),
    );
  }
}

Dice2D.dart

import 'package:flutter/material.dart';
import 'dart:math';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:vibration/vibration.dart';

void vibrateDiceRolling() {
  Vibration.cancel();
  Vibration.vibrate(duration: 50); //default is 500 ms
}

void showToastRolling() {
  Fluttertoast
      .cancel(); //to clear all scheduled toast call ; this is to make the toast call seem instant responsive.
  Fluttertoast.showToast(
    msg: "Roger that!",
    toastLength: Toast.LENGTH_SHORT,
    gravity: ToastGravity.BOTTOM,
    backgroundColor: Colors.black87,
    textColor: Colors.white70,
    fontSize: 14.0,
  );
}

class PaintDice2D extends StatelessWidget {
  @override
  @required
  final int display;
  @required
  final double borderWidth;
  @required
  final Color diceColor;
  @required
  final double diceSize;

  PaintDice2D(
      {Key key, this.diceSize, this.display, this.borderWidth, this.diceColor})
      : super(key: key);

  Widget drawEmptyBox() {
    return Center(
      child: Container(),
    );
  }

  Widget drawCircleDot() {
    return Center(
      child: Container(
        decoration: BoxDecoration(shape: BoxShape.circle, color: diceColor),
      ),
    );
  }

  Widget build(BuildContext context) {
    double divSize = (diceSize - 2 * borderWidth) / 3;
    return Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(10.0)),
        border: Border.all(
            color: diceColor, width: borderWidth, style: BorderStyle.solid),
      ),
      height: diceSize,
      width: diceSize,
      child: Row(
        children: <Widget>[
          Column(
            children: <Widget>[
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 3 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(), // condition == true ?  {code for true } : {code for false}
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child:
                    (display == 6) == true ? drawCircleDot() : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 2 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 1 || display == 3 || display == 5) == true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: drawEmptyBox(),
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 2 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child:
                    (display == 6) == true ? drawCircleDot() : drawEmptyBox(),
              ),
              Container(
                width: divSize,
                height: divSize,
                padding: EdgeInsets.all(divSize / 5),
                child: (display == 3 ||
                            display == 4 ||
                            display == 5 ||
                            display == 6) ==
                        true
                    ? drawCircleDot()
                    : drawEmptyBox(),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class Dice2D extends StatefulWidget {
  @override
  @required
  double size;
  @required
  double borderWidth;
  @required
  int displayInt;

  Dice2D({
    Key key,
    this.size,
    this.borderWidth,
    this.displayInt,
  }) : super(key: key);

  int getDisplay() {
    return this.displayInt;
  }

  Dice2DState createState() {
    return new Dice2DState();
  }
}

class Dice2DState extends State<Dice2D> {
  @override
  Widget build(BuildContext context) {
    int nextDisplay = Random().nextInt(6) + 1;
        void rollDice() {
            setState(() {
                widget.displayInt = nextDisplay;
                nextDisplay = Random().nextInt(6) + 1;
                showToastRolling();
                vibrateDiceRolling();
            });
        }

        return FlatButton(
            onPressed: () {
                rollDice();
            },
            padding: EdgeInsets.all(0.0),
            child: PaintDice2D(
                display: widget.displayInt,
                borderWidth: widget.borderWidth,
                diceSize: widget.size,
                diceColor: Colors.yellow,
            ),
        );
    }
  }


class SliderDiceSize extends StatefulWidget {
  @required final String title;
  @required Dice2D dice;
  @required final double minValue;
  @required final double maxValue;
  @required final Color titleColor;
  @required Function updateDiceSizeCallback;  //~~
  SliderDiceSize({Key key, this.title,  this.dice, this.titleColor, this.maxValue, this.minValue,this.updateDiceSizeCallback}):super(key:key);  //~~

  @override
  SliderDiceSizeState createState() {
    return new SliderDiceSizeState();
  }
}

class SliderDiceSizeState extends State<SliderDiceSize> {
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: new Text(
            widget.title,
            style: TextStyle(color: widget.titleColor, fontSize: 16.0),
          ),
        ),
        Container(
          child: new Slider(
            value: widget.dice.size,
            onChanged: (double value) => widget.updateDiceSizeCallback, //~~ I believe something needs to change here for slider to be able to drag.
            max: widget.maxValue,
            min: widget.minValue,
            activeColor: Colors.grey,
            inactiveColor: Colors.white12,
          ),
        ),
      ],
    );
  }
}

since, this is my first question here, I apologize for messed up code and variable names.

Ad

Answer

You are saving, and using, the properties inside the widget instead of the state class. As I understand the widget of the StatefulWidget is just like a StatelessWidget. The actual state should be in the State<> class that the StatefulWidget creates.

Try adding the property for the dice (Dice2D) to the actual state class.

[edit]

Okay so I've taken a closer look at your code and I think you are not updating the right State. Your sliders are only updating their own State (which is what setState() does). But they should be updating their parents State (_MyHomePageState) because that will trigger the MyHomePageWidget to rebuild with the new State. Your Die is rebuilding properly when clicking on it because this triggers the Dice2DState to update it's own state which DOES trigger a rebuild of the Dice2D Widget.

So that's the problem. Now how to solve this. You need a way to trigger a setState() on the _MyHomePageState. There are different ways to do this. But because you are just trying out some stuff I suggest you use callbacks.

In your _MyHomePageState you add the following function:

// Could also be separate functions for each property
updateDice({double size, double borderWidth, int displayInt}) {
    setState(() {
     if(size != null) dice1.size = size;
     if(borderWidth != null) dice1.borderWidth = borderWidth;
     if(displayInt != null) dice1.displayInt = displayInt; 
    });
  }

This will be used to update the Dice.

Your sliders will have to make use off this so pass this function as a callback in the constructor of the sliders: SliderBorderWidth(updateDice ... rest of your params ...)

and store it in your slider widget: Function updateDiceCallback

more about passing functions here

Now your sliders can use this callback to update the state of your dice. Because your dice's state is now being managed higher up the hierarchy your Dice Widget should be a StatelessWidget because it get's redrawn anyway and is just a visual representation. You should also pass the UpdateDice-callback to it so you can wire it up to the OnPressed event or something and make it update the number.

I hope this make any sense to you. I was trying to fix your code but I think you need to understand the way state works better. I will create an example for you and add it.

[sample]

Try this, maybe it will help you understand. Just past it in the main.dart of a new project.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ROLL THE DICE',
      theme: ThemeData(
        brightness: Brightness.dark,
      ),
      home: MyHomePage(title: 'ROLL THE DICE'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  Dice2D dice1 = new Dice2D(
    size: 150.0,
    displayInt: 2,
  );

  updateDice({double size, int displayInt}) {
    setState(() {
     if(size != null) dice1.size = size;
     if(displayInt != null) dice1.displayInt = displayInt; 
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Center(
            child: Text(
          widget.title,
        )),
      ),
      body: Column(
        children: [
          Slider(
            value: dice1.size,
            min: 100.0,
            max: 200.0,
            onChanged: (value){
            updateDice(size: value);
          },),
          DiceWidget(dice1.size, dice1.displayInt, updateDice),
        ],
      ),
    );
  }
}

class DiceWidget extends StatelessWidget {

  final int _number;
  final double _size;
  final Function _onPressed;

  DiceWidget(this._size, this._number, this._onPressed);

  @override
  Widget build(BuildContext context) {
    return Center(child: RaisedButton(
      onPressed: (){
        _onPressed(displayInt: Random().nextInt(5)+1);
      },
      child: Text("$_number", style: TextStyle(fontSize: _size),)),);
  }
}

class Dice2D {
  var size;
  var displayInt;

  Dice2D({this.size,this.displayInt});
}
Ad
source: stackoverflow.com
Ad