Flutter Slider Inside A Extracted Widget
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,
),
),
),
);
}
}
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.
Related Questions
- → How do you create a 12 or 24 mnemonics code for multiple cryptocurrencies (ETH, BTC and so on..)
- → Flutter: input text field don't work properly in a simple example..... where am I wrong?
- → Can I customize the code formatting of Dart code in Atom?
- → Is it possible to develop iOS apps with Flutter on a Linux virtual machine?
- → Display SnackBar in Flutter
- → JSON ObjectMapper in Flutter
- → Material flutter app source code
- → TabBarSelection No such method error
- → How do I set the animation color of a LinearProgressIndicator?
- → Add different routes/screens to Flutter app
- → Is there a way to get the size of an existing widget?
- → How to share a file using flutter
- → Is there an easy way to find particular text built from RichText in a Flutter test?