flutter
/

GetX Bindings: Complete Guide to Dependency Injection for Routes

Last Sync: Today

On this page

13
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Bindings: Complete Guide to Dependency Injection for Routes

What are GetX Bindings?

Bindings in GetX are classes that handle dependency injection for specific routes. They define which controllers and services should be created when a route is opened, and they automatically clean them up when the route is closed. This promotes better code organization, lazy loading, and prevents memory leaks.

Why Use Bindings?

  • Separation of Concerns – Keep dependency registration separate from UI code.
  • Automatic Lifecycle – Controllers are disposed when the route is popped (unless marked permanent).
  • Lazy Loading – Dependencies are created only when the route is actually visited.
  • Testability – Easily mock dependencies for unit tests.
  • Cleaner Code – No scattered Get.put calls inside widgets.

Creating a Basic Binding

A binding is a class that implements the Bindings interface and overrides the dependencies() method.

DARTRead-only
1
class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<HomeController>(() => HomeController());
    // Register any other dependencies needed for this route
    Get.lazyPut<ApiService>(() => ApiService());
  }
}

Using Bindings with GetPage

Bindings are typically used with named routes in GetMaterialApp.

DARTRead-only
1
GetMaterialApp(
  initialRoute: '/',
  getPages: [
    GetPage(
      name: '/',
      page: () => HomePage(),
      binding: HomeBinding(),
    ),
    GetPage(
      name: '/details',
      page: () => DetailsPage(),
      binding: DetailsBinding(),
    ),
  ],
)

Initial Binding (App-Level)

You can also set an initial binding that runs when the app starts. This is useful for global dependencies like authentication services, theme managers, or local storage.

DARTRead-only
1
class AppBindings extends Bindings {
  @override
  void dependencies() {
    Get.put(AuthService());
    Get.lazyPut<StorageService>(() => StorageService());
  }
}

// In main.dart
GetMaterialApp(
  initialBinding: AppBindings(),
  // ...
)

Binding with Multiple Controllers

A single binding can register multiple controllers and services.

DARTRead-only
1
class ProfileBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<ProfileController>(() => ProfileController());
    Get.lazyPut<UserService>(() => UserService());
    Get.lazyPut<ImagePickerService>(() => ImagePickerService());
  }
}

Binding Lifecycle

When a route with a binding is opened:

  1. The binding's dependencies() method is called.
  2. Controllers are registered (but not instantiated if using lazyPut).
  3. The page widget is built.
  4. When a controller is accessed (e.g., via Get.find() or GetView), it is instantiated.
  5. When the route is popped, GetX checks if the controllers are marked permanent; if not, they are disposed (and onClose() is called).

Binding with Parameters

You can pass parameters to bindings using GetPage with binding that takes arguments.

DARTRead-only
1
// Define a binding that accepts parameters
class UserBinding extends Bindings {
  final String userId;
  UserBinding(this.userId);

  @override
  void dependencies() {
    Get.lazyPut<UserController>(() => UserController(userId));
  }
}

// In navigation
Get.toNamed('/user', arguments: '123');
// Or with GetPage
GetPage(
  name: '/user/:id',
  page: () => UserPage(),
  binding: (context) {
    final id = context.params['id'];
    return UserBinding(id!);
  },
);

Nested Bindings & Dependency Injection

For complex apps, you can nest bindings or inject dependencies that themselves depend on others.

DARTRead-only
1
class NestedBinding extends Bindings {
  @override
  void dependencies() {
    // This service may be used by multiple controllers
    Get.lazyPut<ApiService>(() => ApiService());
    // Controller depends on ApiService
    Get.lazyPut<HomeController>(() => HomeController(Get.find()));
  }
}

Bindings vs Direct Get.put

AspectBindingsDirect Get.put
Place of registrationSeparate class, defined per routeInside widgets or main
Lazy loadingYes (default with lazyPut)Controlled by developer
LifecycleAuto-dispose on route popManual or permanent
TestabilityEasy to mock via bindingRequires resetting dependencies
Code organizationCentralized per featureScattered across files

Bindings are recommended for large apps with many routes, as they provide a clear separation and automatic cleanup. For small apps, Get.put directly in main may suffice.

Best Practices

  • One binding per route – Keep dependencies grouped by feature or screen.
  • Use lazyPut by default – Improves startup performance; instantiate only when needed.
  • Inject dependencies via constructors – Makes testing easier and clarifies dependencies.
  • Avoid heavy work in binding – Keep binding only for registration; move initialization to controller's onInit.
  • Use initial binding for truly global services – Like API clients, database helpers, theme managers.
  • Name bindings consistently – e.g., HomeBinding, ProfileBinding to match route names.

