june 1.0.1+2
june: ^1.0.1+2 copied to clipboard
June is a lightweight and modern state management library that focuses on providing a pattern very similar to Flutter's native state management.
June #
Full documentation: junelee.fun
June is a lightweight Flutter state management library that stays true to Flutter's native state management pattern — setState, variables, and widgets — without forcing you into a new app architecture.
Why June? #
Most state management libraries solve the right problem but go too far. They introduce new patterns, abstractions, and conventions that take over your entire app. June solves only the problem: sharing and updating state across widgets, with minimal ceremony.
| Feature | June |
|---|---|
No wrapping MaterialApp |
✅ |
| No manual initialization | ✅ |
Works with StatelessWidget |
✅ |
Call setState from anywhere |
✅ |
| Multiple instances with tags | ✅ |
| Per-widget local state | ✅ |
Installation #
Add June to your pubspec.yaml:
dependencies:
june: ^1.0.1
Then run:
flutter pub get
Quick Start #
1. Declare your state #
class CounterVM extends JuneState {
int count = 0;
}
2. Wrap your widget with JuneBuilder #
JuneBuilder(
() => CounterVM(),
builder: (vm) => Text('${vm.count}'),
)
3. Update state from anywhere #
var vm = June.getState(() => CounterVM());
vm.count++;
vm.setState();
That's it. No providers, no streams, no boilerplate.
Full Example #
import 'package:flutter/material.dart';
import 'package:june/june.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: JuneBuilder(
() => CounterVM(),
builder: (vm) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'${vm.count}',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => June.getState(() => CounterVM())
..count++
..setState(),
child: const Icon(Icons.add),
),
),
);
}
}
class CounterVM extends JuneState {
int count = 0;
}
Actions Inside the State #
You can define methods directly on your state class to keep logic close to data:
class CounterVM extends JuneState {
int count = 0;
void increment() {
count++;
setState();
}
}
Call it from anywhere:
June.getState(() => CounterVM()).increment();
Advanced #
Multiple Instances with Tags #
Use tag to maintain separate instances of the same state type — useful for list items, feeds, or any repeating structure.
// Default instance
JuneBuilder(
() => CounterVM(),
builder: (vm) => Text('Default: ${vm.count}'),
)
// Tagged instance
JuneBuilder(
() => CounterVM(),
tag: 'cardA',
builder: (vm) => Text('Card A: ${vm.count}'),
)
Update a specific tagged instance from anywhere:
June.getState(() => CounterVM(), tag: 'cardA')
..count++
..setState();
Per-Widget Local State (global: false) #
For state tied to a single widget instance — not shared globally — use global: false. This is the correct pattern when a state object is created outside June and passed into a widget.
class MyState extends JuneState {
int value = 0;
void increment() {
value++;
setState();
}
}
final myState = MyState();
JuneBuilder(
() => myState,
global: false,
builder: (state) => Text('${state.value}'),
)
With global: false, June uses the provided instance directly without touching the global registry, so each widget holds its own isolated state. This avoids listener mismatches when the same state type is used across multiple screens or navigation pushes.
Note: Do not pass the instance itself as the
idparameter. Useglobal: falsewith plainsetState()instead. See the id vs tag section below.
Theme Mode Toggle #
JuneBuilder can wrap MaterialApp to rebuild the entire app when state changes:
class MyController extends JuneState {
bool isDark = false;
void toggleTheme() {
isDark = !isDark;
setState();
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return JuneBuilder(
() => MyController(),
builder: (c) => MaterialApp(
theme: ThemeData(),
darkTheme: ThemeData(brightness: Brightness.dark),
themeMode: c.isDark ? ThemeMode.dark : ThemeMode.light,
home: const HomeUi(),
),
);
}
}
Async State / HTTP #
June works naturally with async operations. Call setState() once data is ready:
class ApiController extends JuneState {
String? result;
Future<void> fetchData() async {
final res = await http.get(Uri.parse('https://api.example.com/data'));
result = res.body;
setState();
}
}
JuneBuilder(
() => ApiController(),
builder: (c) => c.result == null
? const CircularProgressIndicator()
: Text(c.result!),
)
Key Concepts #
| Concept | Description |
|---|---|
JuneState |
Base class for all state objects. Call setState() to trigger rebuilds. |
JuneBuilder |
Widget that subscribes to a state and rebuilds when setState() is called. |
June.getState() |
Retrieves the global singleton for a state type, creating it if needed. |
tag |
Differentiates multiple registered instances of the same type in the global registry. |
global: false |
Bypasses the global registry; the builder uses the provided instance directly. |
id vs tag #
These two parameters serve different purposes:
| Parameter | Purpose | When to use |
|---|---|---|
tag |
Selects which registered instance to use from the global registry | Multiple independent instances of the same state type across the app |
id |
Selectively triggers only JuneBuilder widgets that share the same id, when calling setState([id]) on a single shared state |
Partial rebuilds within a single shared state |
Migration #
0.8.x → 1.0.0 #
// Before
June.getState(CounterVM());
// After
June.getState(() => CounterVM());
Community #
Acknowledgements #
June is inspired by Flutter's native state management, Provider, GetX, Bloc, and Svelte. We are grateful to all contributors and the communities behind these projects for the insights that shaped June.