#flutter #latex #debug #mobile

flutter 3.38 broke my app bc of LaTeX (733 errors per frame!)

Miuna

bro. i almost cried.

opened my app on the "tips & tricks" topic and the terminal EXPLODED. like, 733 errors per frame. the app became unusable, freezing, everything broken. i thought it was a bug in my checklist (bc it had issues before), but nope.

the investigation (spoiler: it was LaTeX)

spent ages fixing the checklist (NoteChecklistContent). found real bugs there:

  • IntrinsicWidth fighting with Flexible (constraint conflict)
  • Checkbox with messed up sizing inside a Row
  • mainAxisSize: MainAxisSize.min on a Row with Flexible children

fixed everything. swapped Checkbox for Icon + GestureDetector, FlexibleExpanded, etc. valid fixes, but they didnt solve the main problem. (一_一)

then i redirected flutter run output to a log file and finally saw the real cascade:

Error Count
Invalid argument(s): string is not well-formed UTF-16 3
RenderLine does not implement "computeDryBaseline" 1
RenderBox was not laid out: RenderIntrinsicWidth 1
RenderBox was not laid out: RenderIndexedSemantics 288
'parentDataDirty' assertion failures 440
TOTAL ~733/frame

the root cause

the topic had a note with inline LaTeX: $E = mc^2$. GptMarkdown converts $...$ to \(...\) and sends it to flutter_math_fork to render via Math.tex().

turns out flutter_math_fork v0.7.4 uses a RenderLine class (custom RenderBox) that doesnt implement computeDryBaseline, a method that became mandatory in flutter 3.38.

THE NIGHTMARE CASCADE

result: RenderLine fails → parent RenderIntrinsicWidth cant layout → RenderIndexedSemantics (accessibility) fails in cascade → framework detects parentDataDirty on hundreds of render objects → repeats every frame. infinite loop of pain and suffering.

and the worst part: theres no new version of either package (gpt_markdown 1.1.5 and flutter_math_fork 0.7.4). both are abandoned and incompatible with flutter 3.38. (ꐦ°᷄д°᷅)

the workaround (yeah its a hack)

since we cant use Math.tex() without crashing, i made a safe builder that renders LaTeX as styled text (monospace + italic) instead of a pretty formula:

static Widget safeLatexBuilder(
  BuildContext context,
  String tex,
  TextStyle textStyle,
  bool inline,
) {
  final theme = Theme.of(context);
  return Text(
    tex,
    style: textStyle.copyWith(
      fontFamily: 'monospace',
      fontStyle: FontStyle.italic,
      color: theme.colorScheme.onSurface.withValues(alpha: 0.85),
    ),
  );
}

also made my own preprocessing to convert $...$\(...\) outside of GptMarkdown, bc its internal regex was generating malformed UTF-16 strings:

static String _convertDollarLatex(String text) {
  var result = text.replaceAllMapped(
    RegExp(r'(?<!\\)\$\$(.*?)(?<!\\)\$\$', dotAll: true),
    (match) => '\\[${match[1] ?? ""}\\]',
  );
  if (!result.contains(r'\(')) {
    result = result.replaceAllMapped(
      RegExp(r'(?<!\\)\$(.*?)(?<!\\)\$'),
      (match) => '\\(${match[1] ?? ""}\\)',
    );
  }
  return result;
}

then called GptMarkdown with useDollarSignsForLatex: false (since conversion was done manually) and applied the safe latexBuilder in all places that use GptMarkdown directly:

  • study_session_screen.dart (3 uses)
  • edit_note_dialog.dart (1 use)
  • flashcard_bubble_content.dart (1 use)

results

Metric Before After
parentDataDirty assertions 440/frame 0
RenderBox was not laid out 289/frame 0
computeDryBaseline errors 1/frame 0
App usable no yes

the trade-off is that LaTeX doesnt render as a pretty formula anymore (it shows E = mc^2 in monospace instead of the formatted one). but at least the app works. when the package maintainers wake up and release an update, ill revert everything.

MORAL OF THE STORY

always redirect logs to a file. if i had kept staring at the terminal scrolling 733 errors per frame id never find the root cause. and doubt your own guesses, i wasted time on the checklist when the problem was LaTeX all along. ¯\_(ツ)_/¯

ler em português →