fuex 0.2.0
fuex: ^0.2.0 copied to clipboard
A Flutter meta-framework inspired by Next.js routing, React hooks, and GetX DI. Features folder-based routing, granular reactive state, context-less navigation, useQuery/useMutation, Super Schema vali [...]
Fuex 🚀 #
Flutter Framework — Yet simple Next.js in Flutter.
Fuex eliminates Flutter boilerplate by combining:
- 📁 Folder-based routing — Navigator 2.0 with auto-generated routes
- ⚡ Obx + Rx — GetX-style auto-tracking reactivity, zero
StatefulWidget - 🌐 Context-less navigation —
Fuex.push('/path') - 💉 Smart DI — Auto garbage collection on route pop
- 🌍 Built-in Network — Dio-powered HTTP client out of the box
- 🔄 React-style hooks —
useQuery,useMutation,useState,useForm - 📱 Adaptive UI — Built-in responsive sizing (
.w,.h,.sp) +AdaptiveLayout - 🔧 Powerful CLI — Scaffold pages, services, repos, CI/CD in one command
- 🌿 Flavor/Env — Built-in
.env.dev/.env.productionmanagement
Installation #
dependencies:
fuex: ^0.1.0
import 'package:fuex/fuex.dart';
Install Fuex CLI #
Global install (from this repo):
dart pub global activate -s path fuex_cli
Verify:
fuex --help
If fuex is not found, add Dart pub-cache binaries to your PATH:
export PATH="$PATH:$HOME/.pub-cache/bin"
Generate new project:
[1] flutter create .
[2] fuex init --force
Quick Start #
void main() {
// 1. Init global services
Fuex.put(FuexNetwork(baseUrl: 'https://api.example.com'), permanent: true);
// 2. Run app
runApp(
FuexApp(
initialPath: '/',
routes: [
FuexRouteEntry(path: '/', builder: (_) => const HomePage(), binding: HomeBinding()),
FuexRouteEntry(path: '/detail', builder: (_) => const DetailPage()),
FuexRouteEntry(path: '/users/:id', builder: (_) => const UserPage()),
],
),
);
}
Core API #
🧭 Context-Less Navigation #
Fuex.push('/dashboard'); // push
Fuex.push('/detail', extra: product); // with object
Fuex.replace('/home'); // replace current
Fuex.offAll('/login'); // clear stack
Fuex.pop(); // go back
final id = Fuex.params['id']; // /users/:id
final sort = Fuex.queryParams['sort']; // ?sort=asc
final obj = Fuex.extra as Product; // extra object
👁️ Obx — Auto-Tracking Widget (GetX Style) #
No builders, no subscriptions. Just wrap with Obx() and read .value:
final count = 0.obs;
final users = <String>[].obs;
// Auto-rebuilds when count.value or users change
Obx(() => Text('Count: ${count.value}, Users: ${users.length}'))
⚡ Rx — Reactive Primitives #
final name = Rx<String>(''); // or ''.obs
final items = RxList<String>(); // reactive list
final map = RxMap<String, int>(); // reactive map
// Manual builder (alternative to Obx)
FuexBuilder<String>(rx: name, builder: (ctx, val) => Text(val))
// Multiple reactives
FuexMultiBuilder(listenables: [name, age], builder: (ctx) => Text('${name.value}'))
⚡ useState — Local Reactivity #
useState<int>(
initial: 0,
builder: (context, count) => Column(children: [
Text('${count.value}'),
ElevatedButton(onPressed: () => count.value++, child: const Text('+')),
]),
)
📦 useStorage — Persistent State #
useStorage<int>(
storageKey: 'visit_count',
initial: 0,
builder: (context, visits) => Text('Visits: ${visits.value}'),
)
🌍 FuexNetwork — Built-in HTTP Client #
Pre-configured Dio wrapper with logging, timeout, and interceptor support:
// Register globally (once in main.dart or GlobalBindings)
Fuex.put(FuexNetwork(baseUrl: 'https://api.example.com'), permanent: true);
// Use anywhere
final net = Fuex.find<FuexNetwork>();
final res = await net.get('/users');
await net.post('/login', data: {'email': '...'});
await net.put('/users/1', data: {...});
await net.delete('/users/1');
🔄 useQuery — Async Data Fetching #
useQuery<List<Article>>(
queryKey: 'articles',
fetcher: () => repo.fetchArticles(),
builder: (context, state, refetch) {
if (state.isLoading) return const CircularProgressIndicator();
if (state.hasError) return Text('Error: ${state.error}');
return ListView(children: state.data!.map((a) => Text(a.title)).toList());
},
)
// Cache management
invalidateQuery('articles'); // clear one
clearQueryCache(); // clear all
🔀 useMutation — POST / PUT / DELETE #
useMutation<void, LoginRequest>(
mutationFn: (req) => authService.login(req),
builder: (context, mutation) => ElevatedButton(
onPressed: mutation.isLoading ? null : () => mutation.mutate(loginReq,
onSuccess: (_) => Fuex.replace('/dashboard'),
),
child: mutation.isLoading ? const CircularProgressIndicator() : const Text('Login'),
),
)
📋 useForm + FuexSchema — Form Validation #
class LoginRequest extends FuexSchema {
final String email, password;
const LoginRequest({required this.email, required this.password});
@override
ValidationResult validate() {
final errors = <String, String>{};
FieldValidator('email', email).required().isEmail().validate()
?.let((e) => errors['email'] = e);
FieldValidator('password', password).required().minLength(8).validate()
?.let((e) => errors['password'] = e);
return errors.isEmpty ? ValidationResult.valid() : ValidationResult.invalid(errors);
}
@override
Map<String, dynamic> toJson() => {'email': email, 'password': password};
}
| Rule | Usage |
|---|---|
required() |
Must not be null/empty |
isEmail() |
Valid email format |
minLength(n) / maxLength(n) |
String length |
isNumeric() |
Must parse as number |
matches(regex) |
Must match RegExp |
min(n) / max(n) |
Numeric bounds |
💉 Dependency Injection + Smart GC #
// Register (auto-disposed when route pops)
Fuex.put(MyController());
// Register permanently (survives navigation)
Fuex.put(AuthService(), permanent: true);
// Retrieve
final ctrl = Fuex.find<MyController>();
final safe = Fuex.tryFind<MyController>(); // null-safe
// Manual cleanup
Fuex.delete<MyController>(); // delete one
Fuex.clearAll(); // wipe everything (e.g. on logout)
// Auto-dispose interface
class MyController implements FuexDisposable {
@override void onDispose() { /* cleanup */ }
}
🔗 Bindings — Per-Route DI #
class HomeBinding implements FuexBinding {
@override
void dependencies() {
Fuex.put(HomeController());
Fuex.put(NewsRepository());
}
}
// Auto-injected when route opens, auto-disposed when route pops
FuexRouteEntry(path: '/', builder: (_) => const HomePage(), binding: HomeBinding())
🛡️ Route Guards #
class AuthMiddleware extends FuexAuthGuard {
const AuthMiddleware();
@override
bool get isAuthenticated => Fuex.isRegistered<String>(tag: 'token');
@override
String get loginPath => '/login';
}
📱 Adaptive & Responsive Design #
Built-in screen scaling — no ScreenUtil needed:
Container(width: 100.w, height: 50.h) // adaptive sizing
Text('Hi', style: TextStyle(fontSize: 16.sp)) // scaled font
EdgeInsets.all(5.sw) // 5% of screen width
// Device-aware layout
AdaptiveLayout(
mobile: MobileView(), // default
tablet: TabletView(), // ≥ 600px
desktop: DesktopView(), // ≥ 1024px
tv: TvView(), // ≥ 1440px
)
🌿 Environment / Flavor #
await FuexEnv.init(flavor: Flavor.dev); // or Flavor.production
final apiUrl = FuexEnv.get('API_URL');
final isDebug = FuexEnv.isDev; // true / false
🏛️ FuexLayout — Persistent Shells #
FuexRouteEntry(
path: '/dashboard',
builder: (_) => const DashboardPage(),
layout: FuexLayout.wrap(bottomNavigationBar: const MyBottomNav()),
)
CLI Tool (fuex_cli) #
Scaffold Commands #
# Scaffold a page (page.dart + controller.dart + binding.dart)
fuex page dashboard
# Scaffold with multi-device views (mobile + tablet + optional desktop/tv)
fuex view dashboard --desktop --tv
# Scaffold a repository (pre-wired to FuexNetwork)
fuex repo user
# Scaffold a global service
fuex service auth
# Scaffold a reusable widget
fuex widget primary_button
# Scaffold a JSON schema blueprint
fuex schema product
# Scaffold environment files (.env.dev, .env.production, .env.example)
fuex env
# Scaffold CI/CD (GitHub Actions, GitLab CI, Fastlane, Google Drive upload)
fuex ci
Code Generation #
# Auto-generate routes & models from folder structure
fuex generate
# Watch for changes and auto-generate
fuex watch
# Full dev server with flavor support
fuex dev --flavor dev --device chrome
# Open DevTools & Network Monitor in browser
fuex dev --devtools
fuex watch --devtools
Recommended Folder Structure #
lib/
├── app/ # Route pages (folder-based)
│ ├── dashboard/
│ │ ├── page.dart # → /dashboard
│ │ ├── controller.dart # Business logic
│ │ ├── binding.dart # DI registration
│ │ ├── mobile_view.dart # Phone layout
│ │ └── tablet_view.dart # Tablet layout
│ ├── users/[id]/
│ │ └── page.dart # → /users/:id
│ └── (auth)/
│ └── login/page.dart # → /login (route group)
├── core/
│ ├── services/ # Global services
│ └── widgets/ # Reusable components
├── data/
│ ├── repositories/ # Data access (uses FuexNetwork)
│ └── schemas/ # JSON schema → Dart model
├── main.dart
├── .env.dev
└── .env.production
Example App #
See example/ for a complete Space News Reader app demonstrating:
FuexNetwork→ fetch from SpaceFlightNews APIFuexBinding→ auto-inject controller per routeObx()→ auto-tracking bookmark badge & toggleuseQuery→ data fetching with loading/error/cacheuseState→ local counteruseStorage→ persistent notes per articleAdaptiveLayout→ list on phone, grid on tablet/desktop.sp/.w→ responsive sizingFuex.push/pop→ context-less navigation
Roadmap #
- ✅ Core:
Rx<T>,RxList,RxMap,FuexStore - ✅ Hooks:
useState,useStorage,useQuery,useMutation,useForm,useInfiniteQuery - ✅ Router: Navigator 2.0, path/query params, route groups, catch-all
- ✅ Navigation: context-less
Fuex.push/replace/offAll/pop - ✅ DI:
Fuex.put/find/delete/clearAll+ auto GC + bindings - ✅ Schema:
FuexSchema,FieldValidator,ValidationResult - ✅ Widgets:
FuexApp,Obx,FuexBuilder,FuexMultiBuilder,FuexLayout - ✅ Network: Built-in
FuexNetwork(Dio) - ✅ Responsive:
AdaptiveLayout,.w,.h,.sp,.sw,.sh - ✅ Env:
FuexEnvflavor management - ✅ CLI:
fuex page/view/repo/service/widget/schema/env/ci/generate/watch/dev - ❌ Internationalization (i18n) support
- ❌ Built-in analytics integration
License #
MIT © Fuex Contributors