getx_distil 1.0.1 copy "getx_distil: ^1.0.1" to clipboard
getx_distil: ^1.0.1 copied to clipboard

A distilled, high-performance micro-state management and scoped DI engine for Flutter.

πŸš€ getx_distil #

A high-performance, ultra-lightweight micro state management and tree-scoped dependency injection (DI) library for Flutter. It extracts, refines, and distills only the most powerful, intuitive core mechanics of GetXβ€”Reactive State (Rx) and Dependency Injectionβ€”while completely shedding the unnecessary legacy architectural overhead.


πŸ›οΈ Philosophy #

As long-time fans and active users of GetX, we deeply admire the unmatched developer experience (DX) it pioneered. The simplicity of .obs, the absolute precision of Obx, and the friction-free dependency lookup completely revolutionized state management in Flutter.

However, as the Flutter ecosystem matured toward declarative routing (like GoRouter) and strict widget-tree-bound lifecycles, the original GetX's heavy global navigation overlays, custom routing engines, and implicit memory management frequently introduced architectural friction, unexpected memory leaks, and edge-case exceptions.

getx_distil is born out of this respect and necessity. We removed the bloat, fixed the long-standing concurrency issues, and hardened memory safety.

Same Developer Experience. Zero Overhead.


πŸ“Š Core Enhancements #

  • 🌳 100% Tree-Scoped DI Lifecycle: Eliminates manual Get.delete() calls. Controllers are strictly bound to the Flutter widget tree using BindingWidget. When a widget unmounts, its controllers are automatically and cleanly garbage-collected (Auto-GC).
  • πŸ›‘οΈ Self-Healing Build-Phase Updates: Modifying reactive state during the widget tree’s build or layout phase normally crashes Flutter with a setState() during build exception. getx_distil automatically intercepts these and safely defers UI updates to the post-frame callback queue.
  • πŸ›‘ Strict Async Obx Validation: Mixing async/await directly inside Obx builders breaks reactive tracking loops. getx_distil catches this anti-pattern instantly and throws a descriptive FlutterError rather than failing silently.
  • 🧡 FIFO Asynchronous Pipeline (updateSequential): Introduces a clean sequential queue to prevent critical race conditions and state inversion during high-frequency async operations.
  • πŸ“‹ Batched Loop Mutations (RxList): Instead of triggering expensive UI rebuilds on every single mutation inside a loop, RxList aggregates changes and schedules a single microtask UI refresh.
  • πŸ” High-Visibility DI Debugging: When Get.find fails, it no longer throws a cryptic message. It prints a comprehensive debug layout showing the requested context name, the exact parent ancestor widget hierarchy path, and active services in memory.
  • ⚑ High-Performance Fast-Path Tracking (Notifier.isTracking): In original GetX, reading any reactive variable (even in normal business logic loops or background tasks outside of Obx widgets) triggers a lookup of the global tracking proxy. getx_distil introduces a lightweight static boolean flag isTracking. Outside of active Obx build frames, this flag is false, bypassing the entire proxy lookup and dependency registration pipeline. This dramatically reduces CPU cycles during heavy calculation loops or traversals.

πŸ› οΈ Essential Components & Quick Start #

1. 🎯 Reactive State Management (Rx & Obx) #

Isolate updates down to the leaf-most widgets with absolute zero boilerplate.

class CounterController extends GetxController {
  final count = 0.obs;             // RxInt
  final name = Rxn<String>();      // Safe Nullable Rx

  void increment() {
    count.value++;                 // Simple Overwrite
    name.value = 'Flutter';
  }
}

In your View layer (pinpoint rebuilds):

Obx(() => Text('${controller.count.value}'));

2. πŸš€ Global/Classic Dependency Injection (Get.put & Get.find) #

Classic GetX singleton dependency injection that registers instances into the global registry instantly or lazily, enabling context-less access from anywhere in your codebase.

Registering instances:

// 1. put: Instantly instantiates and registers a singleton in global memory
final controller = Get.put(CounterController());

// 2. lazyPut: Registers a builder function, instantiating the controller only on its first Get.find call
Get.lazyPut(() => CounterController());

// 3. Register multiple instances of the same type using tags
Get.put(CounterController(), tag: 'special_counter');

Finding instances (context-less anywhere in your code):

// Resolve and retrieve the registered singleton instance
final controller = Get.find<CounterController>();

