flutter
/

GetX Dependency Injection: The Complete Guide

Last Sync: Today

On this page

16
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Dependency Injection: The Complete Guide

What is Dependency Injection in GetX?

Dependency injection (DI) is a design pattern that allows you to manage the dependencies of your classes efficiently. GetX provides a built-in, high-performance DI system that automatically handles the lifecycle of your controllers, services, and other dependencies. This eliminates the need for BuildContext to access instances, reduces boilerplate, and makes your code more testable and modular.

Why Use GetX for DI?

  • Automatic lifecycle management – GetX disposes controllers when they are no longer needed.
  • No BuildContext required – Access dependencies anywhere in your app.
  • Lazy loading – Create instances only when first accessed for better performance.
  • Smart management – Control whether a dependency is permanent, temporary, or recreated.
  • Easy testing – Mock dependencies without modifying production code.
  • Clean code – Separate dependency registration from UI logic.

Comparison of DI Methods

MethodCreation TimeInstance SharingUse Case
`Get.put`ImmediatelySingle instance (shared)When you need the instance right away, e.g., main controllers
`Get.lazyPut`On first accessSingle instance (shared)Most common – improves startup performance
`Get.putAsync`Asynchronously immediatelySingle instance (shared)When initialization is async (DB, network)
`Get.create`Each `Get.find`New instance each timeTransient dependencies, not shared

  1. Get.put – Simple Injection

Get.put is the most straightforward way to inject a dependency. It creates an instance immediately and makes it available throughout the app. Use it when you need the instance right away.

DARTRead-only
1
class MyController extends GetxController {
  // your logic
}

// Inject immediately
final controller = Get.put(MyController());

// Later, anywhere in your app, you can retrieve it
final controller = Get.find<MyController>();

  1. Get.lazyPut – Lazy Injection

Get.lazyPut delays the creation of the instance until it is first accessed. This is ideal for improving startup performance and for dependencies that may not be used immediately.

DARTRead-only
1
Get.lazyPut<MyController>(() => MyController());

// The instance is created only when this line is called
final controller = Get.find<MyController>();

  1. Get.putAsync – Asynchronous Injection

Use Get.putAsync when your dependency requires asynchronous initialization (e.g., opening a database connection, reading files, initializing a service with async setup).

DARTRead-only
1
Get.putAsync<DatabaseService>(() async {
  final db = await DatabaseService.init();
  return db;
});

// Later, you can find it synchronously
final db = Get.find<DatabaseService>();

  1. Get.create – Fresh Instance Each Time

Get.create creates a new instance every time you call Get.find. This is useful for dependencies that should not be shared (e.g., a transient service or a new controller each time).

DARTRead-only
1
Get.create<MyController>(() => MyController());

// Each Get.find creates a new instance
final c1 = Get.find<MyController>();
final c2 = Get.find<MyController>();
// c1 != c2

  1. fenix: true – Keep Instance Alive

By default, when a controller is no longer referenced (e.g., the view is closed), GetX disposes it. Setting fenix: true allows the instance to be recreated later if needed, without losing the state (if it was kept). This is especially useful with Get.lazyPut to allow recreation after disposal.

DARTRead-only
1
Get.lazyPut<MyController>(
  () => MyController(),
  fenix: true,
);
// Now if the controller is disposed, Get.find will create a new one.

  1. Permanent Controllers

You can make a controller permanent so it is never disposed automatically. Use permanent: true. This is useful for global services that should live for the entire app lifecycle.

DARTRead-only
1
Get.put(MyController(), permanent: true);
// This instance will live for the entire app lifecycle.

  1. Removing Dependencies Manually

In some cases, you may want to manually remove a dependency from GetX's memory.

DARTRead-only
1
Get.delete<MyController>(); // deletes the instance

// Check if an instance exists
if (Get.isRegistered<MyController>()) { ... }

// Delete with force (even if permanent)
Get.delete<MyController>(force: true);

  1. Injecting Dependencies into Controllers

Controllers often depend on services. You can inject them using the constructor, which makes your code more testable.

DARTRead-only
1
class ApiService extends GetxService { ... }

class HomeController extends GetxController {
  final ApiService api;
  HomeController(this.api);
}

// In your binding or injection:
Get.lazyPut<ApiService>(() => ApiService());
Get.lazyPut<HomeController>(() => HomeController(Get.find()));

  1. GetxService – App-Wide Services

GetxService is a special type of controller that is never disposed automatically unless you manually delete it. It's perfect for app-wide services like authentication, API clients, theme managers, or local storage.

DARTRead-only
1
class AuthService extends GetxService {
  Future<void> login(String email, String password) async { ... }
  void logout() { ... }
}

// Register as a service (usually in initialBinding)
await Get.putAsync<AuthService>(() => AuthService().init());

