get_ex
A complete, self-contained state management, dependency injection, and navigation solution for Flutter — inspired by the GetX pattern with zero external dependencies.
Features
| Category | Highlights |
|---|---|
| Reactive State | Rx<T>, RxInt, RxDouble, RxString, RxBool, RxList, RxMap, RxSet with .obs |
| Controllers | GetXController with onInit / onReady / onClose lifecycle |
| StateMixin | RxStatus-based async state (loading, success, error, empty) |
| Dependency Injection | put · lazyPut · putAsync · create · find · delete |
| Bindings | GetXBindings / GetXBindingsBuilder for route-scoped DI |
| Reactive Builders | GetXBuilder auto-tracking widget, obx() helper |
| Workers | ever · once · interval · debounce |
| Navigation | to · off · offAll · toNamed · offNamed · offAllNamed with middleware |
| Transitions | 11 built-in types (fade, slide, zoom, size, cupertino, material, …) |
| Snackbar / Dialog / Overlay | Overlay-based snackbar, dialogs, bottom sheets, loading indicator |
| i18n | GetXTranslations with .tr / .trParams() extensions |
| Theming | Reactive GetXTheme with dark mode detection |
| Platform | GetXPlatform detection for Android, iOS, Web, macOS, Windows, Linux |
| Logger | Configurable GetXLog with custom writers |
| Extensions | String, num, Duration, BuildContext, Widget utilities |
Getting Started
Installation
Add get_ex to your pubspec.yaml:
dependencies:
get_ex: ^1.0.0
Then run:
flutter pub get
Import
import 'package:get_ex/get_ex.dart';
Quick Start
Replace MaterialApp with GetXApp and you're ready:
void main() => runApp(
GetXApp(
title: 'My App',
home: HomePage(),
initialBinding: HomeBinding(),
),
);
Usage
Reactive State Management
Create reactive variables with .obs and rebuild the UI automatically with GetXBuilder:
// Declare reactive state
final count = 0.obs;
final name = 'Flutter'.obs;
final items = <String>[].obs;
// Update state — UI rebuilds automatically
count.value++;
name.value = 'Dart';
items.add('New item');
// In your widget tree
GetXBuilder(
builder: () => Text('Count: ${count.value}'),
)
Controllers
Extend GetXController for organized state with lifecycle hooks:
class CounterController extends GetXController {
final count = 0.obs;
@override
void onInit() {
super.onInit();
ever(count, (val) => GetXLog.info('Count changed to $val'));
}
void increment() => count.value++;
@override
void onClose() {
super.onClose();
// Clean up resources
}
}
StateMixin for Async Operations
class UserController extends GetXController with StateMixin<User> {
@override
void onInit() {
super.onInit();
fetchUser();
}
Future<void> fetchUser() async {
change(null, status: RxStatus.loading());
try {
final user = await api.getUser();
change(user, status: RxStatus.success());
} catch (e) {
change(null, status: RxStatus.error(e.toString()));
}
}
}
// In the UI
controller.obx(
(user) => Text(user.name),
onLoading: CircularProgressIndicator(),
onError: (error) => Text('Error: $error'),
onEmpty: Text('No data'),
)
Dependency Injection
// Register
GetX.put(CounterController());
GetX.lazyPut(() => ApiService());
await GetX.putAsync(() async => await Database.init());
GetX.create(() => Logger()); // new instance every find()
// Resolve
final controller = GetX.find<CounterController>();
// Remove
GetX.delete<CounterController>();
Bindings
Group dependencies per route:
class HomeBinding extends GetXBindings {
@override
void dependencies() {
GetX.lazyPut(() => HomeController());
GetX.lazyPut(() => HomeRepository());
}
}
Or inline:
GetXBindingsBuilder(() {
GetX.lazyPut(() => HomeController());
});
Navigation
// Push
GetX.to(SecondPage());
// Push and remove current
GetX.off(SecondPage());
// Push and remove all
GetX.offAll(LoginPage());
// Go back
GetX.back();
// Named routes
GetX.toNamed('/details');
GetX.offNamed('/home');
GetX.offAllNamed('/login');
Define named routes in GetXApp:
GetXApp(
initialRoute: '/',
getPages: [
GetXPage(name: '/', page: () => HomePage(), binding: HomeBinding()),
GetXPage(name: '/details', page: () => DetailsPage()),
GetXPage(
name: '/profile',
page: () => ProfilePage(),
middlewares: [AuthMiddleware()],
transition: GetXTransition.fade,
),
],
)
Middleware
class AuthMiddleware extends GetXMiddleware {
@override
int get priority => 1;
@override
RouteSettings? redirect(String? route) {
final isLoggedIn = GetX.find<AuthService>().isLoggedIn;
return isLoggedIn ? null : const RouteSettings(name: '/login');
}
}
Transitions
Apply transitions per-route or per-navigation call:
GetX.to(
NextPage(),
transition: GetXTransition.rightToLeft,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
Available transitions: fade, rightToLeft, leftToRight, upToDown, downToUp, zoom, size, cupertino, material, native, noTransition.
Workers
React to reactive variable changes:
// Called every time value changes
ever(count, (val) => print('count is $val'));
// Called only on first change
once(count, (val) => print('first change: $val'));
// Called at most once per second
interval(count, (val) => print('interval: $val'), time: 1.seconds);
// Called after 800ms of no changes
debounce(count, (val) => print('debounce: $val'), time: 800.milliseconds);
Snackbar
GetXSnackbarHelper.show(
context: context,
snackbar: GetXSnackbar(
title: 'Success',
message: 'Item saved!',
duration: Duration(seconds: 3),
snackPosition: GetXSnackPosition.top,
),
);
Dialog
// Default dialog
GetXDialog.defaultDialog(
context: context,
title: 'Confirm',
middleText: 'Delete this item?',
onConfirm: () => deleteItem(),
);
// Bottom sheet
GetXDialog.bottomSheet(
context: context,
builder: (_) => MyBottomSheetContent(),
);
Loading Overlay
GetXOverlay.show(context: context);
// ... perform work
GetXOverlay.dismiss();
Internationalization
Define translations:
class AppTranslations extends GetXTranslations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'greeting': 'Hello @name!',
'title': 'My App',
},
'es_ES': {
'greeting': '¡Hola @name!',
'title': 'Mi App',
},
};
}
Use in widgets:
Text('greeting'.trParams({'name': 'Flutter'}))
Text('title'.tr)
Switch locale at runtime:
GetXTranslationService.updateLocale(const Locale('es', 'ES'));
Theming
// Change theme
GetXTheme.changeTheme(ThemeData.dark());
// Toggle theme mode
GetXTheme.changeThemeMode(ThemeMode.dark);
// Check dark mode
if (GetXTheme.isDarkMode) { ... }
Platform Detection
if (GetXPlatform.isWeb) { ... }
if (GetXPlatform.isAndroid) { ... }
if (GetXPlatform.isDesktop) { ... }
if (GetXPlatform.isMobile) { ... }
Extensions
// String
'hello world'.capitalize; // 'Hello world'
'hello world'.capitalizeFirst; // 'Hello World'
'test@email.com'.isEmail; // true
// Num → Duration
2.seconds;
500.milliseconds;
await 1.5.seconds.delay(() => print('Done'));
// Context
context.width;
context.height;
context.isPhone;
context.isTablet;
// Widget
myWidget.paddingAll(16);
myWidget.marginSymmetric(horizontal: 8);
myWidget.onTap(() => doSomething());
Logging
GetXLog.info('User loaded');
GetXLog.warning('Cache miss');
GetXLog.error('Failed to connect');
// Configure
GetXLog.config(
enableLog: true,
minLevel: GetXLogLevel.warning,
logWriterCallback: (message, level) => myLogger.log(message),
);
Full Example
See the example directory for a complete app demonstrating:
- Counter with reactive
Rxstate - Todo list with
GetXController+RxList - Named routing with middleware
- Dependency injection via bindings
- Snackbar and dialog
- Internationalization with locale switching
- Theme switching
Run it:
cd example
flutter pub get
flutter run
Architecture
lib/
├── get_ex.dart # Barrel export
└── src/
├── get_x_main.dart # GetXApp + GetX facade
├── di/
│ ├── get_x_instance.dart # DI container
│ └── get_x_binding.dart # Bindings
├── state/
│ ├── get_x_controller.dart # Controller base
│ ├── get_x_state.dart # StateMixin / RxStatus
│ └── rx_types/
│ ├── rx_core.dart # Rx<T> and typed variants
│ └── rx_notifier.dart # ValueNotifier wrapper
├── rx/
│ ├── get_x_obs.dart # GetXBuilder / obx
│ └── get_x_workers.dart # ever / once / interval / debounce
├── navigation/
│ ├── get_x_navigation.dart # Navigation API
│ ├── get_x_route.dart # GetXPage model
│ ├── get_x_router_delegate.dart # Router delegate
│ ├── get_x_middleware.dart # Middleware
│ └── transitions/
│ └── get_x_transition.dart # Transition animations
├── ui/
│ ├── snackbar/get_x_snackbar.dart
│ ├── dialog/get_x_dialog.dart
│ └── overlay/get_x_overlay.dart
├── i18n/
│ ├── get_x_translations.dart # Translation service
│ └── get_x_locale.dart # Locale utilities
├── theme/
│ └── get_x_theme.dart # Reactive theming
└── utils/
├── get_x_extensions.dart # Convenience extensions
├── get_x_platform.dart # Platform detection
└── get_x_logger.dart # Logger
Requirements
- Dart SDK:
>=3.0.0 <4.0.0 - Flutter:
>=3.10.0 - Dependencies: None (only Flutter SDK)
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure your code passes flutter analyze with no issues before submitting.
License
This project is licensed under the MIT License — see the LICENSE file for details.
Libraries
- get_ex
- A complete state management, dependency injection, and navigation solution for Flutter.