Flutter Intermediate Lesson 52 of 135 4 min read
GetX in Flutter: Obx, GetBuilder, Dependency Injection and Routing
Learn GetX state management step by step: reactive Obx with .obs, simple GetBuilder with update(), Get.put and Get.find, and routing. Easy examples.
On this page
GetX (package name get) is an all-in-one toolkit: state management, dependency injection and routing in one package, with very little code. It is popular for building apps quickly.
GetX has two ways to manage state:
Obx: reactive. Mark a value with.obsand anyObxthat reads it rebuilds automatically.GetBuilder: simple. Change a normal field, callupdate(), andGetBuilderrebuilds.
The get package is not available in the online editor, so the examples below have no Run button. Copy them into lib/main.dart of a new project to try them.
Install
flutter pub add get
Replace MaterialApp with GetMaterialApp so routing, snackbars and dialogs work without a context.
Obx: reactive state
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class CounterController extends GetxController {
final count = 0.obs; // an observable int (RxInt)
void increment() => count.value++;
}
void main() => runApp(const GetMaterialApp(home: CounterPage()));
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
final c = Get.put(CounterController()); // create and register once
return Scaffold(
appBar: AppBar(title: const Text('GetX Obx')),
body: Center(
child: Obx(() => Text('${c.count.value}', style: const TextStyle(fontSize: 48))),
),
floatingActionButton: FloatingActionButton(
onPressed: c.increment,
child: const Icon(Icons.add),
),
);
}
}
.obsturns any value into an observable:0.obs,''.obs,false.obs,<String>[].obs,User().obs.Obx(() => ...)rebuilds when an observable it read changes.Get.putcreates the controller and stores it so other screens can find it.
GetBuilder: simple state
No .obs; call update() yourself. It uses less memory and is very predictable.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class CartController extends GetxController {
final List<String> items = [];
void add(String item) {
items.add(item);
update(); // rebuild every GetBuilder<CartController>
}
}
void main() => runApp(const GetMaterialApp(home: ShopPage()));
class ShopPage extends StatelessWidget {
const ShopPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<CartController>(
init: CartController(),
builder: (cart) => Scaffold(
appBar: AppBar(title: Text('Cart: ${cart.items.length}')),
body: ListView(
children: [
for (final fruit in ['Apple', 'Mango', 'Banana'])
ListTile(
title: Text(fruit),
trailing: IconButton(
icon: const Icon(Icons.add),
onPressed: () => cart.add(fruit),
),
),
],
),
),
);
}
}
Obx vs GetBuilder
Obx | GetBuilder | |
|---|---|---|
| Values | .obs observables | Normal fields |
| Rebuild | Automatic | When you call update() |
| Best for | Lots of small reactive values | Simple, predictable screens |
There is also a GetX<Controller>(builder: ...) widget, a reactive builder that can create the controller for you.
Dependency injection
Get.put(ApiService()); // create now
Get.lazyPut(() => ProfileController()); // create the first time it is used
final api = Get.find<ApiService>(); // get it anywhere, no context
Get.delete<ProfileController>(); // remove it
Bindings group the controllers a screen needs:
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeController());
}
}
Routing, snackbars and dialogs without context
Get.to(() => const DetailsPage()); // push
Get.back(); // pop
Get.off(() => const LoginPage()); // replace
Get.toNamed('/details', arguments: 42); // named route
Get.snackbar('Saved', 'Your note was saved');
Get.defaultDialog(title: 'Delete?', middleText: 'This cannot be undone');
A login screen example
class LoginController extends GetxController {
final email = ''.obs;
final loading = false.obs;
bool get isValid => email.value.contains('@');
Future<void> login() async {
loading.value = true;
await Future.delayed(const Duration(seconds: 1)); // call your API here
loading.value = false;
Get.snackbar('Welcome', email.value);
}
}
// In the widget:
final c = Get.put(LoginController());
TextField(onChanged: (v) => c.email.value = v);
Obx(() => FilledButton(
onPressed: c.isValid && !c.loading.value ? c.login : null,
child: c.loading.value
? const CircularProgressIndicator()
: const Text('Log in'),
));
Pros and cons
- Pros: very little code; state, DI and routing in one place; no
contextneeded; fast to prototype. - Cons: a lot of “magic”, which can make large apps harder to test and reason about; it replaces many Flutter conventions, so skills transfer less to other packages; check the package’s update activity before starting a long project.
Good for: quick development, small to medium apps, developers who want one toolkit. For large team projects, Riverpod or Bloc are more common.
Try it yourself
Build a two-screen app: a list of products on the first screen and a cart on the second, sharing one CartController found with Get.find. Show the cart count in the first screen’s app bar with Obx.