// Or simply
Get.put(AuthService(), permanent: true);

  1. Bindings – The Recommended Way

Bindings allow you to define dependencies for a specific route. This keeps your code organized and ensures that controllers are created and disposed with the route. Bindings are the recommended approach in larger apps.

DARTRead-only
1
class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<HomeController>(() => HomeController());
    Get.lazyPut<ApiService>(() => ApiService());
  }
}

// In your route declaration
GetPage(
  name: '/home',
  page: () => HomeView(),
  binding: HomeBinding(),
);

Best Practices

  • Use Bindings – Keep dependency registration near the route for better organization.
  • Prefer Get.lazyPut – Improves startup performance; only instantiate when needed.
  • Inject dependencies via constructor – Makes testing easier and clarifies dependencies.
  • Use GetxService for singletons – For API clients, database connections, etc.
  • Avoid using Get.put inside widgets – Use bindings or Get.lazyPut to avoid duplicate instances on rebuilds.
  • Set permanent: true sparingly – Only for truly global dependencies that should never be disposed.
  • Use Get.isRegistered before creating – Prevents duplicate registrations.

Common Mistakes

  • ❌ Calling Get.put inside build method – Causes multiple instances. ✅ Use bindings or Get.lazyPut.
  • ❌ Forgetting to dispose streams in onClose – Memory leaks. ✅ Always clean up resources in onClose.
  • ❌ Not using Get.find after async injection – Get.putAsync must be awaited before using Get.find. ✅ Use FutureBuilder or await before accessing.
  • ❌ Overusing permanent controllers – Keeps resources alive unnecessarily. ✅ Let GetX dispose controllers automatically when possible.
  • ❌ Hardcoding Get.find in many places – Makes refactoring harder. ✅ Use GetView or pass dependencies via constructor.

Conclusion

GetX dependency injection is powerful yet simple. It reduces boilerplate, automatically manages lifecycle, and makes your code more modular and testable. By understanding the different injection methods and combining them with bindings, you can build scalable Flutter applications with ease.

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(
      home: HomePage(),
    );
  }
}

// A simple service
class CounterService extends GetxService {
  int count = 0;
  void increment() => count++;
}

// Controller that depends on the service
class HomeController extends GetxController {
  final CounterService service;
  HomeController(this.service);

  void increment() => service.increment();
  int get count => service.count;
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Register dependencies
    if (!Get.isRegistered<CounterService>()) {
      Get.lazyPut<CounterService>(() => CounterService());
    }
    if (!Get.isRegistered<HomeController>()) {
      Get.lazyPut<HomeController>(() => HomeController(Get.find()));
    }

    // Use GetView for cleaner access
    return GetView<HomeController>(
      builder: (controller) {
        return Scaffold(
          appBar: AppBar(title: Text('DI Example')),
          body: Center(
            child: Obx(() => Text(
              'Count: ${controller.count}',
              style: TextStyle(fontSize: 32),
            )),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: controller.increment,
            child: Icon(Icons.add),
          ),
        );
      },
    );
  }
}

Test Your Knowledge

Q1
of 3

Which method should you use to delay the creation of a dependency until it is first accessed?

A
Get.put
B
Get.lazyPut
C
Get.create
D
Get.putAsync
Q2
of 3

What does `fenix: true` do in `Get.lazyPut`?

A
Makes the controller permanent
B
Allows recreation after disposal
C
Forces synchronous creation
D
Enables hot reload
Q3
of 3

Which class should you extend for an app-wide service that should never be disposed automatically?

A
GetxController
B
GetxService
C
Bindings
D
GetView

Frequently Asked Questions

What's the difference between `Get.put` and `Get.lazyPut`?

Get.put creates the instance immediately; Get.lazyPut creates it only when first accessed. Use Get.lazyPut for better startup performance.

When should I use `Get.create`?

When you need a fresh instance every time, for example, a controller that should not be shared across different parts of the app.

Can I use both `Get.put` and `Get.lazyPut` for the same class?

You can, but it will create multiple instances. Usually you want one registration per class. Use Get.isRegistered to check before registering.

How do I pass parameters to a controller when injecting?

Use a factory method in Get.lazyPut that takes parameters, or pass them via the binding's constructor and then to the controller registration.

What's the difference between `GetxController` and `GetxService`?

GetxService is a controller marked permanent by default. Use it for app-wide services. GetxController is disposed when its associated route is removed unless marked permanent.

How do I test a controller that uses GetX DI?

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

What is `fenix` in `Get.lazyPut`?

fenix: true allows the instance to be recreated after it has been disposed. Without it, once disposed, Get.find will throw an error. With fenix: true, a new instance is created on next access.

Previous

getx navigation arguments

Next

getx put

Related Content

Need help?

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