scopo 0.3.3
scopo: ^0.3.3 copied to clipboard
A Flutter package for managing scopes and dependency injection within the widget tree
scopo #
A robust Flutter package for managing scopes, dependency injection, and state management within the widget tree. scopo provides a clean, extensive API for handling dependencies and state with a focus on lifecycle management and async initialization.
Features #
- Scope Management: defining scopes that hold dependencies and state.
- Dependency Injection: Access dependencies effortlessly down the widget tree.
- State Management: Built-in state management linked to scopes.
- Async Initialization: Robust handling of async dependency initialization with loading and error states.
- Specialized Scopes: specialized widgets for simple values, models, and listenables.
- Selectors: Efficient rebuilding of widgets by selecting specific parts of state or dependencies.
Installation #
Add scopo to your pubspec.yaml:
dependencies:
scopo: ^0.3.0
Core Concepts #
Scope #
The Scope widget is the foundation of the package. It manages:
- Dependencies: An extended class of
ScopeDependencies. - State: An extended class of
ScopeState.
It handles the lifecycle of dependencies (initialization and disposal) and provides them to its descendants.
1. Define Dependencies
Create a class implementing ScopeDependencies.
class AppDependencies implements ScopeDependencies {
final SharedPreferences sharedPreferences;
AppDependencies({required this.sharedPreferences});
// Initialization logic
static Stream<ScopeInitState<String, AppDependencies>> init() async* {
yield ScopeProgress('Initializing Storage...');
final sharedPreferences = await SharedPreferences.getInstance();
yield ScopeReady(AppDependencies(sharedPreferences: sharedPreferences));
}
@override
Future<void> dispose() async {
// Dipose resources if needed
}
}
2. Define State
Create a class extending ScopeState.
final class AppState extends ScopeState<App, AppDependencies, AppState> {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyDependents();
}
@override
Widget build(BuildContext context) => HomeScreen();
}
3. Create the Scope
final class App extends Scope<App, AppDependencies, AppState> {
const App({super.key});
@override
Stream<ScopeInitState<String, AppDependencies>> init() => AppDependencies.init();
@override
AppState createState() => AppState();
// Initialization UI handlers
@override
Widget buildOnInitializing(BuildContext context, Object? progress) =>
const CircularProgressIndicator();
@override
Widget buildOnError(BuildContext context, Object error, StackTrace stack, Object? progress) =>
Text('Error: $error');
// Helper accessors
static AppState of(BuildContext context) =>
Scope.of<App, AppDependencies, AppState>(context);
static V select<V>(BuildContext context, V Function(AppState state) selector) =>
Scope.select<App, AppDependencies, AppState, V>(context, (state) => selector(state));
}
Specialized Scopes #
scopo provides lightweight alternatives for specific use cases.
ScopeWidget #
Inject a simple generic value or widget-specific data down the tree.
class MyConfig extends ScopeWidgetBase<MyConfig> {
final String apiKey;
const MyConfig({required this.apiKey, required this.child});
final Widget child;
@override
Widget build(BuildContext context) => child;
static MyConfig of(BuildContext context) =>
ScopeWidgetBase.of<MyConfig>(context, listen: false);
}
ScopeModel #
Inject a pure Dart class (Model) that doesn't need the full overhead of a Scope.
class UserModel {
final String name;
UserModel(this.name);
}
// In widget tree
ScopeModel<UserModel>(
create: (context) => UserModel('Alice'),
child: ConsumerWidget(),
)
// Access
final user = ScopeModel.of<UserModel>(context);
ScopeNotifier #
Automatically manage Listenables (like ChangeNotifier or ValueNotifier). It handles disposal automatically.
class Counter extends ValueNotifier<int> {
Counter() : super(0);
}
// In widget tree
ScopeNotifier<Counter>(
create: (context) => Counter(),
child: CounterView(),
)
ScopeAsyncInitializer #
Handle single-shot asynchronous initialization.
ScopeAsyncInitializer<Database>(
init: () async => await openDatabase(),
buildOnInitializing: (context) => LoadingScreen(),
buildOnReady: (context, database) => AppContent(database: database),
)
ScopeStreamInitializer #
Handle stream-based initialization, useful for reporting progress during startup.
ScopeStreamInitializer<AppDeps>(
init: () async* {
yield ScopeProgress('Loading...');
// ... load resources
yield ScopeReady(deps);
},
buildOnInitializing: (context, progress) => LoadingScreen(msg: progress),
buildOnReady: (context, deps) => AppHome(),
)
Usage #
Accessing Data #
You can access the Scope's State or Dependencies using Scope.of or static helpers you define.
// Get State (listen: true by default)
final appState = App.of(context);
// Select specific value to minimize rebuilds
final counter = App.select(context, (state) => state.counter);