creator 0.3.3
SDKflutter
Platformandroidioswindowslinuxmacosweb
A state management library that enables concise, fluid, readable and testable business logic code.
Starter
An async counter app with login! A minimal template to start a Flutter project with:
- Go router for routing
- Creator for state management.
- Optional Firebase Auth, or your own auth mechanism.
There is zero unnecessary logic, so you can copy and good to go.

Getting Started
- Create new project using
flutter createto leverage new Flutter features:
flutter create my_fancy_app
- Add dependencies:
cd my_fancy_app
flutter pub add go_router
flutter pub add creator
-
Delete the default counter app file
lib/main.dartandtest/widget_test.dart. -
Copy all files from this
libfolder to your app'slibfolder. -
You are good to go. Start making your fancy app!
flutter run
- [Optional] To use Firebase Auth, follow comments in
lib/logic/auth_logic.dart.
Folder structure
It set up the structure to split your code into:
- Repo layer: data models and API calls.
- Logic layer: controllers or view models.
- View layer: all the widgets.
What does the template do?
Just browse the source code. But here is the gist:
- Defines a few basic urls.
/// Allowed urls. Enum is safer than strings.
enum Url {
home('/'),
login('/login'),
setting('/setting'),
splash('/splash'),
;
final String url;
const Url(this.url);
}
- Manages auth state using creator.
/// Get user id of the current login user.
final userCreator = Emitter<String?>((ref, emit) async {
await Future.delayed(const Duration(milliseconds: 100));
// Depends on how you store user's session, fetch the session data.
// Here let's assume there is no login user.
emit(null);
}, name: 'user', keepAlive: true);
/// Login a user using email and password. Change to whatever login method you
/// use.
void login(Ref ref, String email, String password) async {
await Future.delayed(const Duration(milliseconds: 100));
ref.emit(userCreator, 'user_$email');
}
- Builds a basic router that uses auth state for redirection.
/// GoRouter creator. This is what being used by main.dart. It is also handy
/// since as long as you have access to "ref", you can do
/// ref.read(goRouter).go('some url').
final goRouter = Creator((ref) {
return GoRouter(
initialLocation: Url.splash.url,
urlPathStrategy: UrlPathStrategy.path, // Hide '#' from url
routes: <GoRoute>[
GoRoute(path: Url.home.url, builder: (_, __) => const HomeScreen()),
GoRoute(path: Url.login.url, builder: (_, __) => const LoginScreen()),
GoRoute(path: Url.splash.url, builder: (_, __) => const SplashScreen()),
],
redirect: (state) {
/// Still checking whether there is a login user.
if (ref.read(userCreator.asyncData).status == AsyncDataStatus.waiting) {
return state.location != Url.splash.url ? Url.splash.url : null;
}
// There is no login user, let's redirect to login page.
//
// Note here we read instead of watch, so we don't rebuild this creator
// when login user changes. We rely on refreshListenable to do the work.
if (ref.read(userCreator.asyncData).data == null) {
return state.location != Url.login.url ? Url.login.url : null;
}
return state.location == Url.login.url ? Url.home.url : null;
},
// go_counter will re-route whenever this notifier fires.
refreshListenable: RouterNotifier(ref),
);
}, name: 'goRouter', keepAlive: true);
- Provides minimum sample for a async counter app (data model, API calls and async rendering).
// counter_model.dart
/// An example data model
class Counter {
const Counter(this.count);
final int count;
}
// counter_api.dart
/// Fetch the user's counter from server. Fake the logic for now.
Future<Counter> fetchCounter(String userId) async {
await Future.delayed(const Duration(milliseconds: 500));
return const Counter(42);
}
// counter_logic.dart
/// Provide the counter data to view layer.
final counterCreator = Emitter<Counter>((ref, emit) async {
final userId = await ref.watch(userCreator.where((u) => u != null));
emit(await fetchCounter(userId!));
}, name: 'counter');
// counter_view.dart
class CounterView extends StatelessWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context) {
return Watcher((context, ref, _) {
final counter = ref.watch(counterCreator.asyncData).data;
if (counter == null) {
return const CircularProgressIndicator();
}
return Text('Counter: ${counter.count}');
});
}
}