Let's say I am constantly changing the value of a Slider and make some call to a server in the onChanged callback function. Is it possible to change the minimum time period between the callbacks efficiently?
Slider showSoundBar(){
return Slider(
value: this.volume,
activeColor: Colors.blue,
onChanged: (vol){
// Don't send too often
send('volume', vol);
},
);
}
You could do something like this using a Timer from dart:async..
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Timer timer;
int timePassedInMilliseconds = 1500;
#override
void initState(){
super.initState();
timer = Timer.periodic(Duration(milliseconds: 100), (_){
timePassedInMilliseconds = timePassedInMilliseconds + 100;
});
}
#override
Widget build(BuildContext context) {
return Slider(
value: 10,
activeColor: Colors.blue,
onChanged: (vol){
// Don't send too often
if(timePassedInMilliseconds > 1500){
send('volume', vol);
timePassedInMilliseconds = 0;
}
},
);
}
void send(String sendWhat, double value){
}
#override
void dispose(){
timer.cancel();
super.dispose();
}
}
Related
I have to make a countdown timer for which i have written this code. but it is not updating it's UI even after using setState.
I want to refresh remaining time every second.
class CustomTimer extends StatefulWidget {
final DateTime endtime;
const CustomTimer({Key? key, required this.endtime}) : super(key: key);
#override
State<CustomTimer> createState() => _CustomTimerState();
}
class _CustomTimerState extends State<CustomTimer> {
Timer? timer;
Duration remainTime = Duration();
newTime() {
// print('hi');
setState(() {
final seconds = remainTime.inSeconds - 1;
remainTime = Duration(seconds: seconds);
});
}
#override
void initState() {
remainTime = widget.endtime.difference(DateTime.now());
timer = Timer.periodic(const Duration(seconds: 1), (_) => newTime());
super.initState();
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: Text(remainTime.toString().split('.')[0].split('')[0] == '-'
? "00:00:00"
: remainTime.toString().split('.')[0]),
);
}
}
When I try to setState on a fast Timer.periodic, the raster performance is decreased and increased suddenly:
The code is a simple setState inside a Timer.periodic:
class _MyHomePageState extends State<MyHomePage> {
Timer? timer;
#override
void initState() {
super.initState();
timer = Timer.periodic(
const Duration(milliseconds: 1000 ~/ 60),
(timer) {
setState(() {});
},
);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Center(child: Container(width: 100, height: 100, color: Colors.red));
}
}
This happens to me in Windows.
This is what the performance graph of a "normal" timed frame looks like:
This and a frame that takes a lot of time:
Why does this happen? How can i prevent it?
I'm trying to create a simple widget which counts down from 10 upon getting built. I expected this to start counting down but it remains stuck on 10. Could anyone see what is going wrong here?
class GameTimer extends StatefulWidget {
const GameTimer({
Key? key,
}) : super(key: key);
#override
State<GameTimer> createState() => _GameTimerState();
}
class _GameTimerState extends State<GameTimer> {
initState() {
_startTimer();
}
int _counter = 10;
late Timer _timer;
void _startTimer() {
_counter = 10;
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState() {
_counter--;
}
});
}
#override
Widget build(BuildContext context) {
return SizedBox(
child: Text('$_counter',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
)));
}
}
You need to #override before initState and inside it you need to call super.initState()
First of all your initState isnt overriding the super method. so it should be like this:
#override
initState() {
_startTimer();
super.initState();
}
Second, in your _startTimer you are declaring a new function called setState. you missed a couple of parentheses:
setState(() {
_counter--;
});
There are errors in your code
add super.initState(); in init state
seconde your call back function should looks like
setState((){
_counter--;
});
You are almost there !!!!!!
You have missed wrapping the setState callback in parentheses.
Updated code:
class GameTimer extends StatefulWidget {
const GameTimer({
Key? key,
}) : super(key: key);
#override
State<GameTimer> createState() => _GameTimerState();
}
class _GameTimerState extends State<GameTimer> {
initState() {
_startTimer();
}
int _counter = 10;
late Timer _timer;
void _startTimer() {
_counter = 10;
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
_counter--;
});
});
}
#override
Widget build(BuildContext context) {
return SizedBox(
child: Text('$_counter',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
)));
}
}
In a Flutter Desktop app, I want to know if, when a user clicks on a button with the mouse, they were also holding down a key (like Shift, Control, Alt etc).
How can this be done?
EDIT
My initial question wasn't clear enough.
I have a dynamic list of checkboxes and I want to use SHIFT+click to select everything between the last selected one and the one that was selected with SHIFT down.
I have looked at FocusNode but that seems to only work for 1 element.
This can be done with a FocusNode.
You'll need a stateful widget where you can use initialize the node. You need to attach the node and define the callback that is called on keyboard presses. Then you can request focus from the node with requestFocus so that the node receives the keyboard events.
You'll also need to call _nodeAttachment.reparent(); in your build method. You should also dispose the node in dispose.
The example below prints true or false for whether the shift key is pressed when the button is pressed. This can be easily expanded to other keys like control and alt with the isControlPressed and isAltPressed properties.
Full example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final FocusNode focus;
late final FocusAttachment _nodeAttachment;
bool isShiftPressed = false;
#override
void initState() {
super.initState();
focus = FocusNode(debugLabel: 'Button');
_nodeAttachment = focus.attach(context, onKey: (node, event) {
isShiftPressed = event.isShiftPressed;
});
focus.requestFocus();
}
#override
void dispose() {
focus.dispose();
super.dispose();
}
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return TextButton(
onPressed: () {
print(isShiftPressed);
},
child: Text('Test'),
);
}
}
You can still use this solution for your more specific problem. Wrap the above example around your list of checkboxes. You can do a bit of simple logic to get your intended behavior. If what I have here is not exact, you should be able to easily modify it to your needs. This proves that you can use this method for your need, however, even if some details in the logic are not exact:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final FocusNode focus;
late final FocusAttachment _nodeAttachment;
bool isShiftPressed = false;
List<bool> checkboxStates = List.filled(5, false);
int lastClicked = -1;
#override
void initState() {
super.initState();
focus = FocusNode(debugLabel: 'Button');
_nodeAttachment = focus.attach(context, onKey: (node, event) {
isShiftPressed = event.isShiftPressed;
});
focus.requestFocus();
}
#override
void dispose() {
focus.dispose();
super.dispose();
}
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return Column(
children: List.generate(checkboxStates.length, (index) => Checkbox(
value: checkboxStates[index],
onChanged: (val) {
if(val == null) {
return;
}
setState(() {
if(isShiftPressed && val) {
if(lastClicked >= 0) {
bool loopForward = lastClicked < index;
if(loopForward) {
for(int x = lastClicked; x < index; x++) {
checkboxStates[x] = true;
}
}
else {
for(int x = lastClicked; x > index; x--) {
checkboxStates[x] = true;
}
}
}
}
checkboxStates[index] = val;
});
if(val) {
lastClicked = index;
}
else {
lastClicked = -1;
}
print('Checkbox $index: $isShiftPressed');
}
)),
);
}
}
I'm having trouble to understend why my Stateful widget is not updating the state after a rebuild. I have a stateful widget responsable for decrementing a counter each second, so it recieves a initial value, I pass this initial value to the State and start decrementing it.
Also it has a button that when pressed sends a event to my bloc which rebuilds the stateful widget with a new initial value, the thing is, it indeed sends and update the initial value, but the counter continue decrementing the old value and I have no clue why. Here a example of the code:
Widget
void main() => runApp(
BlocProvider(
create: (context) => TBloc(),
child: MaterialApp(
home: Scaffold(
body: BlocBuilder<TBloc, BState>(
builder: (context, state) {
if (state is BState) {
return Column(
children: [
Counter(state.value),
FlatButton(
child: Text("tap"),
onPressed: () =>
BlocProvider.of<TBloc>(context).add(BEvent(200)),
),
],
);
}
return Container();
},
),
),
),
),
);
class Counter extends StatefulWidget {
final int initialValue;
Counter(this.initialValue);
#override
CounterState createState() => CounterState(this.initialValue);
}
class CounterState extends State<Counter> {
Timer timer;
int value;
CounterState(this.value);
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() => --value);
});
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Value: $value"),
Text("Initial: ${widget.initialValue}")
],
);
}
}
Bloc
class TBloc extends Bloc<BEvent, BState> {
#override
BState get initialState => BState(100);
#override
Stream<BState> mapEventToState(BEvent event) async* {
yield BState(event.value);
}
}
class BEvent extends Equatable {
final int value;
BEvent(this.value);
#override
List<Object> get props => [value];
}
class BState extends Equatable {
final int value;
BState(this.value);
#override
List<Object> get props => [value];
}
Thank you in advance.
That's because you are only setting your Timer on your Counter's initState which is called only once per widget lifecycle.
Basically, when your Counter is first built, it will call initState() and as long as it remains in that widget tree, only didUpdateWidget() and/or didChangeDependencies() will be called in order to update that widget (eg. when rebuilt with new parameters, just like you're doing).
The same happens for dispose() method, which is the opposite of initState() called only once but this time, when the widget is being removed from the tree.
Also, you are using states the wrong way here. You don't need to pass the parameters to the state itself (like you are doing by:
CounterState createState() => CounterState(this.initialValue);
but instead, you can access all parameters from that widget by calling: widget.initialValue.
So, with all of that said, basically, what you'll want to do is to update your Counter widget based on the new updates, like so:
class Counter extends StatefulWidget {
final int initialValue;
Counter(this.initialValue);
#override
CounterState createState() => CounterState();
}
class CounterState extends State<Counter> {
Timer timer;
int value;
void _setTimer() {
value = widget.initialValue;
timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() => --value);
});
}
#override
void initState() {
super.initState();
_setTimer();
}
#override
void didUpdateWidget(Counter oldWidget) {
super.didUpdateWidget(oldWidget);
if(oldWidget.initialValue != widget.initialValue) {
_setTimer();
}
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Value: $value"),
Text("Initial: ${widget.initialValue}")
],
);
}
}