[animations] Support for returning value from popped route (#153)

* Added support for returning value from popped route

* Example for `onClosed` added
diff --git a/packages/animations/example/lib/container_transition.dart b/packages/animations/example/lib/container_transition.dart
index 867ace0..155b5a4 100644
--- a/packages/animations/example/lib/container_transition.dart
+++ b/packages/animations/example/lib/container_transition.dart
@@ -49,6 +49,14 @@
 class _OpenContainerTransformDemoState
     extends State<OpenContainerTransformDemo> {
   ContainerTransitionType _transitionType = ContainerTransitionType.fade;
+  final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
+
+  void _showMarkedAsDoneSnackbar(bool isMarkedAsDone) {
+    if (isMarkedAsDone ?? false)
+      scaffoldKey.currentState.showSnackBar(const SnackBar(
+        content: Text('Marked as done!'),
+      ));
+  }
 
   void _showSettingsBottomModalSheet(BuildContext context) {
     showModalBottomSheet<void>(
@@ -103,6 +111,7 @@
   @override
   Widget build(BuildContext context) {
     return Scaffold(
+      key: scaffoldKey,
       appBar: AppBar(
         title: const Text('Container transform'),
         actions: <Widget>[
@@ -122,6 +131,7 @@
             closedBuilder: (BuildContext _, VoidCallback openContainer) {
               return _ExampleCard(openContainer: openContainer);
             },
+            onClosed: _showMarkedAsDoneSnackbar,
           ),
           const SizedBox(height: 16.0),
           _OpenContainerWrapper(
@@ -129,6 +139,7 @@
             closedBuilder: (BuildContext _, VoidCallback openContainer) {
               return _ExampleSingleTile(openContainer: openContainer);
             },
+            onClosed: _showMarkedAsDoneSnackbar,
           ),
           const SizedBox(height: 16.0),
           Row(
@@ -142,6 +153,7 @@
                       subtitle: 'Secondary text',
                     );
                   },
+                  onClosed: _showMarkedAsDoneSnackbar,
                 ),
               ),
               const SizedBox(width: 8.0),
@@ -154,6 +166,7 @@
                       subtitle: 'Secondary text',
                     );
                   },
+                  onClosed: _showMarkedAsDoneSnackbar,
                 ),
               ),
             ],
@@ -170,6 +183,7 @@
                       subtitle: 'Secondary',
                     );
                   },
+                  onClosed: _showMarkedAsDoneSnackbar,
                 ),
               ),
               const SizedBox(width: 8.0),
@@ -182,6 +196,7 @@
                       subtitle: 'Secondary',
                     );
                   },
+                  onClosed: _showMarkedAsDoneSnackbar,
                 ),
               ),
               const SizedBox(width: 8.0),
@@ -194,17 +209,19 @@
                       subtitle: 'Secondary',
                     );
                   },
