flutter
/

GetX Dependency Injection: Manage Controllers & Services Like a Pro

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: Manage Controllers & Services Like a Pro

Introduction to 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 powerful, built-in DI system that automatically handles the lifecycle of your controllers, services, and other dependencies. This reduces boilerplate, improves testability, and makes your code more modular.

Why Use GetX for DI?

  • Automatic disposal: GetX disposes controllers when they are no longer needed.
  • No BuildContext required: Access dependencies anywhere in your app.
  • Lazy loading: Create instances only when needed for better performance.
  • Smart management: Control whether a dependency is permanent or temporary.
  • Easy testing: Mock dependencies without modifying production code.

  1. Get.put – Simple Injection

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

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

// Inject in a widget, binding, or anywhere
final controller = Get.put(MyController());

// Later, access it anywhere
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).

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).

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.

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.

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
Get.delete<MyController>(force: 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(),
);

  1. GetView and GetWidget

GetView provides a convenient way to access a controller without manually calling Get.find. It automatically looks up the controller type you specify.

DARTRead-only
1
class HomeView extends GetView<HomeController> {
  @override
  Widget build(BuildContext context) {
    // controller is automatically available
    return Text('${controller.count}');
  }
}

  1. Injecting Dependencies into Controllers

Controllers often depend on services. You can inject them using the constructor.

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 unless you manually delete it. Use it for app-wide services like authentication, theme, or local storage.

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

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

Best Practices

  • Use Bindings – Keep your dependency registration near the route.
  • Prefer Get.lazyPut – Improve startup performance unless you need the instance immediately.
  • Inject dependencies via constructor – Makes testing easier.
  • Use GetxService for singletons – Like API clients, database connections.
  • Avoid using Get.put inside widgets – Use bindings or Get.lazyPut to avoid duplicate instances on rebuilds.

Common Mistakes

  • ❌ Calling Get.put in build method – Causes duplicate controllers. ✅ Use Get.lazyPut or bindings.
  • ❌ Forgetting to dispose streams in onClose – Can cause memory leaks. ✅ Always clean up resources.
  • ❌ 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 – Keep them only for truly global dependencies.

Conclusion

GetX's dependency injection system 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 {
  // Inject dependencies using Get.lazyPut
  @override
  Widget build(BuildContext context) {
    // Register service and controller using lazyPut
    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

Previous

getx structure

Next

getx reactive state

Related Content

Need help?

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