diff --git a/examples/api/lib/widgets/inherited_model/inherited_model.0.dart b/examples/api/lib/widgets/inherited_model/inherited_model.0.dart new file mode 100644 index 000000000000..feb88874e532 --- /dev/null +++ b/examples/api/lib/widgets/inherited_model/inherited_model.0.dart @@ -0,0 +1,165 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flutter code sample for InheritedModel + +import 'package:flutter/material.dart'; + +enum LogoAspect { backgroundColor, large } + +void main() => runApp(const InheritedModelApp()); + +class InheritedModelApp extends StatelessWidget { + const InheritedModelApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: InheritedModelExample(), + ); + } +} + +class LogoModel extends InheritedModel { + const LogoModel({ + super.key, + this.backgroundColor, + this.large, + required super.child, + }); + + final Color? backgroundColor; + final bool? large; + + static Color? backgroundColorOf(BuildContext context) { + return InheritedModel.inheritFrom(context, aspect: LogoAspect.backgroundColor)?.backgroundColor; + } + + static bool sizeOf(BuildContext context) { + return InheritedModel.inheritFrom(context, aspect: LogoAspect.large)?.large ?? false; + } + + @override + bool updateShouldNotify(LogoModel oldWidget) { + return backgroundColor != oldWidget.backgroundColor || + large != oldWidget.large; + } + + @override + bool updateShouldNotifyDependent(LogoModel oldWidget, Set dependencies) { + if (backgroundColor != oldWidget.backgroundColor && dependencies.contains(LogoAspect.backgroundColor)) { + return true; + } + if (large != oldWidget.large && dependencies.contains(LogoAspect.large)) { + return true; + } + return false; + } +} + +class InheritedModelExample extends StatefulWidget { + const InheritedModelExample({super.key}); + + @override + State createState() => _InheritedModelExampleState(); +} + +class _InheritedModelExampleState extends State { + bool large = false; + Color color = Colors.blue; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('InheritedModel Sample')), + body: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Center( + child: LogoModel( + backgroundColor: color, + large: large, + child: const BackgroundWidget( + child: LogoWidget(), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Rebuilt Background'), + duration: Duration(milliseconds: 500), + ), + ); + setState(() { + if (color == Colors.blue){ + color = Colors.red; + } else { + color = Colors.blue; + } + }); + }, + child: const Text('Update background'), + ), + ElevatedButton( + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Rebuilt LogoWidget'), + duration: Duration(milliseconds: 500), + ), + ); + setState(() { + large = !large; + }); + }, + child: const Text('Resize Logo'), + ), + ], + ) + ], + ), + ); + } +} + +class BackgroundWidget extends StatelessWidget { + const BackgroundWidget({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) { + final Color color = LogoModel.backgroundColorOf(context)!; + + return AnimatedContainer( + padding: const EdgeInsets.all(12.0), + color: color, + duration: const Duration(seconds: 2), + curve: Curves.fastOutSlowIn, + child: child, + ); + } +} + +class LogoWidget extends StatelessWidget { + const LogoWidget({super.key}); + + @override + Widget build(BuildContext context) { + final bool largeLogo = LogoModel.sizeOf(context); + + return AnimatedContainer( + padding: const EdgeInsets.all(20.0), + duration: const Duration(seconds: 2), + curve: Curves.fastLinearToSlowEaseIn, + alignment: Alignment.center, + child: FlutterLogo(size: largeLogo ? 200.0 : 100.0), + ); + } +} diff --git a/examples/api/test/widgets/inherited_model/inherited_model.0_test.dart b/examples/api/test/widgets/inherited_model/inherited_model.0_test.dart new file mode 100644 index 000000000000..3f3f9a569efe --- /dev/null +++ b/examples/api/test/widgets/inherited_model/inherited_model.0_test.dart @@ -0,0 +1,34 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/inherited_model/inherited_model.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Rebuild widget using InheritedModel', (WidgetTester tester) async { + await tester.pumpWidget( + const example.InheritedModelApp(), + ); + + BoxDecoration? decoration = tester.widget( + find.byType(AnimatedContainer).first, + ).decoration as BoxDecoration?; + expect(decoration!.color, Colors.blue); + + await tester.tap(find.text('Update background')); + await tester.pumpAndSettle(); + decoration = tester.widget( + find.byType(AnimatedContainer).first, + ).decoration as BoxDecoration?; + expect(decoration!.color, Colors.red); + + double? size = tester.widget(find.byType(FlutterLogo)).size; + expect(size, 100.0); + await tester.tap(find.text('Resize Logo')); + await tester.pumpAndSettle(); + size = tester.widget(find.byType(FlutterLogo)).size; + expect(size, 200.0); + }); +} diff --git a/packages/flutter/lib/src/widgets/inherited_model.dart b/packages/flutter/lib/src/widgets/inherited_model.dart index 3b5622754cdc..93ebfcd0ff27 100644 --- a/packages/flutter/lib/src/widgets/inherited_model.dart +++ b/packages/flutter/lib/src/widgets/inherited_model.dart @@ -34,7 +34,7 @@ import 'framework.dart'; /// ```dart /// class MyModel extends InheritedModel { /// // ... -/// static MyModel of(BuildContext context, String aspect) { +/// static MyModel? of(BuildContext context, String aspect) { /// return InheritedModel.inheritFrom(context, aspect: aspect); /// } /// } @@ -44,37 +44,42 @@ import 'framework.dart'; /// be rebuilt when the `foo` aspect of `MyModel` changes. If the aspect /// is null, then the model supports all aspects. /// +/// {@tool snippet} /// When the inherited model is rebuilt the [updateShouldNotify] and /// [updateShouldNotifyDependent] methods are used to decide what /// should be rebuilt. If [updateShouldNotify] returns true, then the /// inherited model's [updateShouldNotifyDependent] method is tested for /// each dependent and the set of aspect objects it depends on. /// The [updateShouldNotifyDependent] method must compare the set of aspect -/// dependencies with the changes in the model itself. -/// -/// For example: +/// dependencies with the changes in the model itself. For example: /// /// ```dart /// class ABModel extends InheritedModel { -/// ABModel({ this.a, this.b, Widget child }) : super(child: child); +/// const ABModel({ +/// super.key, +/// this.a, +/// this.b, +/// required super.child, +/// }); /// -/// final int a; -/// final int b; +/// final int? a; +/// final int? b; /// /// @override -/// bool updateShouldNotify(ABModel old) { -/// return a != old.a || b != old.b; +/// bool updateShouldNotify(ABModel oldWidget) { +/// return a != oldWidget.a || b != oldWidget.b; /// } /// /// @override -/// bool updateShouldNotifyDependent(ABModel old, Set aspects) { -/// return (a != old.a && aspects.contains('a')) -/// || (b != old.b && aspects.contains('b')) +/// bool updateShouldNotifyDependent(ABModel oldWidget, Set dependencies) { +/// return (a != oldWidget.a && dependencies.contains('a')) +/// || (b != oldWidget.b && dependencies.contains('b')); /// } /// /// // ... /// } /// ``` +/// {@end-tool} /// /// In the previous example the dependencies checked by /// [updateShouldNotifyDependent] are just the aspect strings passed to @@ -84,6 +89,14 @@ import 'framework.dart'; /// then changes in the model will cause the widget to be rebuilt /// unconditionally. /// +/// {@tool dartpad} +/// This example shows how to implement [InheritedModel] to rebuild a +/// widget based on a qualified dependence. When tapped on the "Resize Logo" button +/// only the logo widget is rebuilt while the background widget remains unaffected. +/// +/// ** See code in examples/api/lib/widgets/inherited_model/inherited_model.0.dart ** +/// {@end-tool} +/// /// See also: /// /// * [InheritedWidget], an inherited widget that only notifies dependents