+                  onClosed: _showMarkedAsDoneSnackbar,
                 ),
               ),
             ],
           ),
           const SizedBox(height: 16.0),
           ...List<Widget>.generate(10, (int index) {
-            return OpenContainer(
+            return OpenContainer<bool>(
               transitionType: _transitionType,
               openBuilder: (BuildContext _, VoidCallback openContainer) {
-                return _DetailsPage();
+                return const _DetailsPage();
               },
+              onClosed: _showMarkedAsDoneSnackbar,
               tappable: false,
               closedShape: const RoundedRectangleBorder(),
               closedElevation: 0.0,
@@ -226,7 +243,9 @@
       floatingActionButton: OpenContainer(
         transitionType: _transitionType,
         openBuilder: (BuildContext context, VoidCallback _) {
-          return _DetailsPage();
+          return const _DetailsPage(
+            includeMarkAsDoneButton: false,
+          );
         },
         closedElevation: 6.0,
         closedShape: const RoundedRectangleBorder(
@@ -256,18 +275,21 @@
   const _OpenContainerWrapper({
     this.closedBuilder,
     this.transitionType,
+    this.onClosed,
   });
 
   final OpenContainerBuilder closedBuilder;
   final ContainerTransitionType transitionType;
+  final ClosedCallback<bool> onClosed;
 
   @override
   Widget build(BuildContext context) {
-    return OpenContainer(
+    return OpenContainer<bool>(
       transitionType: transitionType,
       openBuilder: (BuildContext context, VoidCallback _) {
-        return _DetailsPage();
+        return const _DetailsPage();
       },
+      onClosed: onClosed,
       tappable: false,
       closedBuilder: closedBuilder,
     );
@@ -453,10 +475,24 @@
 }
 
 class _DetailsPage extends StatelessWidget {
+  const _DetailsPage({this.includeMarkAsDoneButton = true});
+
+  final bool includeMarkAsDoneButton;
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-      appBar: AppBar(title: const Text('Details page')),
+      appBar: AppBar(
+        title: const Text('Details page'),
+        actions: <Widget>[
+          if (includeMarkAsDoneButton)
+            IconButton(
+              icon: const Icon(Icons.done),
+              onPressed: () => Navigator.pop(context, true),
+              tooltip: 'Mark as done',
+            )
+        ],
+      ),
       body: ListView(
         children: <Widget>[
           Container(
diff --git a/packages/animations/lib/src/open_container.dart b/packages/animations/lib/src/open_container.dart
index c4a1f6f..5274322 100644
--- a/packages/animations/lib/src/open_container.dart
+++ b/packages/animations/lib/src/open_container.dart
@@ -5,13 +5,28 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/scheduler.dart';
 
-/// Signature for a function that creates a [Widget] to be used within an
+/// Signature for `action` callback function provided to [OpenContainer.openBuilder].
+///
+/// Parameter `returnValue` is the value which will be provided to [OpenContainer.onClosed]
+/// when `action` is called.
+typedef CloseContainerActionCallback<S> = void Function({S returnValue});
+
+/// Signature for a function that creates a [Widget] in open state within an
 /// [OpenContainer].
 ///
 /// The `action` callback provided to [OpenContainer.openBuilder] can be used
-/// to close the container. The `action` callback provided to
-/// [OpenContainer.closedBuilder] can be used to open the container again.
-typedef OpenContainerBuilder = Widget Function(
+/// to close the container.
+typedef OpenContainerBuilder<S> = Widget Function(
+  BuildContext context,
+  CloseContainerActionCallback<S> action,
+);
+
+/// Signature for a function that creates a [Widget] in closed state within an
+/// [OpenContainer].
+///
+/// The `action` callback provided to [OpenContainer.closedBuilder] can be used
+/// to open the container.
+typedef CloseContainerBuilder = Widget Function(
   BuildContext context,
   VoidCallback action,
 );
@@ -29,6 +44,10 @@
   fadeThrough,
 }
 
+/// Callback function which is called when the [OpenContainer]
+/// is closed.
+typedef ClosedCallback<S> = void Function(S data);
+
 /// A container that grows to fill the screen to reveal new content when tapped.
 ///
 /// While the container is closed, it shows the [Widget] returned by
@@ -45,17 +64,21 @@
 /// [closedBuilder] exist in the tree at the same time. Therefore, the widgets
 /// returned by these builders cannot include the same global key.
 ///
+/// `T` refers to the type of data returned by the route when the container
+/// is closed. This value can be accessed in the `onClosed` function.
+///
 // TODO(goderbauer): Add example animations and sample code.
 ///
 /// See also:
 ///
 ///  * [Transitions with animated containers](https://material.io/design/motion/choreography.html#transformation)
 ///    in the Material spec.
-class OpenContainer extends StatefulWidget {
+@optionalTypeArgs
+class OpenContainer<T extends Object> extends StatefulWidget {
   /// Creates an [OpenContainer].
   ///
   /// All arguments except for [key] must not be null. The arguments
-  /// [closedBuilder] and [openBuilder] are required.
+  /// [openBuilder] and [closedBuilder] are required.
   const OpenContainer({
     Key key,
     this.closedColor = Colors.white,
@@ -167,7 +190,13 @@
   final ShapeBorder openShape;
 
   /// Called when the container was popped and has returned to the closed state.
-  final VoidCallback onClosed;
+  ///
+  /// The return value from the popped screen is passed to this function as an
+  /// argument.
+  ///
+  /// If no value is returned via [Navigator.pop] or [OpenContainer.openBuilder.action],
+  /// `null` will be returned by default.
+  final ClosedCallback<T> onClosed;
 
   /// Called to obtain the child for the container in the closed state.
   ///
@@ -177,7 +206,7 @@
   ///
   /// The `action` callback provided to the builder can be called to open the
   /// container.
-  final OpenContainerBuilder closedBuilder;
+  final CloseContainerBuilder closedBuilder;
 
   /// Called to obtain the child for the container in the open state.
   ///
@@ -187,7 +216,7 @@
   ///
   /// The `action` callback provided to the builder can be called to close the
   /// container.
-  final OpenContainerBuilder openBuilder;
+  final OpenContainerBuilder<T> openBuilder;
 
   /// Whether the entire closed container can be tapped to open it.
   ///
@@ -218,10 +247,10 @@
   final bool useRootNavigator;
 
   @override
-  _OpenContainerState createState() => _OpenContainerState();
+  _OpenContainerState<T> createState() => _OpenContainerState<T>();
 }
 
-class _OpenContainerState extends State<OpenContainer> {
+class _OpenContainerState<T> extends State<OpenContainer<T>> {
   // Key used in [_OpenContainerRoute] to hide the widget returned by
   // [OpenContainer.openBuilder] in the source route while the container is
   // opening/open. A copy of that widget is included in the
@@ -235,10 +264,10 @@
   final GlobalKey _closedBuilderKey = GlobalKey();
 
   Future<void> openContainer() async {
-    await Navigator.of(
+    final T data = await Navigator.of(
       context,
       rootNavigator: widget.useRootNavigator,
-    ).push(_OpenContainerRoute(
+    ).push(_OpenContainerRoute<T>(
       closedColor: widget.closedColor,
       openColor: widget.openColor,
       closedElevation: widget.closedElevation,
@@ -254,7 +283,7 @@
       useRootNavigator: widget.useRootNavigator,
     ));
     if (widget.onClosed != null) {
-      widget.onClosed();
+      widget.onClosed(data);
     }
   }
 
@@ -350,7 +379,7 @@
   }
 }
 
-class _OpenContainerRoute extends ModalRoute<void> {
+class _OpenContainerRoute<T> extends ModalRoute<T> {
   _OpenContainerRoute({
     @required this.closedColor,
     @required this.openColor,
@@ -506,8 +535,8 @@
   final Color openColor;
   final double openElevation;
   final ShapeBorder openShape;
-  final OpenContainerBuilder closedBuilder;
-  final OpenContainerBuilder openBuilder;
+  final CloseContainerBuilder closedBuilder;
+  final OpenContainerBuilder<T> openBuilder;
 
   // See [_OpenContainerState._hideableKey].
   final GlobalKey<_HideableState> hideableKey;
@@ -587,7 +616,7 @@
   }
 
   @override
-  bool didPop(void result) {
+  bool didPop(T result) {
     _takeMeasurements(
       navigatorContext: subtreeContext,
       delayForSourceRoute: true,
@@ -667,8 +696,8 @@
     return wasInProgress && isInProgress;
   }
 
-  void closeContainer() {
-    Navigator.of(subtreeContext).pop();
+  void closeContainer({T returnValue}) {
+    Navigator.of(subtreeContext).pop(returnValue);
   }
 
   @override
diff --git a/packages/animations/test/open_container_test.dart b/packages/animations/test/open_container_test.dart
index df54258..77e9eb9 100644
--- a/packages/animations/test/open_container_test.dart
+++ b/packages/animations/test/open_container_test.dart
@@ -1486,7 +1486,7 @@
       (WidgetTester tester) async {
     bool hasClosed = false;
     final Widget openContainer = OpenContainer(
-      onClosed: () {
+      onClosed: (dynamic _) {
         hasClosed = true;
       },
       closedBuilder: (BuildContext context, VoidCallback action) {
@@ -1525,6 +1525,51 @@
     expect(hasClosed, isTrue);
   });
 
+  testWidgets(
+      'onClosed callback receives popped value when container has closed',
+      (WidgetTester tester) async {
+    bool value = false;
+    final Widget openContainer = OpenContainer<bool>(
+      onClosed: (bool poppedValue) {
+        value = poppedValue;
+      },
+      closedBuilder: (BuildContext context, VoidCallback action) {
+        return GestureDetector(
+          onTap: action,
+          child: const Text('Closed'),
+        );
+      },
+      openBuilder:
+          (BuildContext context, CloseContainerActionCallback<bool> action) {
+        return GestureDetector(
+          onTap: () => action(returnValue: true),
+          child: const Text('Open'),
+        );
+      },
+    );
+
+    await tester.pumpWidget(
+      _boilerplate(child: openContainer),
+    );
+
+    expect(find.text('Open'), findsNothing);
+    expect(find.text('Closed'), findsOneWidget);
+    expect(value, isFalse);
+
+    await tester.tap(find.text('Closed'));
+    await tester.pumpAndSettle();
+
+    expect(find.text('Open'), findsOneWidget);
+    expect(find.text('Closed'), findsNothing);
+
+    await tester.tap(find.text('Open'));
+    await tester.pumpAndSettle();
+
+    expect(find.text('Open'), findsNothing);
+    expect(find.text('Closed'), findsOneWidget);
+    expect(value, isTrue);
+  });
+
   Widget _createRootNavigatorTest({
     @required Key appKey,
     @required Key nestedNavigatorKey,