Inherited Widget
In Flutter is a base class that allows classes that extend it to propagate information down the tree efficiently. Basically, it works by notifying registered build contexts if there is any change. Therefore, the descendant widgets that depend on it will only be rebuilt if necessary. In addition, the descendant widgets can also get the nearest instance of the inherited widget and access non-private properties.
You might have already used the inherited widget unknowingly in Flutter. As Flutter framework uses it internally in order to solve multiple uses case such as Theming, Device sizing, routing, and so on.
You may also check out our Flutter app development company.
Let’s talk about some main constructors of Inherited Widget.
1 2 3 |
const InheritedWidget({ Key? key, required Widget child }): super(key: key, child: child); |
child: We use the child property to use widgets like row, column stack, etc. so that we can exclude many children.
key: The key property is a one-of-a-kind parameter. Basically, every widget creator can be found if the two widget’s runtime type and key property are is equal respectively, then the new widget updates the old widget by calling it the underlying element. Otherwise, the old element is updated. The tree is removed from the tree, the new widget is converted to an element, and the new element is inserted into the tree.
The roadmap: what are we going to do with Inherited Widget In Flutter?
Since we are going to treat the current theme as a state of the app, we’re going to need an Inherited Widget wrapped by a Stateful Widget. Our Inherited Widget will expose the state of the Stateful Widget which is containing it.
Step 1: Let’s create our themes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import 'package:flutter/material.dart'; enum ThemeEnums { LIGHT, DARK, DARKER } class MyThemes { static final ThemeData lightTheme = ThemeData( primaryColor: Colors.blue, brightness: Brightness.light, ); static final ThemeData darkTheme = ThemeData( primaryColor: Colors.grey, brightness: Brightness.dark, ); static final ThemeData darkerTheme = ThemeData( primaryColor: Colors.black, brightness: Brightness.dark, ); static ThemeData getThemeFromEnums(ThemeEnums themeKey) { switch (themeKey) { case ThemeEnums.LIGHT: return lightTheme; case ThemeEnums.DARK: return darkTheme; case ThemeEnums.DARKER: return darkerTheme; default: return lightTheme; } } } |
Step 2: setup our Stateful Widget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import 'package:dynamic_themes/themes.dart'; class CustomTheme extends StatefulWidget { final Widget child; final ThemeEnums initialThemeKey; const CustomTheme({ Key key, this.initialThemeKey, @required this.child, }) : super(key: key); @override CustomThemeState createState() => new CustomThemeState(); } class CustomThemeState extends State<CustomTheme> { ThemeData _theme; ThemeData get theme => _theme; @override void initState() { _theme = MyThemes.getThemeFromEnums(widget.initialThemeKey); super.initState(); } void changeTheme(ThemeEnums themeKey) { setState(() { _theme = MyThemes.getThemeFromEnums(themeKey); }); } @override Widget build(BuildContext context) { return Container(); //nothing here for now! } } |
We’ve defined our CustomTheme
StatefulWidget, which contains the data for the currently selected theme.
We initialise the theme with an initialThemeKey
and provide a changeTheme
method that allows us to change the theme.
We need now to wrap this state inside an InheritedWidget, so that we can share our theme across the app.
Step 3: wrapping the state with an Inherited Widget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class _CustomTheme extends InheritedWidget { final CustomThemeState data; _CustomTheme({ this.data, Key key, @required Widget child, }) : super(key: key, child: child); @override bool updateShouldNotify(_CustomTheme oldWidget) { return true; } } |
We’re going to expose the _CustomTheme
InheritedWidget through our Stateful widget, as we’ll see in a bit.
Step 4: putting it all together
We’re now going to update our Stateful Widget in order to use the Inherited Widget and to expose the theme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:dynamic_themes/themes.dart'; class _CustomTheme extends InheritedWidget { final CustomThemeState data; _CustomTheme({ this.data, Key key, @required Widget child, }) : super(key: key, child: child); @override bool updateShouldNotify(_CustomTheme oldWidget) { return true; } } class CustomTheme extends StatefulWidget { final Widget child; final ThemeEnums initialThemeKey; const CustomTheme({ Key key, this.initialThemeKey, @required this.child, }) : super(key: key); @override CustomThemeState createState() => new CustomThemeState(); static ThemeData of(BuildContext context) { _CustomTheme inherited = (context.dependOnInheritedWidgetOfExactType<_CustomTheme>()); return inherited.data.theme; } static CustomThemeState instanceOf(BuildContext context) { _CustomTheme inherited = (context.dependOnInheritedWidgetOfExactType<_CustomTheme>()); return inherited.data; } } class CustomThemeState extends State<CustomTheme> { ThemeData _theme; ThemeData get theme => _theme; @override void initState() { _theme = MyThemes.getThemeFromEnums(widget.initialThemeKey); super.initState(); } void changeTheme(ThemeEnums themeKey) { setState(() { _theme = MyThemes.getThemeFromEnums(themeKey); }); } @override Widget build(BuildContext context) { return new _CustomTheme( data: this, child: widget.child, ); } } |
Let’s wrap this up with our UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
import 'package:dynamic_themes/custom_theme.dart'; import 'package:dynamic_themes/themes.dart'; import 'package:flutter/material.dart'; class HomeScreen extends StatefulWidget { @override _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { void _changeTheme(BuildContext buildContext, ThemeEnums key) { CustomTheme.instanceOf(buildContext).changeTheme(key); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).primaryColor, title: Text("Homepage"), ), body: Padding( padding: const EdgeInsets.all(16), child: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: <Widget>[ RaisedButton( onPressed: () { _changeTheme(context, ThemeEnums.LIGHT); }, child: Text("Light!"), ), RaisedButton( onPressed: () { _changeTheme(context, ThemeEnums.DARK); }, child: Text("Dark!"), ), RaisedButton( onPressed: () { _changeTheme(context, ThemeEnums.DARKER); }, child: Text("Darker!"), ), Divider(height: 100,), AnimatedContainer( duration: Duration(milliseconds: 500), color: Theme.of(context).primaryColor, width: 100, height: 100, ), ], ), ), ), ); } } |
This is an ordinary Flutter screen, referencing the Theme
as the usual apps do. We could achieve this because we’ve placed the CustomTheme
to the root of our MaterialApp
this way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import 'package:dynamic_themes/custom_theme.dart'; import 'package:dynamic_themes/home.dart'; import 'package:flutter/material.dart'; import 'package:dynamic_themes/themes.dart'; void main() { runApp( CustomTheme( initialThemeKey: ThemeEnums.LIGHT, child: MyApp(), ), ); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Dynamic themes demo', theme: CustomTheme.of(context), home: HomeScreen(), ); } } |
Since the whole app did not have to reference anything different than the classic Theme
, we could do all of this on an already existing app. We’ll just need to define our CustomTheme
and put it on top of our existing app and that would be it, no further changes required, no nasty renaming, nothing at all.
What’s happening under the hood?
- We change the theme calling
CustomTheme.instanceOf(context).changeTheme(ourThemeKey)
. - The change triggers a
setState
updating the theme with the new value inside theCustomTheme
Stateful Widget.
Due to the setState
, CustomTheme
`rebuilds. The Inherited Widget gets rebuilt as well, and since its updateShouldNotify
returns true
all the depending widgets (MaterialApp
in our case) are updated, resulting in a UI update with the new color scheme.
That’s all for this Article 🎊 .
Conclusion
We learned about the Inherited Widget in Flutter with theme changes in this blog.
Visit the link for additional information on the Inherited Widget in Flutter.
Thanks for reading this blog. You can also check other blogs from here for more knowledge.