Last updated:
0 purchases
flutter scope
flutter_scope #
A declarative dependency injection library which use dart syntax and flutter style
Features #
Configuration is aligned with syntax with dart language
Scope strategy is aligned with scoping of functions
Can handle async setup
Using Observable\States as notification system with composition in mind
Using StatesBuilder to map a sequence of state to widget
Using StatesListener to add a listener in flutter layer
Table Of Content #
flutter_scope
Features
Table Of Content
Packages
Quick Tour
Usage of FlutterScope(...)
Usage of name
Usage of FlutterScope.async(...)
Usage of child scope
Usage of InheritedScope
Usage of FlutterScope's parentScope parameter
Usage of States
Usage of States.computed
Usage of states.convert
Usage of StatesBuilder(...)
Usage of StatesBuilder with States.computed operator
Usage of StatesListener(...)
Usage of StatesListener with states.convert operator
dart_scope
Features
Table Of Content
Quick Tour
Usage of Scope.root(...)
Usage of name
Scope.root(...) async setup
Usage of scope.push(...)
Usage of scope.has<T>(...)
Usage of scope.getOrNull<T>(...)
Usage of scope.dispose()
(Non)Lazily assignment
Advanced
Configurable
Inline Configurable
Decompose configuration
Compose configurations
Packages #
dart_scope - a dart's declarative dependency injection library
flutter_scope - a flutter's declarative dependency injection library
Quick Tour #
Let's explore with quick examples, assume we are developing a todos apps using ValueNotifier:
class TodosNotifier extends ValueNotifier<Map<String, Todo>> {
TodosNotifier([super._value = const {}]);
void addTodo(Todo todo) { ... }
void toggleTodoCompleted(String todoId) { ... }
void removeTodo(String todoId) { ... }
}
enum TodoFilter { all, completed, uncompleted }
class TodoFilterNotifier extends ValueNotifier<TodoFilter> {
TodoFilterNotifier([super._value = TodoFilter.all]);
void updateFilter(TodoFilter filter) { ... }
}
copied to clipboard
Usage of FlutterScope(...) #
Use FlutterScope(...) to create a scope with configurations:
FlutterScope(
configure: [
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
name: 'todosNotifier',
equal: (_) => TodosNotifier(),
),
FinalValueNotifier<TodoFilterNotifier, TodoFilter>(
name: 'todoFilterNotifier',
equal: (_) => TodoFilterNotifier(),
),
],
child: Builder(
builder: (context) {
final myTodosNotifier = context.scope.get<TodosNotifier>(name: 'todosNotifier');
final myTodoFilterNotifier = context.scope.get<TodoFilterNotifier>(name: 'todoFilterNotifier');
return ...;
}
),
);
copied to clipboard
A FlutterScope is created which expose singletons of TodosNotifier and TodoFilterNotifier. Later, these instances can be resolved by calling context.scope.get<T>(...).
Above example simulates:
void flutterScope() { // `{` is the start of scope
// create and exposed instances in current scope
final TodosNotifier todosNotifier = TodosNotifier();
final TodoFilterNotifier todoFilterNotifier = TodoFilterNotifier();
// resolve instances in current scope
final myTodosNotifier = todosNotifier;
final myTodoFilterNotifier = todoFilterNotifier;
} // `}` is the end of scope
copied to clipboard
This simple pseudocode shown:
function scope that starts with {, ends with }
how to create and expose instances in current scope
how to resolve instances in current scope
Usage of name #
Use different names to create multiple instances:
FlutterScope(
configure: [
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
name: 'todosNotifier1',
equal: (_) => TodosNotifier(),
),
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
name: 'todosNotifier2',
equal: (_) => TodosNotifier(),
),
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
name: 'todosNotifier3',
equal: (_) => TodosNotifier(),
),
],
child: Builder(
builder: (context) {
final myTodosNotifier1 = context.scope.get<TodosNotifier>(name: 'todosNotifier1');
final myTodosNotifier2 = context.scope.get<TodosNotifier>(name: 'todosNotifier2');
final myTodosNotifier3 = context.scope.get<TodosNotifier>(name: 'todosNotifier3');
return ...;
},
),
);
copied to clipboard
Which simulates:
void flutterScope() {
final TodosNotifier todosNotifier1 = TodosNotifier();
final TodosNotifier todosNotifier2 = TodosNotifier();
final TodosNotifier todosNotifier3 = TodosNotifier();
final myTodosNotifier1 = todosNotifier1;
final myTodosNotifier2 = todosNotifier2;
final myTodosNotifier3 = todosNotifier3;
}
copied to clipboard
Name can be private, so instance will only be resolved in current library (mostly current file):
final _privateName = Object();
class SomeWidget extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return FlutterScope(
configure: [
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
name: _privateName, // use private name
equal: (_) => TodosNotifier(),
),
],
child: Builder(
builder: (context) {
final myTodosNotifier = context.scope.get<TodosNotifier>(name: _privateName);
return ...;
},
),
);
}
}
copied to clipboard
Name can also be omitted, in this case null is used as name:
FlutterScope(
configure: [
// assigned without name
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
equal: (_) => TodosNotifier(),
),
],
child: Builder(
builder: (context) {
// also resolved without name
final myTodosNotifier = context.scope.get<TodosNotifier>();
return ...;
},
),
);
copied to clipboard
Usage of FlutterScope.async(...) #
Use FlutterScope.async(...) to create a scope with async configurations.
If there is async setup like resolving SharedPreference. We can follow this:
Future<Map<String, Todo>> resolveInitialTodosAsync() {
await Future<void>.delayed(Duration(seconds: 1));
return { ... };
}
...
FlutterScope.async( // use `async` constructor
configure: [
// using `AsyncFinal` to handle async setup
AsyncFinal<Map<String, Todo>>(
equal: (_) async {
return await resolveInitialTodosAsync();
},
),
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
equal: (scope) => TodosNotifier(
scope.get<Map<String, Todo>>(),
),
),
],
builder: (context, asyncScope) {
switch (asyncScope.status) {
case AsyncStatus.loading:
return ...; // loading widget
case AsyncStatus.error:
return ...; // error widget
case AsyncStatus.loaded:
final scope = asyncScope.requireData;
final myTodosNotifier = scope.get<TodosNotifier>();
return ...; // success widget
},
},
);
copied to clipboard
Which simulates:
void flutterScope() async {
final Map<String, Todo> initialTodos = await resolveInitialTodosAsync();
final TodosNotifier todosNotifier = TodosNotifier(initialTodos);
final myTodosNotifier = todosNotifier;
}
copied to clipboard
Usage of child scope #
Use FlutterScope to create a child scope which inherited getters from parent scope:
class AddTodoState { ... }
class AddTodoNotifier extends ValueNotifier<AddTodoState> { ... }
...
FlutterScope(
configure: [
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
equal: (_) => TodosNotifier(),
),
FinalValueNotifier<TodoFilterNotifier, TodoFilter>(
equal: (_) => TodoFilterNotifier(),
),
],
child: FlutterScope( // creating a new scope in subtree of parent scope
configure: [
FinalValueNotifier<AddTodoNotifier, AddTodoState>(
equal: (_) => AddTodoNotifier(),
),
],
child: Builder(
builder: (context) {
final myTodoNotifier = context.scope.get<TodosNotifier>();
final myTodoFilterNotifier = context.scope.get<TodoFilterNotifier>();
final myAddTodoNotifier = context.scope.get<AddTodoNotifier>();
return ...;
},
),
),
);
copied to clipboard
Which simulates:
void flutterScope() {
final TodosNotifier todosNotifier = TodosNotifier();
final TodoFilterNotifier todoFilterNotifier = TodoFilterNotifier();
void childFlutterScope() {
final AddTodoNotifier addTodoNotifier = AddTodoNotifier();
// resolve instances:
// `todosNotifier` is inherited from parent scope
// `todoFilterNotifier` is inherited from parent scope
// `addTodoNotifier` is exposed in current scope
final myTodosNotifier = todosNotifier;
final myTodoFilterNotifier = todoFilterNotifier;
final myAddTodoNotifier = addTodoNotifier;
}
}
copied to clipboard
Usage of InheritedScope #
Use InheritedScope for making an exist scope available to subtree. This is useful when current route share scope with new route:
FlutterScope(
configure: [
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
equal: (_) => TodosNotifier(),
),
FinalValueNotifier<TodoFilterNotifier, TodoFilter>(
equal: (_) => TodoFilterNotifier(),
),
],
child: Builder(
builder: (context) {
return Scaffold(
...
floatActionButton: FloatActionButton(
onPressed: () => _showAddTodoDialog(context),
child: ...,
),
),
},
),
);
...
void _showAddTodoDialog(BuildContext context) {
showDialog( // show dialog will push a new route
context: context,
builder: (_) {
return InheritedScope( // use `InheritedScope` for
scope: context.scope, // making exist scope available to subtree
child: AlertDialog(
...,
content: Builder(
builder: (context) {
// resolve instance in new route
final myTodosNotifier = context.scope.get<TodosNotifier>();
return ...;
},
),
),
);
},
);
}
copied to clipboard
Above example shown:
press FloatActionButton will push a new route
passing scope from current route to new route using InheritedScope
resolve TodosNotifier in new route
Usage of FlutterScope's parentScope parameter #
Use FlutterScope's parentScope parameter to create a new scope which is based on exist one, and has additional configurations.
void _showAddTodoDialog(BuildContext context) {
showDialog(
context: context,
builder: (_) {
- return InheritedScope( // use `InheritedScope` for
- scope: context.scope, // making exist scope available to subtree
+ return FlutterScope(
+ parentScope: context.scope, // passing exist scope
+ configure: [ // with additional configurations
+ FinalValueNotifier<AddTodoNotifier, AddTodoState>(
+ equal: (_) => AddTodoNotifier(),
+ ),
+ ],
child: AlertDialog(
title: ...,
content: Builder(
builder: (context) {
// resolve instance in new route
final myTodosNotifier = context.scope.get<TodosNotifier>();
+ final myAddTodoNotifier = context.scope.get<AddTodoNotifier>();
return ...;
},
),
actions: ...,
),
);
},
);
}
copied to clipboard
Which simulates:
void flutterScope() {
final TodosNotifier todosNotifier = TodosNotifier();
final TodoFilterNotifier todoFilterNotifier = TodoFilterNotifier();
void childFlutterScope() {
final AddTodoNotifier addTodoNotifier = AddTodoNotifier();
final myTodosNotifier = todosNotifier;
final myAddTodoNotifier = addTodoNotifier;
}
}
copied to clipboard
We've covered the dependency injection part of FlutterScope. Now, let's explore Observable/States based notification system.
Usage of States #
States is a sequence of state.
It will replay current state synchronously, then emit following state asynchronously or synchronously.
Example in pure dart:
void flutterScope() async {
final TodoFilterNotifier todosFilterNotifier = TodoFilterNotifier();
final States<TodoFilter> todoFilterStates = todosFilterNotifierAsStates(todosFilterNotifier);
late TodoFilter state;
final observation = todoFilterStates.observe((todoFilter) { // start observe states
print('simulate flutter set state');
state = todoFilter;
print('simulate map state to widget');
});
await Future<void>.delayed(const Duration(seconds: 3));
print('simulate `navigator.pop(...)`');
observation.dispose(); // stop observe
...// dispose `todosFilterNotifier`
}
// a function turns notifier to states
States<TodoFilter> todosFilterNotifierAsStates(TodoFilterNotifier notifier) { ... }
copied to clipboard
Above example shown:
late TodoFilter state is a plain state
final States<TodoFilter> todoFilterStates is a sequence of plain state. Sometimes States can be considered as plain state with a time dimension
use todoFilterStates.observe(...) to start observe states
use observation.dispose() to stop observe states
Note: States is similar to dart Stream, but it is slightly different. States promise replay current state synchronously to observer, while dart Stream has its trade off, is designed not support this feature.
Since States has composition capability, let's introducing two common used operators.
Usage of States.computed
Use States.computed to combine multiple states into one States.
When an item is emitted by one of multiple States, combine the latest item emitted by each States via a specified function and emit combined item that changed.
For example filteredTodos is computed by combining todos and todoFilter:
List<Todo> filterTodos(Map<String, Todo> todos, TodoFilter filter) {
return todos.values
.where((todo) {
switch (filter) {
case TodoFilter.all: return true;
case TodoFilter.completed: return todo.isCompleted;
case TodoFilter.uncompleted: return !todo.isCompleted;
}
})
.toList();
}
...
void flutterScope() async {
...
final States<Map<String, Todo>> todosStates = ...;
final States<TodoFilter> todoFilterStates = ...;
final States<List<Todo>> filteredTodosStates = States.computed2(
states1: todosStates,
states2: todoFilterStates,
compute: filterTodos, // `filterTodos` is a pure function declared at top
);
late List<Todos> state;
final observation = filteredTodosStates.observe((filteredTodos) {
print('simulate flutter set state');
state = filteredTodos;
print('simulate map state to widget');
});
...
}
copied to clipboard
Above example shown:
filterTodos is a pure function which compute plain filteredTodos by combining plain todos and todoFilter
filteredTodosStates is computed by combining todosStates and todoFilterStates
Usage of states.convert
Use states.convert to convert each item by applying a function and only emit result that changed.
For example todosLength is converted from todos:
void flutterScope() {
final TodosNotifier todosNotifier = TodosNotifier();
final States<Map<String, Todo>> todosStates = todosNotifierAsStates(todosNotifier);
// `todosLength` is converted from `todos`
final States<int> todosLengthStates = todosStates
.convert((todos) => todos.length);
final observation = todosLengthStates.observe((todosLength) {
print('todos length changed to $todosLength');
});
...
}
copied to clipboard
We've seen basic usage of States, let's see how to integrate with flutter.
Usage of StatesBuilder(...) #
Use StatesBuilder(...) to map a sequence of state to widget, as UI = f(state).
FlutterScope(
configure: [
FinalValueNotifier<TodoFilterNotifier, TodoFilter>(
equal: (_) => TodoFilterNotifier(),
),
],
child: StatesBuilder<TodoFilter>(
builder: (context, todoFilter, child) {
return ...; // map state to widget
},
),
);
copied to clipboard
Which simulates:
void flutterScope() async {
final TodoFilterNotifier todosFilterNotifier = TodoFilterNotifier();
final States<TodoFilter> todoFilterStates = todosFilterNotifierAsStates(todosFilterNotifier);
late TodoFilter state;
final observation = todoFilterStates.observe((todoFilter) {
print('simulate flutter set state');
state = todoFilter;
print('simulate map state to widget');
});
...
}
...
copied to clipboard
StatesBuilder has composition capability, since it is based on States.
Usage of StatesBuilder with States.computed operator
Use StatesBuilder with States.computed operator to combine multiple states into one states, then map it to widget.
...
FlutterScope(
configure: [
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
equal: (_) => TodosNotifier(),
),
FinalValueNotifier<TodoFilterNotifier, TodoFilter>(
equal: (_) => TodoFilterNotifier(),
),
],
child: Builder(
builder: (context) {
return StatesBuilder<List<Todo>>(
states: States.computed2(
states1: context.scope.getStates<Map<String, Todo>>(),
states2: context.scope.getStates<TodoFilter>(),
compute: filterTodos,
),
builder: (context, filteredTodos, child) {
return ...; // map state to widget
},
);
},
),
);
copied to clipboard
Which simulates:
...
void flutterScope() async {
final TodosNotifier todosNotifier = TodosNotifier();
final TodoFilterNotifier todosFilterNotifier = TodoFilterNotifier();
final States<Map<String, Todo>> todosStates = todosNotifierAsStates(todosNotifier);
final States<TodoFilter> todoFilterStates = todosFilterNotifierAsStates(todosFilterNotifier);
final States<List<Todo>> filteredTodosStates = States.computed2(
states1: todosStates,
states2: todoFilterStates,
compute: filterTodos,
);
late List<Todos> state;
final observation = filteredTodosStates.observe((filteredTodos) {
print('simulate flutter set state');
state = filteredTodos;
print('simulate map state to widget');
});
...
}
copied to clipboard
Usage of StatesListener(...) #
Use StatesListener(...) to add a listener in flutter layer.
FlutterScope(
configure: [
FinalValueNotifier<TodoFilterNotifier, TodoFilter>(
equal: (_) => TodoFilterNotifier(),
),
],
child: StatesListener<TodoFilter>(
onData: (context, todoFilter) {
ScaffoldMessenger.of(context)
.showSnackbar(SnackBar(
content: Text('todo filter changed to $todoFilter'),
));
},
child: ...,
),
);
copied to clipboard
Which simulates:
void flutterScope() async {
final TodoFilterNotifier todosFilterNotifier = TodoFilterNotifier();
final States<TodoFilter> todoFilterStates = todosFilterNotifierAsStates(todosFilterNotifier);
final observation = todoFilterStates.observe((todoFilter) {
print('todo filter changed to $todoFilter');
});
...
}
...
copied to clipboard
StatesListener also has composition capability, since it is based on States.
Usage of StatesListener with states.convert operator
Use StatesListener with states.convert operator to convert states to another states, then add a listener to the states.
FlutterScope(
configure: [
FinalValueNotifier<TodosNotifier, Map<String, Todo>>(
equal: (_) => TodosNotifier(),
),
],
child: Builder(
builder: (context) {
return StatesListener<int>(
states: context.scope.getStates<Map<String, Todo>>()
.convert((todos) => todos.length),
onData: (context, todosLength) {
ScaffoldMessenger.of(context)
.showSnackbar(SnackBar(
content: Text('todos length changed to $todosLength'),
));
},
child: ...,
);
},
),
);
copied to clipboard
Which simulates:
void flutterScope() {
final TodosNotifier todosNotifier = TodosNotifier();
final States<Map<String, Todo>> todosStates = todosNotifierAsStates(todosNotifier);
// `todosLength` is converted from `todos`
final States<int> todosLengthStates = todosStates
.convert((todos) => todos.length);
final observation = todosLengthStates.observe((todosLength) {
print('todos length changed to $todosLength');
});
...
}
...
copied to clipboard
Next Page - dart_scope
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.