Nested Scopes (ScopedDependyMixin)
In this example, we demonstrate how to share nested scopes across widgets using ScopedDependyMixin. The key
difference from previous examples is that we are managing different dependency scopes at different levels of the widget
tree, while ensuring that each widget has access to the necessary dependencies.
App Overview
The app consists of multiple scopes:
- App level: Shares a
LoggerServicefor logging messages across the entire application. - MyHomePage level: Shares a
CounterServicethat uses theLoggerServicefrom the higher scope to log actions. - CounterButton: Accesses both
LoggerServiceandCounterServiceto log actions and update the counter. - CounterView: Displays the counter value while accessing both
LoggerServiceandCounterService.
Step 1: Define the LoggerService
The LoggerService is used to log messages, and its implementation is provided at the App scope.
abstract class LoggerService {
void log(String message);
}
class ConsoleLoggerService implements LoggerService {
void log(String message) {
print(message);
}
}
Step 2: Define the CounterService
The CounterService manages the counter state and interacts with LoggerService to log updates. Here, we use a simple
implementation of CounterService that increments the counter by a step of 5.
abstract class CounterService {
int get counter;
void increment();
}
class CounterServiceImpl implements CounterService {
int _counter;
final LoggerService loggerService;
CounterServiceImpl(this._counter, this.loggerService);
int get counter => _counter;
void increment() {
_counter += 5;
loggerService.log('Counter incremented to $_counter');
}
}
Step 3: Create the DependyModule for MyApp
In the MyApp widget, we define the DependyModule that provides the LoggerService to the entire application.
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with ScopedDependyMixin {
Widget build(BuildContext context) {
return shareDependyScope(
child: MaterialApp(
title: 'Example 5 (Share Multiple Scopes using ScopedDependyMixin)',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
),
);
}
DependyModule moduleBuilder() {
return DependyModule(
providers: {
// Provide LoggerService at the App level
DependyProvider<LoggerService>(
(_) => ConsoleLoggerService(),
),
},
modules: {},
);
}
}
Step 4: Create the DependyModule for MyHomePage
In the MyHomePage widget, we define a separate DependyModule that provides CounterService. This service depends on
LoggerService, which is provided at a higher scope (the App level).
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with ScopedDependyMixin {
Widget build(BuildContext context) {
return shareDependyScope(
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme
.of(context)
.colorScheme
.inversePrimary,
title: const Text(
'Example 5 (Share Multiple Scopes using ScopedDependyMixin)',
),
),
body: const Center(
child: CounterView(),
),
floatingActionButton: const CounterButton(),
),
);
}
DependyModule moduleBuilder() {
return DependyModule(
providers: {
// Provide the CounterService at this widget scope
DependyProvider<CounterService>(
(dependy) async {
final loggerService = await dependy<LoggerService>();
// Create a CounterService with increment step of 5
return CounterServiceImpl(5, loggerService);
},
dependsOn: {
LoggerService,
},
),
},
modules: {
// Import the higher scope module
// In this case it would be the scope of App which does contain the LoggerService
parentModule(),
},
);
}
}
Step 5: Define the CounterButton Widget
The CounterButton widget interacts with both LoggerService and CounterService. It logs the action when the button
is pressed and increments the counter.
class CounterButton extends StatelessWidget {
const CounterButton({super.key});
Widget build(BuildContext context) {
final scope = getDependyScope(context);
return FloatingActionButton(
onPressed: () async {
final loggerService = await scope.dependy<LoggerService>();
loggerService.log('CounterButton onPressed');
final counterService = await scope.dependy<CounterService>();
counterService.increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
);
}
}
Step 6: Define the CounterView Widget
The CounterView widget displays the current counter value and also logs each build. It listens to updates from
CounterService while accessing the LoggerService.
class CounterView extends StatelessWidget {
const CounterView({super.key});
Widget build(BuildContext context) {
return ScopedDependyConsumer(
builder: (context, scope) {
return FutureBuilder(
future: scope.watchDependy<CounterService>(),
builder: (context, snapshot) {
final counterService = snapshot.data;
// Log every time the CounterView widget is built
scope.dependy<LoggerService>().then(
(value) => value.log("CounterView build"),
);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${counterService?.counter}',
style: Theme
.of(context)
.textTheme
.headlineMedium,
),
],
);
},
);
},
);
}
}
Key Concepts
-
Nested Scopes: This example demonstrates how to share multiple dependency scopes in the same app. The
LoggerServiceis provided at the App level, while theCounterServiceis provided at a more localized level (inMyHomePage). -
ScopedDependyMixin: By applying
ScopedDependyMixinto bothMyAppandMyHomePage, each widget can share and access dependencies within its scope, even if those dependencies exist at different levels in the widget tree. -
ScopedDependyConsumer: Used to listen for changes in a dependency and rebuild the widget accordingly. This is used in
CounterButtonandCounterViewto observe changes toCounterServicewhile also accessingLoggerService. -
Inheritance of Modules: The
parentModule()function ensures thatLoggerService, provided at the App level, is accessible to theCounterServicein the child scope.
Example Code
The complete code for this example can be found on GitHub: example_5/app.dart