Ad

Flutter Slider Inside A Extracted Widget

- 1 answer

OK, I'm trying to be more specific. My goal is to subdivide parts of my code into seperate widgets so that the main tree remains as clean as possible.

In the following example I have a main tree in a StatefulWidget. I have successfully converted the container with the button into its own widget with Extract to Widget. the setState of the button works in via the constructor.

Now I want to do the same with the second container that holds the slider. But I don't know how to treat the Slider onChanged setState with the constructor because the setState doesn't work inside the extracted Widget.

Here is the Code sample with the working button as extracted Widget and the slider still inside the main tree.

import 'package:flutter/material.dart';

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

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

class MyStfl extends StatefulWidget {
  @override
  _MyStflState createState() => _MyStflState();
}

class _MyStflState extends State<MyStfl> {
  double sliderPos = 0.0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          color: Color(0x80061A28),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: MyCardA(
                aktion: () {
                  setState(() {
                    print('Button Pressed');
                  });
                },
              ),
            ),
            Expanded(
              child: Container(
                child: Slider(
                  divisions: 48,
                  value: sliderPos,
                  min: 0.0,
                  max: 24.0,
                  onChanged: (double newValue) {
                    setState(
                      () {
                        sliderPos = double.parse(newValue.toStringAsFixed(1));
                        print(sliderPos);
                      },
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class MyCardA extends StatelessWidget {
  MyCardA({this.aktion});
  final Function aktion;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(20.0),
      padding: EdgeInsets.all(20.0),
      color: Color(0x80125889),
      child: GestureDetector(
        onTap: aktion,
        child: Text(
          'MY BUTTON',
          style: TextStyle(
            color: Color(0xFF3D3939),
            fontSize: 22.0,
          ),
        ),
      ),
    );
  }
}
Ad

Answer

First things first, there's one thing you want to change in your CardA component. Instead of typing your aktion parameter as Function (which is very generic), you should give it a type of void Function(), saying: it is a function that return nothing (void). This is what the onTap param of the GestureDetector expects. Passing any other type of function is not something you want to do, as it will break your app. Be as specific as you can when typing your parameters. You could shorten this void Function() type to VoidCallback. It's the same thing.

I also made the parameter required. This is not necessary, but it is what you want (I think). If you want to be able to emit this argument, get rid of the required keyword and type your aktion as void Function()?. The ? indicates that you could pass either void Function() or null.

class MyCardA extends StatelessWidget {
  MyCardA({required this.aktion});
  final void Function() aktion;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(20.0),
      padding: EdgeInsets.all(20.0),
      color: Color(0x80125889),
      child: GestureDetector(
        onTap: aktion,
        child: Text(
          'MY BUTTON',
          style: TextStyle(
            color: Color(0xFF3D3939),
            fontSize: 22.0,
          ),
        ),
      ),
    );
  }
}

For 'MyCardB', you need to set up a callback function that takes a parameter. It's a bit more complex.

A Slider component has an onChanged parameter, which is triggered when the slider position is changed. This gives you a double (the new position) and allows you to do stuff with it. This is typed as void Function(double). We have a function that gets a double as a parameter, which return nothing (void).

You can see how this looks in your custom component below.

class MyCardB extends StatelessWidget {
  const MyCardB({
    required this.sliderPos,
    required this.onChanged,
  });

  final double sliderPos;
  final void Function(double) onChanged;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Slider(
        divisions: 48,
        value: sliderPos,
        min: 0.0,
        max: 24.0,
        onChanged: onChanged,
      ),
    );
  }
}

And you use this new component in your main component as follows.

Expanded(
  child: MyCardB(
    sliderPos: sliderPos,
    onChanged: (double newValue) {
      setState(
        () {
          sliderPos = double.parse(newValue.toStringAsFixed(1));
        },
      );
    },
  ),
),

Long story short, you want to utilise callback functions. I urge you to read up on them if it's new to you, they're incredibly important. As you rightly noticed, you can't execute the logic inside the child widget; you want to keep that inside its parent. You manage this by creating these Callback functions.

You create the callback function in the parent. You pass the function to your child widgets. Once triggered, a function will be executed inside the parent. The child only knows that it must trigger the function, but all the logic is hidden from it.

Ad
source: stackoverflow.com
Ad