Common Mistakes

  • ❌ Calling Get.put inside a binding without lazy – Can still work, but lazy is generally better. ✅ Use Get.lazyPut unless you need the instance immediately.
  • ❌ Forgetting to dispose of stream subscriptions – Controllers are disposed, but you still need to clean up manually in onClose. ✅ Always override onClose to dispose listeners.
  • ❌ Creating bindings inside build methods – Causes re-creation of binding objects. ✅ Define bindings as separate classes or use GetPage with constant references.
  • ❌ Overusing permanent controllers – Can cause memory leaks if they hold onto resources. ✅ Use permanent: true only for truly global dependencies.

Conclusion

GetX bindings provide a powerful, route-centric way to manage dependencies. They improve code structure, performance, and lifecycle management. By adopting bindings, you ensure that your controllers are created and disposed at the right time, leading to cleaner, more maintainable Flutter apps.

Try it yourself

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX Bindings Demo',
      initialRoute: '/',
      getPages: [
        GetPage(
          name: '/',
          page: () => HomePage(),
          binding: HomeBinding(),
        ),
        GetPage(
          name: '/details',
          page: () => DetailsPage(),
          binding: DetailsBinding(),
        ),
      ],
    );
  }
}

// Controllers
class HomeController extends GetxController {
  var count = 0.obs;
  void increment() => count++;

  @override
  void onInit() {
    super.onInit();
    print('HomeController created');
  }

  @override
  void onClose() {
    print('HomeController disposed');
    super.onClose();
  }
}

class DetailsController extends GetxController {
  final String message;
  DetailsController(this.message);

  @override
  void onInit() {
    super.onInit();
    print('DetailsController created with message: $message');
  }

  @override
  void onClose() {
    print('DetailsController disposed');
    super.onClose();
  }
}

// Bindings
class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController());
  }
}

class DetailsBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => DetailsController('Hello from binding!'));
  }
}

// Views
class HomePage extends GetView<HomeController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Obx(() => Text('Count: ${controller.count}', style: TextStyle(fontSize: 24))),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: controller.increment,
              child: Text('Increment'),
            ),
            ElevatedButton(
              onPressed: () => Get.toNamed('/details'),
              child: Text('Go to Details'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailsPage extends GetView<DetailsController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Details')),
      body: Center(
        child: Text('Message: ${controller.message}'),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

What method must be overridden in a binding class?

A
init()
B
dependencies()
C
onStart()
D
register()
Q2
of 4

What is the main benefit of using bindings?

A
They automatically create widgets
B
They manage dependencies at the route level
C
They replace state management
D
They improve animations
Q3
of 4

How do you register a binding for a named route in GetPage?

A
binding: MyBinding()
B
bind: MyBinding()
C
inject: MyBinding()
D
deps: MyBinding()
Q4
of 4

Which GetX method is preferred inside bindings for better performance?

A
Get.put
B
Get.lazyPut
C
Get.create
D
Get.find

Frequently Asked Questions

Do I have to use bindings with GetX?

No, they are optional. You can still use Get.put directly in your widgets. Bindings just provide a cleaner organization, especially for large apps.

Can I use both `Get.put` inside a binding and in a view?

Yes, but be careful not to create duplicate controllers. The binding will create it once; additional Get.put might try to create again unless you check isRegistered first.

How do I test a controller that is injected via binding?

You can use Get.reset() to clear registrations and then manually register mock dependencies before testing.

What happens if I navigate to the same route twice?

Each navigation will trigger its own binding, but if the same controller class is used, and it's not marked permanent, a new instance is created for the new route (if using lazyPut without fenix).

Can I have a binding without a route?

Yes, you can manually call InitialBinding().dependencies() at startup, but it's better to use initialBinding in GetMaterialApp.

How do I pass data to a controller via binding?

Use the binding's constructor to accept arguments, then pass them to the controller registration as shown in the parameterized binding example.

What is the difference between `Get.lazyPut` and `Get.put` in bindings?

Get.lazyPut creates the instance only when it's first accessed (lazy loading), improving startup time. Get.put creates it immediately. For most cases, lazyPut is preferred inside bindings.

Previous

getx lifecycle

Next

getx navigation

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.