In Flutter, you’ve probably encountered third-party state management packages like Provider or Bloc. You’ve likely worked with core utilities such as Theme, Navigator, or MediaQuery. These tools all have something in common: they rely on InheritedWidget, a fundamental widget that passes state down the widget tree. In this tutorial, you’ll utilize this power to finish the Weather++ app. By the end, you’ll be able to answer the following questions:
- What is InheritedWidget and how does it function?
- How do you utilize InheritedWidget?
Getting Started
Download the project by clicking the Download materials button at the top or bottom of this tutorial. Unzip the project, and you’ll find two folders: starter and final. The final directory contains the completed project, while the starter directory is where you’ll begin working. Open the starter project in the latest version of Android Studio or Visual Studio Code, and you’ll see a similar project structure:
The red-outlined folders are specific to this project, while the others are standard Flutter boilerplate.
- assets/secrets.json: JSON file for storing keys like the API key; this key will be used to fetch weather data in later steps.
- lib/location: Contains Dart files for managing user location. More files will be added to this directory later.
- lib/weather: Handles weather-specific Dart files such as widgets and data classes.
- lib/constants.dart: Holds app-wide constants.
- lib/home.dart: Includes widgets seen when the app is launched for the first time.
- lib/main.dart: Initializes the app and wraps the HomeWidget in a MaterialApp.
- lib/secrets.dart: Contains logic for loading the secrets in assets/secrets.json.
Now, open pubspec.yaml and click the Pub get tab in your IDE. Run the project to view it on your target emulator or device:
This represents the basic structure of the app. The white boxes serve as placeholders for widgets that will be replaced as you progress through this tutorial.
Overview of Inherited Widgets
Begin this section by exploring the concept of InheritedWidget, understanding its definition, importance, and functionality. Later, delve into how it differs from StatefulWidget and StatelessWidget.
What Is an InheritedWidget?
InheritedWidget is a core widget in the Flutter framework that facilitates the efficient passing of state down the widget tree. It utilizes the build context to share state and updates dependent widgets when this state changes. Without InheritedWidget, sharing ThemeData like light and dark mode between a parent and child widget would require manual passing of the theme to each widget. This not only creates tight coupling but also fails to automatically rebuild widgets when the theme changes, potentially causing UI inconsistencies. By leveraging InheritedWidget, child widgets can access the latest ThemeData as shown below:
class ChildWidget extends StatelessWidget { const ChildWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return IconButton( onPressed: () { // TODO: Implement functionality to change the app theme. }, icon: Icon(Theme.of(context).brightness == Brightness.dark ? Icons.nightlight : Icons.sunny)); } }
With this approach, you no longer need to pass ThemeData to the widget, and any theme changes will trigger automatic rebuilding.
Differences Between StatelessWidget, StatefulWidget, and InheritedWidget
A StatelessWidget is immutable, meaning its properties cannot be changed once set. It is suitable for static content that does not require modification by the widget itself. In contrast, a StatefulWidget can be mutable, maintaining separate state object(s) that can change over the widget’s lifecycle. This makes it ideal for dynamic content that may evolve over time. On the other hand, InheritedWidget is a special widget designed to efficiently propagate information down the widget tree. Instead of manually passing data to each widget, descendants of an InheritedWidget can directly access the data it holds, making it a robust tool for state propagation and management.
State Propagation and State Management
To demonstrate and comprehend InheritedWidget, you’ll focus on the Weather++ app, the project downloaded in the previous steps. Using InheritedWidget, you’ll construct a location picker whose selection state can be read and modified from any widget in the build tree. This selected location will be used to retrieve and display current and future weather forecasts.
State Propagation with InheritedLocation
The initial step in utilizing an InheritedWidget is to subclass it. Therefore, in the starter project, create the file inherited_location.dart within the location package and add the following code:
import 'package:flutter/widgets.dart'; import 'location_data.dart'; class InheritedLocation extends InheritedWidget { final LocationData? location; const InheritedLocation( {Key? key, required Widget child, required this.location}) : super(key: key, child: child); }
InheritedLocation is an InheritedWidget that triggers a rebuild of all dependent widgets when the location changes. LocationData serves as a data container holding the latitude, longitude, and name of a specific location. The latitude and longitude are used to fetch weather data from OpenWeather, a popular service for real-time weather information. The name will be displayed in the location picker widget.
But how does InheritedLocation know when to rebuild? This is where updateShouldNotify() comes into play. Override it below the constructor as shown below:
@override bool updateShouldNotify(InheritedLocation oldWidget) { return oldWidget.location != location || oldWidget.location?.name.isEmpty == true; }
This method triggers rebuilding of dependent widgets when the location changes or if its name is empty. Further details on the empty condition will be discussed later.
How does InheritedLocation identify its dependencies? The answer lies in context.dependOnInheritedWidgetOfExactType(). Calling this using the context of the dependent widget accomplishes two things: registering the calling widget as a dependent of InheritedLocation and returning a reference to the InheritedLocation instance.
For brevity, encapsulate this code within a helper function called of() in the InheritedLocation class, below updateShouldNotify():
static InheritedLocation of(BuildContext context) { final result = context.dependOnInheritedWidgetOfExactType(); assert(result != null, 'No InheritedLocation found in context'); return result!; }
This method allows you to utilize InheritedLocation.of(context) similar to Theme, Provider, and other widgets powered by InheritedWidget.