// Resolve tagged instances
final specialController = Get.find<CounterController>(null, 'special_counter');

Tip

getx_distil features a Hybrid DI system. If you provide a BuildContext like Get.find(context), it will prioritize widget tree-scoped lookup (BindingWidget). If it is not found, it seamlessly falls back to resolving the dependency from the global registry.


3. 🌳 Widget Tree-Scoped Dependency Injection (BindingWidget) #

Synchronize your controller's lifetime directly with your screen's visibility. Perfect for GoRouter or native Navigator.

GoRoute(
  path: '/settings',
  builder: (context, state) => BindingWidget(
    bindings: [
      Bind<SettingsController>(() => SettingsController()),
    ],
    child: const SettingsPage(),
  ),
)

Inside SettingsPage (resolves automatically via BuildContext):

class SettingsPage extends GetView<SettingsController> {
  const SettingsPage({super.key});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(() => Text(controller.someData.value)),
    );
  }
}

4. 🌐 Global Persistent Services (GetxService) #

For infrastructure-level layers that must remain resident as Immortal Singletons (e.g., Databases, Auth Session Managers, Network Clients).

class DatabaseService extends GetxService {
  Future<void> init() async => print('DB Connected');
}

Register at the root of your application:

GetMaterialApp(
  bindings: [Bind<DatabaseService>(() => DatabaseService())],
  child: const MyApp(),
);

Resolve context-less anywhere in your business logic:

final db = Get.find<DatabaseService>();

5. πŸ› οΈ Background Side-Effects (Worker) #

Monitor state variations reactively and execute asynchronous validations, API triggers, or debounces cleanly.

class SearchController extends GetxController {
  final searchQuery = ''.obs;
  late final Worker _worker;

  @override
  void onInit() {
    super.onInit();
    // Triggers API only after 500ms of user typing inactivity
    _worker = debounce(
      searchQuery, 
      (query) => fetchApi(query), 
      time: const Duration(milliseconds: 500),
    );
  }

  @override
  void onClose() {
    _worker.dispose(); // Enforced explicit disposal prevents memory leaks!
    super.onClose();
  }
}

6. πŸ”„ Declarative Async Branching (StateMixin) #

Eradicate convoluted if-else blocks in your build methods for typical API states: Loading, Success, Empty, and Error.

class UserController extends GetxController with StateMixin<String> {
  void fetchUser() async {
    change(null, status: RxStatus.loading());
    try {
      final res = await api.getUser();
      res.isEmpty 
          ? change(null, status: RxStatus.empty()) 
          : change(res, status: RxStatus.success());
    } catch (e) {
      change(null, status: RxStatus.error(e.toString()));
    }
  }
}

Declarative mapping in the View layer:

controller.obx(
  (state) => Text('Welcome, $state'),
  onLoading: const CircularProgressIndicator(),
  onEmpty: const Text('No user data found.'),
  onError: (error) => Text('Error: $error', style: const TextStyle(color: Colors.red)),
);

7. 🌐 Internationalization & Localization (Translations & tr) #

Manage translation dictionaries reactively and switch UI language dynamically on-the-fly based on user preferences or device locale settings.

Define custom translations:

class MyTranslations extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
    'en_US': {
      'hello': 'Hello World',
      'welcome': 'Welcome, @name!',
    },
    'ko_KR': {
      'hello': 'μ•ˆλ…•ν•˜μ„Έμš”',
      'welcome': 'μ•ˆλ…•ν•˜μ„Έμš”, @nameλ‹˜!',
    }
  };
}

Register translations at root GetMaterialApp:

GetMaterialApp(
  translations: MyTranslations(),
  locale: const Locale('en', 'US'),
  fallbackLocale: const Locale('en', 'US'),
  child: const MyApp(),
);

Render localized text reactively in your View layer:

// 1. Simple translation lookup
Obx(() => Text('hello'.tr))

// 2. Parameter-injected translation
Obx(() => Text('welcome'.trParams({'name': 'John Doe'})))

Switch locale dynamically at runtime:

// Change locale to Spanish (or Korean)
Get.locale = const Locale('ko', 'KR');

// Change locale to English
Get.locale = const Locale('en', 'US');

πŸ“„ License #

This project is licensed under the MIT License.

1
likes
0
points
62
downloads

Publisher

unverified uploader

Weekly Downloads

A distilled, high-performance micro-state management and scoped DI engine for Flutter.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on getx_distil