getx_distil 1.0.0 copy "getx_distil: ^1.0.0" to clipboard
getx_distil: ^1.0.0 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.

πŸ› οΈ 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