I'm using Flutter 2.5.1 and I'm having this error:
- "text_painter.dart: Failed assertion: line 900 pos 12: '!_needsLayout': is not true"
while using the following code (which generates a switch button, just like IOS, in Flutter). I found it here: pub.dev, and I'm modifying it in order to add null safety. However, I have encountered a bug that I do not understand.
import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:math';
/// Customable and attractive Switch button.
/// Currently, you can't change the widget
/// width and height properties.
///
/// As well as the classical Switch Widget
/// from flutter material, the following
/// arguments are required:
///
/// * [value] determines whether this switch is on or off.
/// * [onChanged] is called when the user toggles the switch on or off.
class LiteRollingSwitch extends StatefulWidget {
/// A required boolean that sets the value of the button.
/// * "on" = `true`
/// * "off" = `false`
final bool value;
/// A function called every time there is a change in state. (required)
final Function(bool) onChanged;
/// Text displayed when the [value] is `false`. By default
/// * "Off"
final String textOff;
/// Text displayed when the [value] is `true`. By default:
/// * "On"
final String textOn;
/// Color shown when the [value] is `true`. By default:
/// * `Colors.green`
final Color colorOn;
/// Color shown when the [value] is `false`. By default:
/// * `Colors.red`
final Color colorOff;
/// The size of the text. By default:
/// * `14.0`
final double textSize;
/// The duration of the animation. By default:
/// * `Duration(milliseconds: 600)`
final Duration animationDuration;
/// Text displayed when the [value] is `true`. By default:
/// * `Icons.check`
final IconData iconOn;
/// Text displayed when the [value] is `false`. By default:
/// * `Icons.flag`
final IconData iconOff;
/// The width of the switch. By default:
/// * `130`
final double width;
/// Additional action on tap.
final Function? onTap;
/// Additional action on double tap.
final Function? onDoubleTap;
/// Additional action on swipe.
final Function? onSwipe;
const LiteRollingSwitch({
Key? key,
required this.value,
required this.onChanged,
this.textOff = "Off",
this.textOn = "On",
this.textSize = 14.0,
this.colorOn = Colors.green,
this.colorOff = Colors.red,
this.iconOff = Icons.flag,
this.iconOn = Icons.check,
this.animationDuration = const Duration(milliseconds: 600),
this.width = 130,
this.onTap,
this.onDoubleTap,
this.onSwipe,
}) : super(key: key);
@override
_RollingSwitchState createState() => _RollingSwitchState();
}
class _RollingSwitchState extends State<LiteRollingSwitch>
with SingleTickerProviderStateMixin {
late AnimationController animationController;
late Animation<double> animation;
late bool turnState;
double value = 0.0;
@override
void dispose() {
animationController.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
lowerBound: 0.0,
upperBound: 1.0,
duration: widget.animationDuration,
);
animation = CurvedAnimation(
parent: animationController,
curve: Curves.easeInOut,
);
animationController.addListener(() {
setState(() {
value = animation.value;
});
});
turnState = widget.value;
_determine();
}
@override
Widget build(BuildContext context) {
Color transitionColor = Color.lerp(widget.colorOff, widget.colorOn, value)!;
return GestureDetector(
onDoubleTap: () {
_action();
if (widget.onDoubleTap != null) widget.onDoubleTap!();
},
onTap: () {
_action();
if (widget.onTap != null) widget.onTap!();
},
onPanEnd: (details) {
_action();
if (widget.onSwipe != null) widget.onSwipe!();
},
child: Container(
padding: const EdgeInsets.all(5),
width: widget.width,
decoration: BoxDecoration(
color: transitionColor,
borderRadius: BorderRadius.circular(50),
),
child: Stack(
children: <Widget>[
// the "off" text
Transform.translate(
offset: Offset(10 * value, 0),
child: Opacity(
// its opacity will change
opacity: (1 - value).clamp(0.0, 1.0),
child: Container(
// it's in a container
padding: const EdgeInsets.only(right: 5),
alignment: Alignment.centerRight,
height: 40,
child: Text(
widget.textOff,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: widget.textSize,
),
),
),
),
),
// the "on" text
Transform.translate(
offset: Offset(10 * (1 - value), 0),
child: Opacity(
opacity: value.clamp(0.0, 1.0),
child: Container(
padding: const EdgeInsets.only(left: 5),
alignment: Alignment.centerLeft,
height: 40,
child: Text(
widget.textOn,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: widget.textSize,
),
),
),
),
),
// The icons
Transform.translate(
offset: Offset(widget.width / 2 * value, 0),
child: Transform.rotate(
angle: lerpDouble(0, 2 * pi, value)!,
// the white thing that will move
child: Container(
height: 40,
width: 40,
alignment: Alignment.center,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Stack(
children: <Widget>[
// The "on" icon
Center(
child: Opacity(
opacity: value.clamp(0.0, 1.0),
child: Icon(
widget.iconOn,
size: 21,
color: transitionColor,
),
),
),
// The "off" icon
Center(
child: Opacity(
opacity: (1 - value).clamp(0.0, 1.0),
child: Icon(
widget.iconOff,
size: 21,
color: transitionColor,
),
),
),
],
),
),
),
)
],
),
),
);
}
_action() {
_determine(changeState: true);
}
/// Handles the animation.
_determine({bool changeState = false}) {
setState(() {
if (changeState) turnState = !turnState;
(turnState)
? animationController.forward()
: animationController.reverse();
widget.onChanged(turnState);
});
}
}
This happens when I click on the "off" icon of the switch:
NOTE The switcher works, but you need to understand that my error doesn't always occur. In fact, it occurs just sometimes, when I try to turn off the switcher (and only when I click very precisely on the icon)
Please help me
