I'm using pref plugin in my Flutter project. I have two slider settings for configure a lower limit and an upper limit. I'd like prevent the user to configure an invalid combination setting when the lower limit would be larger than the upper or the other way around.
The PrefSlider
doesn't have a validate method unfortunately, so I'm trying to put out the fire after the fact in an onChanged handler:
PrefSlider<int>(
title: "Lower Limit",
pref: "lower_limit",
trailing: (num value) => Text("$value"),
min: 0,
max: 300,
onChange: (value) {
final upperLimit = PrefService.of(context).get<int>("upper_limit") ?? 300;
if (value >= upperLimit) {
PrefService.of(context).set<int>("lower_limit", upperLimit - 1);
}
},
),
PrefSlider<int>(
title: "Upper Limit",
pref: "upper_limit",
trailing: (num value) => Text("$value"),
min: 0,
max: 300,
onChange: (value) {
final lowerLimit = PrefService.of(context).get<int>("lower_limit") ?? 0;
if (value <= lowerLimit) {
PrefService.of(context).set<int>("upper_limit", lowerLimit + 1);
}
},
),
When I move the slider and the pref tries to call my onChanged function I get a big fat exception:
E/flutter (21583): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: type '(int) => void' is not a subtype of type '((num) => void)?'
E/flutter (21583): #0 _PrefSliderState._onChange (package:pref/src/slider.dart:90:18)
E/flutter (21583): #1 _SliderState._handleChanged (package:flutter/src/material/slider.dart:540:24)
E/flutter (21583): #2 _RenderSlider._startInteraction (package:flutter/src/material/slider.dart:1235:17)
E/flutter (21583): #3 _RenderSlider._handleTapDown (package:flutter/src/material/slider.dart:1296:5)
E/flutter (21583): #4 TapGestureRecognizer.handleTapDown.<anonymous closure> (package:flutter/src/gestures/tap.dart:580:61)
E/flutter (21583): #5 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
E/flutter (21583): #6 TapGestureRecognizer.handleTapDown (package:flutter/src/gestures/tap.dart:580:11)
E/flutter (21583): #7 BaseTapGestureRecognizer._checkDown (package:flutter/src/gestures/tap.dart:287:5)
E/flutter (21583): #8 BaseTapGestureRecognizer.didExceedDeadline (package:flutter/src/gestures/tap.dart:258:5)
E/flutter (21583): #9 PrimaryPointerGestureRecognizer.didExceedDeadlineWithEvent (package:flutter/src/gestures/recognizer.dart:501:5)
E/flutter (21583): #10 PrimaryPointerGestureRecognizer.addAllowedPointer.<anonymous closure> (package:flutter/src/gestures/recognizer.dart:454:41)
E/flutter (21583): #11 _rootRun (dart:async/zone.dart:1346:47)
E/flutter (21583): #12 _CustomZone.run (dart:async/zone.dart:1258:19)
E/flutter (21583): #13 _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
E/flutter (21583): #14 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1202:23)
E/flutter (21583): #15 _rootRun (dart:async/zone.dart:1354:13)
E/flutter (21583): #16 _CustomZone.run (dart:async/zone.dart:1258:19)
E/flutter (21583): #17 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1186:23)
E/flutter (21583): #18 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
E/flutter (21583): #19 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:395:19)
E/flutter (21583): #20 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:426:5)
E/flutter (21583): #21 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
And I just cannot figure out what's wrong. Somehow the handler function should be nullable? What's that question mark in '((num) => void)?'
? Here is the code section where the plugin tries to call my handler:
Future<void> _onChange(double value) async {
final service = PrefService.of(context, listen: false);
if (T == double) {
service.set<double>(widget.pref, value);
if (widget.onChange != null) {
widget.onChange!(value);
}
} else if (T == int) { // for PrefSlider<int>
service.set<int>(widget.pref, value.round());
if (widget.onChange != null) {
widget.onChange!(value.round()); // <- this is the call
}
} else if (T == num) {
service.set<double>(widget.pref, value);
if (widget.onChange != null) {
widget.onChange!(value);
}
}
}
To view at the broader picture I was thinking that maybe I could convert the min
and max
fields to states and somehow rebuild with each change, but that would probably break the UI.
Looks like I could sooth the exception by specifying explicit num
type for the callback:
PrefSlider<int>(
title: "Lower Limit",
pref: "lower_limit",
trailing: (num value) => Text("$value"),
min: 0,
max: 300,
onChange: (num value) {
final upperLimit = PrefService.of(context).get<int>("upper_limit") ?? 300;
if (value >= upperLimit) {
PrefService.of(context).set<int>("lower_limit", upperLimit - 1);
}
},
),
PrefSlider<int>(
title: "Upper Limit",
pref: "upper_limit",
trailing: (num value) => Text("$value"),
min: 0,
max: 300,
onChange: (num value) {
final lowerLimit = PrefService.of(context).get<int>("lower_limit") ?? 0;
if (value <= lowerLimit) {
PrefService.of(context).set<int>("upper_limit", lowerLimit + 1);
}
},
),
It's interesting that these are all run-time only exceptions and it also happens if I'd remove the explicit num
from the trailing: (num value) => Text("$value"),
.