Custom UIBuilder does not preserve widget state
Have you checked for an existing issue?
- [x] I have searched the existing issues
Flutter Quill Version
11.2.0
Steps to Reproduce
QuillEditorConfig(
customStyles: DefaultStyles(
lists: DefaultListBlockStyle(
const TextStyle(color: Colors.red),
HorizontalSpacing.zero,
const VerticalSpacing(4, 4),
const VerticalSpacing(4, 4),
null,
_CustomCheckBox(), // <-- custom builder
),
)
class _CustomCheckBox extends QuillCheckboxBuilder {
@override
Widget build({
required BuildContext context,
required bool isChecked,
required ValueChanged<bool> onChanged,
}) {
return GestureDetector(
onTap: () => onChanged(!isChecked),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: CountStateWidget(),
),
);
}
}
int count = 0;
class CountStateWidget extends StatefulWidget {
const CountStateWidget({super.key});
@override
State<CountStateWidget> createState() => _CountStateWidgetState();
}
class _CountStateWidgetState extends State<CountStateWidget> {
late final int i;
@override
void initState() {
super.initState();
count++;
i = count;
}
@override
Widget build(BuildContext context) => Text(i.toString());
}
Observe the numbers (0 1 2 …) next to each line: they do not stay fixed; the same line may rebuild with a new value.
Expected results
CountStateWidget should have its initState called exactly once for each checklist line, retaining its internal data as long as that line exists. Tapping the checkbox should not create a brand‑new CountStateWidget.
Actual results
initState is called multiple times for the same logical item, so the counter keeps changing unexpectedly.
Additional Context
Add a stable Key when bulletPointLeading returns QuillBulletPoint:
Widget bulletPointLeading(LeadingConfig config) => QuillBulletPoint(
key: ...,
style: config.style!,
width: config.width!,
padding: config.padding!,
);
With the key in place, Flutter can match the widget across rebuilds and your nested StatefulWidget keeps its state.
🛠️ Fix: Add a Stable Key to Your Bullet Builder
The issue here is that without a key, Flutter can’t match your checkbox widgets across rebuilds—so each rebuild creates a brand-new CountStateWidget (and calls initState again). You can fix this by giving your QuillBulletPoint a stable key based on the line’s identity (for example its index or document offset):
Widget bulletPointLeading(LeadingConfig config) {
return QuillBulletPoint(
key: ValueKey(config.index), // ← use a stable identifier per checklist line
style: config.style!,
width: config.width!,
padding: config.padding!,
// ...or wrap your custom builder here instead of the default
);
}
If config.index isn’t available, you can also use something like:
key: Key('bullet-${config.documentOffset}'),
or any other unique & consistent value for each list item.
With this change, each CountStateWidget is matched across rebuilds, so its initState only runs once and your counter stays fixed per line.