Fix crash when dumping the app if it uses RichText
Specifically: * Handle null styles in TextSpan without crashing in toString(). * Handle null children in TextSpan child lists without crashing in toString(). * Handle entirely empty TextSpans in toString() explicitly. * Assert that TextSpans don't contain nulls in various places. This is done more often than one might think necessary, because it turns out that TextSpan takes a (mutable) List for one of its arguments, so who knows what it will contain at any given time. By asserting all over the place, hopefully we'll catch it near the change if they do change it. * Add a RichText example to Stocks to exercise RichText and TextSpans. See also: https://github.com/flutter/flutter/issues/2514, https://github.com/flutter/flutter/issues/2519
This commit is contained in:
parent
3dc22dce58
commit
7cf2dbdf37
@ -46,6 +46,19 @@ class StockSymbolView extends StatelessComponent {
|
||||
),
|
||||
new Text('Market Cap', style: headings),
|
||||
new Text('${stock.marketCap}'),
|
||||
new Container(
|
||||
height: 8.0
|
||||
),
|
||||
new RichText(
|
||||
text: new TextSpan(
|
||||
style: DefaultTextStyle.of(context).merge(new TextStyle(fontSize: 8.0)),
|
||||
text: 'Prices may be delayed by ',
|
||||
children: <TextSpan>[
|
||||
new TextSpan(text: 'several', style: new TextStyle(fontStyle: FontStyle.italic)),
|
||||
new TextSpan(text: ' years.'),
|
||||
]
|
||||
)
|
||||
),
|
||||
],
|
||||
justifyContent: FlexJustifyContent.collapse
|
||||
)
|
||||
|
@ -12,6 +12,7 @@
|
||||
library services;
|
||||
|
||||
export 'src/services/activity.dart';
|
||||
export 'src/services/assertions.dart';
|
||||
export 'src/services/asset_bundle.dart';
|
||||
export 'src/services/binding.dart';
|
||||
export 'src/services/fetch.dart';
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphStyle, TextBox;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'basic_types.dart';
|
||||
import 'text_editing.dart';
|
||||
@ -51,6 +52,7 @@ class TextSpan {
|
||||
final GestureRecognizer recognizer;
|
||||
|
||||
void build(ui.ParagraphBuilder builder) {
|
||||
assert(debugAssertValid());
|
||||
final bool hasStyle = style != null;
|
||||
if (hasStyle)
|
||||
builder.pushStyle(style.textStyle);
|
||||
@ -81,6 +83,7 @@ class TextSpan {
|
||||
}
|
||||
|
||||
TextSpan getSpanForPosition(TextPosition position) {
|
||||
assert(debugAssertValid());
|
||||
TextAffinity affinity = position.affinity;
|
||||
int targetOffset = position.offset;
|
||||
int offset = 0;
|
||||
@ -101,6 +104,7 @@ class TextSpan {
|
||||
}
|
||||
|
||||
String toPlainText() {
|
||||
assert(debugAssertValid());
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
visitTextSpan((TextSpan span) {
|
||||
buffer.write(span.text);
|
||||
@ -113,15 +117,47 @@ class TextSpan {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.writeln('$prefix$runtimeType:');
|
||||
String indent = '$prefix ';
|
||||
if (style != null)
|
||||
buffer.writeln(style.toString(indent));
|
||||
if (text != null)
|
||||
buffer.writeln('$indent"$text"');
|
||||
if (children != null)
|
||||
for (TextSpan child in children)
|
||||
buffer.writeln(child.toString(indent));
|
||||
if (children != null) {
|
||||
for (TextSpan child in children) {
|
||||
if (child != null) {
|
||||
buffer.write(child.toString(indent));
|
||||
} else {
|
||||
buffer.writeln('$indent<null>');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (style == null && text == null && children == null)
|
||||
buffer.writeln('$indent(empty)');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
bool debugAssertValid() {
|
||||
assert(() {
|
||||
if (!visitTextSpan((TextSpan span) {
|
||||
if (span.children != null) {
|
||||
for (TextSpan child in span.children) {
|
||||
if (child == null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})) {
|
||||
throw new FlutterError(
|
||||
'TextSpan contains a null child.\n'
|
||||
'A TextSpan object with a non-null child list should not have any nulls in its child list.\n'
|
||||
'The full text in question was:\n'
|
||||
'${toString(" ")}'
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
@ -149,6 +185,7 @@ class TextPainter {
|
||||
/// The (potentially styled) text to paint.
|
||||
TextSpan get text => _text;
|
||||
void set text(TextSpan value) {
|
||||
assert(value == null || value.debugAssertValid());
|
||||
if (_text == value)
|
||||
return;
|
||||
_text = value;
|
||||
|
@ -15,6 +15,7 @@ class RenderParagraph extends RenderBox {
|
||||
TextSpan text
|
||||
) : _textPainter = new TextPainter(text) {
|
||||
assert(text != null);
|
||||
assert(text.debugAssertValid());
|
||||
}
|
||||
|
||||
final TextPainter _textPainter;
|
||||
@ -24,6 +25,7 @@ class RenderParagraph extends RenderBox {
|
||||
/// The text to display
|
||||
TextSpan get text => _textPainter.text;
|
||||
void set text(TextSpan value) {
|
||||
assert(value.debugAssertValid());
|
||||
if (_textPainter.text == value)
|
||||
return;
|
||||
_textPainter.text = value;
|
||||
|
9
packages/flutter/lib/src/services/assertions.dart
Normal file
9
packages/flutter/lib/src/services/assertions.dart
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
class FlutterError extends AssertionError {
|
||||
FlutterError(this.message);
|
||||
final String message;
|
||||
String toString() => message;
|
||||
}
|
@ -27,4 +27,38 @@ void main() {
|
||||
expect(b1 == a2, isFalse);
|
||||
expect(c1 == b2, isFalse);
|
||||
});
|
||||
|
||||
test("TextSpan ", () {
|
||||
final TextSpan test = new TextSpan(
|
||||
text: 'a',
|
||||
style: new TextStyle(
|
||||
fontSize: 10.0
|
||||
),
|
||||
children: <TextSpan>[
|
||||
new TextSpan(
|
||||
text: 'b',
|
||||
children: <TextSpan>[
|
||||
new TextSpan()
|
||||
]
|
||||
),
|
||||
null,
|
||||
new TextSpan(
|
||||
text: 'c'
|
||||
),
|
||||
]
|
||||
);
|
||||
expect(test.toString(), equals(
|
||||
'TextSpan:\n'
|
||||
' inherit: true\n'
|
||||
' size: 10.0\n'
|
||||
' "a"\n'
|
||||
' TextSpan:\n'
|
||||
' "b"\n'
|
||||
' TextSpan:\n'
|
||||
' (empty)\n'
|
||||
' <null>\n'
|
||||
' TextSpan:\n'
|
||||
' "c"\n'
|
||||
));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user