Skip to content

Quick Start

This guide will walk you through creating a simple counter application using Air Framework.

Use the Air CLI to scaffold a new project structure:

Terminal window
air create my_app --template=blank
cd my_app

Create a module by extending AppModule. A module encapsulates your routes and dependencies.

lib/modules/counter/counter_module.dart
import 'package:air_framework/air_framework.dart';
class CounterModule extends AppModule {
@override
String get id => 'counter';
@override
List<AirRoute> get routes => [
AirRoute(
path: '/counter',
builder: (context, state) => const CounterPage(),
),
];
@override
void onBind(AirDI di) {
di.registerLazySingleton<CounterState>(() => CounterState());
}
}

Use the @GenerateState annotation. The framework will generate the reactive plumbing for you.

lib/modules/counter/ui/state/state.dart
import 'package:air_framework/air_framework.dart';
part 'state.air.g.dart';
@GenerateState('counter')
class CounterState extends _CounterState {
// Private fields become reactive StateFlows
int _count = 0;
// Public methods become dispatchable Pulses
@override
void increment() {
count++; // Direct field access is tracked!
}
}

Run build_runner to generate the state.air.g.dart file:

Terminal window
air generate state

Use AirView to listen to state changes efficiently.

class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Air Counter')),
body: Center(
child: AirView((context) {
// Rebuilds ONLY when 'count' changes
return Text('Count: ${CounterFlows.count.value}');
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => CounterPulses.increment.pulse(null),
child: Icon(Icons.add),
),
);
}
}

Register your module in main.dart.

void main() async {
WidgetsFlutterBinding.ensureInitialized();
configureAirState();
await ModuleManager().register(CounterModule());
runApp(MaterialApp.router(
routerConfig: AirRouter().router,
));
}