GetX Workers in Flutter: Complete Guide to ever, once, debounce and interval
GetX workers let a Flutter controller react to changes in an Rx value without putting side effects inside the widget tree. They are especially useful for search, analytics, autosave, one-time navigation decisions, form validation and tap throttling.
ever reacts to every change, once reacts to the first change, debounce waits until updates stop, and interval limits how often a fast stream of updates can trigger work.What are GetX workers in Flutter?
GetX uses reactive values such as 0.obs, false.obs and <String>[].obs. Widgets wrapped in Obx rebuild when the values they read change. A worker serves a different purpose: it listens to the same reactive change and runs an action.
That action may call a repository, write a draft, send an analytics event or decide where to navigate. In other words, Obx is for rendering UI; workers are for reacting with behaviour. Keeping this distinction makes a controller easier to test and prevents API calls or navigation from being triggered during build().
| Tool | Primary job | Typical example |
|---|---|---|
Obx | Rebuilds a widget from Rx state | Show a loading spinner |
| GetX worker | Runs a callback after Rx emissions | Search after typing pauses |
GetBuilder | Rebuilds a selected UI block after update() | Refresh a grouped non-reactive screen section |
Install GetX and set up a controller
Add the current compatible get package version from pub.dev to pubspec.yaml, then import it in your controller.
import 'package:get/get.dart';
class SearchController extends GetxController {
final query = ''.obs;
final results = <String>[].obs;
}
Each worker function returns a Worker object. Store that reference when the listener has a controller-level lifetime. Initialise workers in onInit() and cancel them in onClose(). This makes ownership clear and protects the app when the controller outlives a route unexpectedly.
class SearchController extends GetxController {
final query = ''.obs;
late final Worker _queryWorker;
@override
void onInit() {
super.onInit();
_queryWorker = debounce<String>(
query,
_search,
time: const Duration(milliseconds: 400),
);
}
@override
void onClose() {
_queryWorker.dispose();
super.onClose();
}
Future<void> _search(String term) async {
// Call a repository here.
}
}
build(), inside an Obx callback, or each time a button is pressed. Those locations can create duplicate listeners.The five GetX worker types
| Worker | When callback runs | Best use case |
|---|---|---|
ever | Every eligible emission from one Rx | Synchronise a preference or record a state transition |
everAll | When any Rx in a list emits | Recalculate state from several filters |
once | Only the first eligible emission | Run onboarding or first successful login logic |
debounce | After changes stop for the chosen duration | Search, autosave, server-side validation |
interval | Limits callbacks while frequent changes continue | Throttle rapid taps, scroll telemetry or repeated actions |
1. ever: react to every change
Use ever when every state change matters. The callback receives the new value. A condition can be a Boolean or a function that returns a Boolean; it decides whether the callback is allowed to run.
class ThemeController extends GetxController {
final isDarkMode = false.obs;
late final Worker _themeWorker;
@override
void onInit() {
super.onInit();
_themeWorker = ever<bool>(
isDarkMode,
(enabled) => _saveThemePreference(enabled),
);
}
void _saveThemePreference(bool enabled) {
// Save the value through a repository or local storage service.
}
@override
void onClose() {
_themeWorker.dispose();
super.onClose();
}

