Make debugCheckHasMaterial more useful (#14101)

Fixes https://github.com/flutter/flutter/issues/2877
diff --git a/packages/flutter/lib/src/material/debug.dart b/packages/flutter/lib/src/material/debug.dart
index fece354..8b21778 100644
--- a/packages/flutter/lib/src/material/debug.dart
+++ b/packages/flutter/lib/src/material/debug.dart
@@ -22,20 +22,45 @@
 bool debugCheckHasMaterial(BuildContext context) {
   assert(() {
     if (context.widget is! Material && context.ancestorWidgetOfExactType(Material) == null) {
-      final Element element = context;
-      throw new FlutterError(
-        'No Material widget found.\n'
-        '${context.widget.runtimeType} widgets require a Material widget ancestor.\n'
-        'In material design, most widgets are conceptually "printed" on a sheet of material. In Flutter\'s material library, '
-        'that material is represented by the Material widget. It is the Material widget that renders ink splashes, for instance. '
-        'Because of this, many material library widgets require that there be a Material widget in the tree above them.\n'
-        'To introduce a Material widget, you can either directly include one, or use a widget that contains Material itself, '
-        'such as a Card, Dialog, Drawer, or Scaffold.\n'
-        'The specific widget that could not find a Material ancestor was:\n'
-        '  ${context.widget}\n'
-        'The ownership chain for the affected widget is:\n'
-        '  ${element.debugGetCreatorChain(10)}'
+      final StringBuffer message = new StringBuffer();
+      message.writeln('No Material widget found.');
+      message.writeln(
+        '${context.widget.runtimeType} widgets require a Material '
+        'widget ancestor.'
       );
+      message.writeln(
+        'In material design, most widgets are conceptually "printed" on '
+        'a sheet of material. In Flutter\'s material library, that '
+        'material is represented by the Material widget. It is the '
+        'Material widget that renders ink splashes, for instance. '
+        'Because of this, many material library widgets require that '
+        'there be a Material widget in the tree above them.'
+      );
+      message.writeln(
+        'To introduce a Material widget, you can either directly '
+        'include one, or use a widget that contains Material itself, '
+        'such as a Card, Dialog, Drawer, or Scaffold.'
+      );
+      message.writeln(
+        'The specific widget that could not find a Material ancestor was:'
+      );
+      message.writeln('  ${context.widget}');
+      final List<Widget> ancestors = <Widget>[];
+      context.visitAncestorElements((Element element) {
+        ancestors.add(element.widget);
+        return true;
+      });
+      if (ancestors.isNotEmpty) {
+        message.write('The ancestors of this widget were:');
+        for (Widget ancestor in ancestors)
+          message.write('\n  $ancestor');
+      } else {
+        message.writeln(
+          'This widget is the root of the tree, so it has no '
+          'ancestors, let alone a "Material" ancestor.'
+        );
+      }
+      throw new FlutterError(message.toString());
     }
     return true;
   }());
diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart
index 46da6b0..f347904 100644
--- a/packages/flutter/test/material/debug_test.dart
+++ b/packages/flutter/test/material/debug_test.dart
@@ -11,6 +11,8 @@
       onPressed: null,
       child: const Text('Go'),
     ));
-    expect(tester.takeException(), isFlutterError);
+    final dynamic exception = tester.takeException();
+    expect(exception, isFlutterError);
+    expect(exception.toString(), endsWith(':\n  FlatButton(disabled)\n  [root]'));
   });
 }