Added InputDecoration helperText, helperStyle (#10852)

diff --git a/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart b/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart
index 7abfade..54ce4fb 100644
--- a/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/text_form_field_demo.dart
@@ -144,6 +144,7 @@
             new TextFormField(
               decoration: const InputDecoration(
                 hintText: 'Tell us about yourself',
+                helperText: 'Keep it short, this is just a demo',
                 labelText: 'Life story',
               ),
               maxLines: 3,
diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart
index 668d897..fe422e4 100644
--- a/packages/flutter/lib/src/material/input_decorator.dart
+++ b/packages/flutter/lib/src/material/input_decorator.dart
@@ -31,6 +31,8 @@
     this.icon,
     this.labelText,
     this.labelStyle,
+    this.helperText,
+    this.helperStyle,
     this.hintText,
     this.hintStyle,
     this.errorText,
@@ -55,6 +57,8 @@
   }) : icon = null,
        labelText = null,
        labelStyle = null,
+       helperText = null,
+       helperStyle = null,
        errorText = null,
        errorStyle = null,
        isDense = false,
@@ -93,6 +97,17 @@
   /// input field and the current [Theme].
   final TextStyle labelStyle;
 
+  /// Text that provides context about the field’s value, such as how the value
+  /// will be used.
+  ///
+  /// If non-null, the text is displayed below the input field, in the same
+  /// location as [errorText]. If a non-null [errorText] value is specified then
+  /// the helper text is not shown.
+  final String helperText;
+
+  /// The style to use for the [helperText].
+  final TextStyle helperStyle;
+
   /// Text that suggests what sort of input the field accepts.
   ///
   /// Displayed on top of the input field (i.e., at the same location on the
@@ -113,7 +128,7 @@
 
   /// Text that appears below the input field.
   ///
-  /// If non-null the divider, that appears below the input field is red.
+  /// If non-null, the divider that appears below the input field is red.
   final String errorText;
 
   /// The style to use for the [errorText].
@@ -171,6 +186,8 @@
     Widget icon,
     String labelText,
     TextStyle labelStyle,
+    String helperText,
+    TextStyle helperStyle,
     String hintText,
     TextStyle hintStyle,
     String errorText,
@@ -186,6 +203,8 @@
       icon: icon ?? this.icon,
       labelText: labelText ?? this.labelText,
       labelStyle: labelStyle ?? this.labelStyle,
+      helperText: helperText ?? this.helperText,
+      helperStyle: helperStyle ?? this.helperStyle,
       hintText: hintText ?? this.hintText,
       hintStyle: hintStyle ?? this.hintStyle,
       errorText: errorText ?? this.errorText,
@@ -209,6 +228,8 @@
     return typedOther.icon == icon
         && typedOther.labelText == labelText
         && typedOther.labelStyle == labelStyle
+        && typedOther.helperText == helperText
+        && typedOther.helperStyle == helperStyle
         && typedOther.hintText == hintText
         && typedOther.hintStyle == hintStyle
         && typedOther.errorText == errorText
@@ -228,6 +249,8 @@
       icon,
       labelText,
       labelStyle,
+      helperText,
+      helperStyle,
       hintText,
       hintStyle,
       errorText,
@@ -249,6 +272,8 @@
       description.add('icon: $icon');
     if (labelText != null)
       description.add('labelText: "$labelText"');
+    if (helperText != null)
+      description.add('helperText: "$helperText"');
     if (hintText != null)
       description.add('hintText: "$hintText"');
     if (errorText != null)
@@ -390,6 +415,7 @@
     assert(!isDense || !isCollapsed);
 
     final String labelText = decoration.labelText;
+    final String helperText = decoration.helperText;
     final String hintText = decoration.hintText;
     final String errorText = decoration.errorText;
 
@@ -485,16 +511,20 @@
       stackChildren.add(_buildContent(borderColor, topPadding, isDense, inputChild));
     }
 
-    if (!isDense && errorText != null) {
+    if (!isDense && (errorText != null || helperText != null)) {
       assert(!isCollapsed);
-      final TextStyle errorStyle = decoration.errorStyle ?? themeData.textTheme.caption.copyWith(color: themeData.errorColor);
+      final TextStyle captionStyle = themeData.textTheme.caption;
+      final TextStyle subtextStyle = errorText != null
+        ? decoration.errorStyle ?? captionStyle.copyWith(color: themeData.errorColor)
+        : decoration.helperStyle ?? captionStyle.copyWith(color: themeData.hintColor);
+
       stackChildren.add(new Positioned(
         left: 0.0,
         right: 0.0,
         bottom: 0.0,
         child: new Text(
-          errorText,
-          style: errorStyle,
+          errorText ?? helperText,
+          style: subtextStyle,
           textAlign: textAlign,
           overflow: TextOverflow.ellipsis,
         ),
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index f9ea072..7382e83 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -741,6 +741,75 @@
     await checkText('Hello World');
   });
 
+  testWidgets('TextField errorText trumps helperText', (WidgetTester tester) async {
+    Widget builder() {
+      return const Center(
+        child: const Material(
+          child: const TextField(
+            decoration: const InputDecoration(
+              errorText: 'error text',
+              helperText: 'helper text',
+            ),
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(builder());
+    expect(find.text('helper text'), findsNothing);
+    expect(find.text('error text'), findsOneWidget);
+  });
+
+  testWidgets('TextField with default helperStyle', (WidgetTester tester) async {
+    final ThemeData themeData = new ThemeData(
+      hintColor: Colors.blue[500],
+    );
+
+    Widget builder() {
+      return new Center(
+        child: new Theme(
+          data: themeData,
+          child: const Material(
+            child: const TextField(
+              decoration: const InputDecoration(
+                helperText: 'helper text',
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(builder());
+    final Text helperText = tester.widget(find.text('helper text'));
+    expect(helperText.style.color, themeData.hintColor);
+    expect(helperText.style.fontSize, themeData.textTheme.caption.fontSize);
+  });
+
+  testWidgets('TextField with specified helperStyle', (WidgetTester tester) async {
+    final TextStyle style = new TextStyle(
+      color: Colors.pink[500],
+      fontSize: 10.0,
+    );
+
+    Widget builder() {
+      return new Center(
+        child: new Material(
+          child: new TextField(
+            decoration: new InputDecoration(
+              helperText: 'helper text',
+              helperStyle: style,
+            ),
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(builder());
+    final Text helperText = tester.widget(find.text('helper text'));
+    expect(helperText.style, style);
+  });
+
   testWidgets('TextField with default hintStyle', (WidgetTester tester) async {
     final TextStyle style = new TextStyle(
       color: Colors.pink[500],