current 3.0.0-beta-3
current: ^3.0.0-beta-3 copied to clipboard
A simple yet powerful state management library for Flutter. Keeps your widget build methods clean, with ZERO dependencies on other packages.
Flutter Current
A simple, lightweight state management library for Flutter
Features #
- Typed reactive properties for primitives, nullable values, lists, and maps.
- View-model-driven widgets with
CurrentWidgetandCurrentState. - Application-wide shared state with
Current. - Issue-based validation that keeps localization in the widget layer.
- Text input binding with
CurrentTextController,CurrentTextFormField, andCurrentTextFieldfor form and non-form flows. - Built-in busy state, change notifications, and event listeners for async flows.
Getting Started #
In your Flutter project, add the dependency to your pubspec.yaml.
dependencies:
current: ^3.0.0-beta-3
Tip: Consider installing the Current Flutter Snippets extension in Visual Studio Code to make creating Current classes easier.
Quick Start #
A small counter is still the fastest way to see the core pattern: keep state in a CurrentViewModel, list reactive properties in currentProps, and render that view model through a CurrentWidget.
counter_view_model.dart #
import 'package:current/current.dart';
class CounterViewModel extends CurrentViewModel {
final count = CurrentProperty.integer();
void incrementCounter() {
count.value += 1;
}
@override
Iterable<CurrentProperty> get currentProps => [count];
}
counter_page.dart #
import 'package:current/current.dart';
import 'package:flutter/material.dart';
class CounterPage extends CurrentWidget<CounterViewModel> {
const CounterPage({super.key, required super.viewModel});
@override
CurrentState<CounterPage, CounterViewModel> createCurrent() {
return _CounterPageState(viewModel);
}
}
class _CounterPageState extends CurrentState<CounterPage, CounterViewModel> {
_CounterPageState(super.viewModel);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Current Example')),
body: Center(
child: Text('Count: ${viewModel.count}'),
),
floatingActionButton: FloatingActionButton(
onPressed: viewModel.incrementCounter,
child: const Icon(Icons.add),
),
);
}
}
main.dart #
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Current State Example',
home: CounterPage(
viewModel: CounterViewModel(),
),
);
}
}
This keeps business logic out of the widget tree without introducing a second state object. When any property in currentProps changes, the matching CurrentState rebuilds automatically.
Forms And Validation #
Current validation is issue-based. Rules return CurrentValidationIssue, not display strings. That keeps validation logic locale-agnostic and lets widgets resolve the final text using whatever localization system the app already uses.
profile_view_model.dart #
import 'package:current/current.dart';
class ProfileViewModel extends CurrentViewModel {
final displayName = CurrentProperty.string();
final age = CurrentProperty.integer();
// Can define your validation rules in the view model
// or in a separate validation focused file if that keeps things cleaner and/or easier to test.
CurrentFieldValidation<String> displayNameValidation(CurrentStringProperty displayName, AppLocalizations intl) {
return displayName.createValidation(rules: [_displayNameNotEmpty(intl)]);
}
CurrentValidationRule<String> _displayNameNotEmpty(AppLocalizations intl) {
return (value) => value.trim().isEmpty
? CurrentValidationIssue.message(intl.displayNameRequired)
: null;
}
CurrentFieldValidation<int> ageValidation(CurrentIntegerProperty age) {
return age.createValidation(rules: [_userIsAdult()]);
}
CurrentValidationRule<int> _userIsAdult() {
return (value) => value < 18
? const CurrentValidationIssue(
'profile.age.minimum', // error code if localization is based on codes
arguments: {'minimumAge': 18},
fallbackMessage: 'Must be at least 18',
)
: null;
}
@override
Iterable<CurrentProperty> get currentProps => [displayName, age];
}
profile_page.dart #
import 'package:current/current.dart';
import 'package:flutter/material.dart';
class ProfilePage extends CurrentWidget<ProfileViewModel> {
const ProfilePage({super.key, required super.viewModel});
@override
CurrentState<ProfilePage, ProfileViewModel> createCurrent() {
return _ProfilePageState(viewModel);
}
}
class _ProfilePageState extends CurrentState<ProfilePage, ProfileViewModel>
with CurrentTextControllersLifecycleMixin {
_ProfilePageState(super.viewModel);
final _formKey = GlobalKey<FormState>();
final displayNameController = CurrentTextController.string();
final ageController = CurrentTextController.integer();
@override
void bindCurrentControllers() {
// Bind each controller to its property, with an optional validation builder.
// Once the controller is bound and assigned to a CurrentTextFormField,
// value parsing, validation, state updates, and error visibility are automatically handled for you.
displayNameController.bind(
property: viewModel.displayName,
lifecycleProvider: this,
validationBuilder: (property, context) =>
viewModel.displayNameValidation(property, AppLocalizations.of(context)),
);
ageController.bind(
property: viewModel.age,
lifecycleProvider: this,
validationBuilder: (property, _) => viewModel.ageValidation(property),
validationIssues: CurrentTextControllerValidationIssues(
invalidValueIssueBuilder: _invalidAgeIssue, // Optional custom message for invalid values
),
);
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
CurrentTextFormField<String>(
controller: displayNameController,
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(labelText: 'Display name'),
),
CurrentTextFormField<int>(
controller: ageController,
autovalidateMode: AutovalidateMode.onUserInteraction,
validationTextResolver: _resolveIssueText,
decoration: const InputDecoration(labelText: 'Age'),
),
FilledButton(
onPressed: () {
if (_formKey.currentState?.validate() ?? false) {
// Submit the form.
}
},
child: const Text('Save profile'),
),
],
),
);
}
static String? _resolveIssueText(CurrentValidationIssue issue) {
switch (issue.code) {
case 'profile.age.minimum':
return 'Must be at least ${issue.arguments['minimumAge']} years old.';
case 'profile.age.invalid':
return 'Enter a valid whole number.';
default:
return issue.fallbackMessage;
}
}
static CurrentValidationIssue _invalidAgeIssue(String value) {
return const CurrentValidationIssue.invalidValue(
code: 'profile.age.invalid',
fallbackMessage: 'Enter a valid whole number.',
);
}
}
Choosing a field widget #
Current exposes three field-integration paths. Pick the one that matches who owns your widget tree and validation UX.
- Use
CurrentTextFormFieldwhen you are already inside aFormand want the shortest Current-specific wrapper. - Use native
TextFormFieldwithcontroller.formValidator(...)when your app already has its own field wrapper or design-system component and you only want Current to provide binding plus validation bridging. - Use
CurrentTextFieldwhen you are not using FlutterFormwidgets but still want Current-managed error visibility withAutovalidateMode-style behavior.
CurrentTextFormField<String>(
controller: displayNameController,
autovalidateMode: AutovalidateMode.onUserInteraction,
validationTextResolver: _resolveIssueText,
decoration: const InputDecoration(labelText: 'Display name'),
);
TextFormField(
controller: ageController,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: ageController.formValidator(
context: context,
resolver: _resolveIssueText,
),
decoration: const InputDecoration(labelText: 'Age'),
);
CurrentTextField<String>(
controller: displayNameController,
autovalidateMode: AutovalidateMode.onUserInteractionIfError,
validationTextResolver: _resolveIssueText,
decoration: const InputDecoration(labelText: 'Quick search'),
);
Key points:
- Register validation once, either by calling
createValidation()directly or by supplyingvalidationBuilderwhen binding a controller. - Use
CurrentValidationGroup.forProperties([...])when you want grouped validation without separately listing validators. - Use
CurrentTextFormFieldfor the shortestFormintegration, nativeTextFormFieldwithcontroller.formValidator(...)when your widget layer already exists, andCurrentTextFieldwhen you want Current-managed validation without aForm. - Let widgets resolve issue text either through a resolver or through
BuildContextwhen your localization API requires it. - Use
CurrentTextControllerValidationIssuesfor controller-generated parse or required-value failures. - Validation rules can live in the widget, the view model, or in a separate plain-Dart helper file when that keeps a larger form easier to read.
Application Wide State Management #
Use Current when you want a shared CurrentViewModel anywhere below a subtree, or across the whole app.
application_view_model.dart #
import 'package:current/current.dart';
class ApplicationViewModel extends CurrentViewModel {
final userName = CurrentProperty.nullableString();
final signedIn = CurrentProperty.boolean();
void signIn(String name) {
userName.value = name;
signedIn.value = true;
}
@override
Iterable<CurrentProperty> get currentProps => [userName, signedIn];
}
main.dart #
import 'package:current/current.dart';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Current(
ApplicationViewModel(),
child: Builder(
builder: (context) {
final appViewModel = Current.viewModelOf<ApplicationViewModel>(context);
final userName = appViewModel.userName.value;
return Column(
children: [
Text('User: ${userName ?? 'Guest'}'),
TextButton(
onPressed: () => appViewModel.signIn('Taylor'),
child: const Text('Sign In'),
),
],
);
},
),
),
);
}
}
Explore The Example App #
The repository includes a larger showcase app in example/README.md. It demonstrates typed properties, form validation, controller binding, collection properties, busy state, custom events, and reference snippets in a single responsive mission-control UI.
Contributing #
This is an open source project, and contributions are welcome. Please feel free to create a new issue if you encounter any problems, or submit a pull request. For community contribution guidelines, please review the Code of Conduct.
If submitting a pull request, please ensure the following standards are met:
- Code files must be well formatted with
dart format .. - Tests must pass with
flutter test. New test cases to validate your changes are highly recommended. - Implementations must not add unnecessary project dependencies.
- Project must contain zero warnings. Running
flutter analyzemust return zero issues. - Keep docstrings and README guidance up to date when public APIs change.
Additional information #
This package has ZERO third-party package dependencies.
You can find the full API documentation here.
© 2026 Third Version Technology Ltd