pushScope is a great solution for controlling the lifecycle of a Widget manager class. However, it causes issues with other parts of the app that wish to register objects to the baseline scope at runtime. They will get caught in a Widget's scope, leading to the object getting unexpectedly unregistered.
There are two options to solve this problem:
Create a named scope for the object to register at runtime
This works, but now requires unnecessarily naming a scope, when the app would prefer to register to the baseline scope
Create the Widget scopes as isFinal so that other registered objects are propagated up to the parent scope
This is currently not possible with watch_it because pushScope does not expose the isFinal field for when it calls get_it's pushNewScope
Proposed solution:
Allow watch_it's pushScope to be supplied with isFinal
Below is an example application which exemplifies the issue. It contains three Widgets, each with their own manager class. Notably, the LoginManager registers a singleton at runtime, but because the Widget scopes are not final, LoginManager registers its singleton to the Form scope, which gets disposed. This triggers an exception when trying to retrieve the object from LoggedInPage. If the Widget scopes can be created with isFinal, the app behaves as expected.
import 'package:flutter/material.dart';
import 'package:watch_it/watch_it.dart';
void main() {
runApp(const MaterialApp(home: Scaffold(body: LoginPage())));
}
class Model {
final int data;
Model(this.data);
}
class FormManager {
int getData() => 69;
}
class Form extends StatelessWidget with WatchItMixin {
const Form({super.key});
@override
Widget build(BuildContext context) {
pushScope(init: (g) => g.registerSingleton<FormManager>(FormManager()));
return Center(
child: FilledButton.tonal(
onPressed: () {
// Submitting the form will cause this widget to be disposed and the
// GetIt scope will be popped
Navigator.pop(context, di.get<FormManager>().getData());
},
child: const Text('Submit'),
),
);
}
}
class LoginManager {
void createModel(int data) {
di.registerSingleton<Model>(Model(data));
}
}
class LoginPage extends StatelessWidget with WatchItMixin {
const LoginPage({super.key});
void showModal(BuildContext context) {
showModalBottomSheet(context: context, builder: (context) => const Form())
.then(
(data) {
if (data != null) {
// PROBLEM:
// `createModel` will register a singleton but it will register to the
// `Form`'s scope, which will soon be disposed. `createModel` would
// like to register to the baseline scope
di.get<LoginManager>().createModel(data);
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const LoggedInPage()));
}
},
);
}
@override
Widget build(BuildContext context) {
pushScope(init: (g) => g.registerSingleton<LoginManager>(LoginManager()));
return Scaffold(
body: Center(
child: FilledButton.tonal(
onPressed: () => showModal(context),
child: const Text('Login'),
),
),
);
}
}
class LoggedInManager {
void doSomethingWithModel() {
di.get<Model>();
}
void logOut() {
di.unregister<Model>();
}
}
class LoggedInPage extends StatelessWidget with WatchItMixin {
const LoggedInPage({super.key});
@override
Widget build(BuildContext context) {
pushScope(
init: (g) => g.registerSingleton<LoggedInManager>(LoggedInManager()));
return PopScope(
onPopInvoked: (didPop) {
if (didPop) {
di.get<LoggedInManager>().logOut();
}
},
child: Scaffold(
body: Center(
child: FilledButton.tonal(
onPressed: () {
// PROBLEM:
// This will trigger an exception because `Model` has already been
// unregistered when `Form`'s scope was popped
di.get<LoggedInManager>().doSomethingWithModel();
},
child: const Text('Do Something'),
),
),
),
);
}
}
pushScope
is a great solution for controlling the lifecycle of a Widget manager class. However, it causes issues with other parts of the app that wish to register objects to the baseline scope at runtime. They will get caught in a Widget's scope, leading to the object getting unexpectedly unregistered.There are two options to solve this problem:
isFinal
so that other registered objects are propagated up to the parent scopepushScope
does not expose theisFinal
field for when it calls get_it'spushNewScope
Proposed solution: Allow watch_it's
pushScope
to be supplied withisFinal
Below is an example application which exemplifies the issue. It contains three Widgets, each with their own manager class. Notably, the
LoginManager
registers a singleton at runtime, but because the Widget scopes are not final,LoginManager
registers its singleton to theForm
scope, which gets disposed. This triggers an exception when trying to retrieve the object fromLoggedInPage
. If the Widget scopes can be created withisFinal
, the app behaves as expected.