advanced getx patterns

Advanced GetX Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "advanced getx patterns" with this command: npx skills add kaakati/rails-enterprise-dev/kaakati-rails-enterprise-dev-advanced-getx-patterns

Advanced GetX Patterns

Advanced GetX patterns for building sophisticated reactive applications with proper state management, dependency injection, and network communication.

Workers - Reactive Side Effects

Workers allow you to execute callbacks when observable values change.

ever - Execute on Every Change

class UserController extends GetxController { final user = Rx<User?>(null); final isAuthenticated = false.obs;

@override void onInit() { super.onInit();

// Execute callback every time user changes
ever(user, (User? userData) {
  if (userData != null) {
    print('User logged in: ${userData.name}');
    isAuthenticated.value = true;
  } else {
    print('User logged out');
    isAuthenticated.value = false;
  }
});

} }

once - Execute Only Once

class OnboardingController extends GetxController { final hasCompletedOnboarding = false.obs;

@override void onInit() { super.onInit();

// Execute only the first time value becomes true
once(hasCompletedOnboarding, (_) {
  Get.offAllNamed(AppRoutes.home);
  // This won't run again even if value changes back to false and then true
});

} }

debounce - Delay Execution

class SearchController extends GetxController { final searchQuery = ''.obs; final searchResults = <Product>[].obs; final isSearching = false.obs;

@override void onInit() { super.onInit();

// Wait 800ms after user stops typing before searching
debounce(
  searchQuery,
  (_) => performSearch(),
  time: const Duration(milliseconds: 800),
);

}

Future<void> performSearch() async { if (searchQuery.value.isEmpty) { searchResults.clear(); return; }

isSearching.value = true;
final result = await repository.search(searchQuery.value);
result.fold(
  (failure) => searchResults.clear(),
  (products) => searchResults.value = products,
);
isSearching.value = false;

} }

interval - Execute Periodically

class DashboardController extends GetxController { final stats = Rx<DashboardStats?>(null);

@override void onInit() { super.onInit();

// Refresh stats every 30 seconds while value changes
interval(
  stats,
  (_) => refreshStats(),
  time: const Duration(seconds: 30),
);

// Initial load
refreshStats();

}

Future<void> refreshStats() async { final result = await repository.getStats(); result.fold( (failure) => {}, (data) => stats.value = data, ); } }

Worker Best Practices

class MyController extends GetxController { final count = 0.obs; Worker? _countWorker;

@override void onInit() { super.onInit();

// Store worker reference for manual disposal
_countWorker = ever(count, (value) {
  print('Count changed to: $value');
});

}

@override void onClose() { // Dispose worker manually if needed _countWorker?.dispose(); super.onClose(); } }

GetxService - Permanent Services

GetxService instances are never disposed, perfect for app-wide services.

Creating a Service

import 'package:get/get.dart';

class AuthService extends GetxService { final _isAuthenticated = false.obs; bool get isAuthenticated => _isAuthenticated.value;

final _currentUser = Rx<User?>(null); User? get currentUser => _currentUser.value;

// Called when service is first created @override Future<void> onInit() async { super.onInit(); await _loadSavedAuth(); }

Future<void> _loadSavedAuth() async { final storage = Get.find<GetStorage>(); final token = storage.read<String>('auth_token'); if (token != null) { await validateToken(token); } }

Future<void> login(String email, String password) async { final result = await repository.login(email, password); result.fold( (failure) => throw Exception(failure.message), (user) { _currentUser.value = user; _isAuthenticated.value = true; Get.find<GetStorage>().write('auth_token', user.token); }, ); }

void logout() { _currentUser.value = null; _isAuthenticated.value = false; Get.find<GetStorage>().remove('auth_token'); }

@override void onClose() { // GetxService onClose is never called // Service persists throughout app lifecycle super.onClose(); } }

Registering Services

// In main.dart or dependency injection setup Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await GetStorage.init();

// Register permanent services Get.putAsync(() => AuthService().init(), permanent: true); Get.put(ThemeService(), permanent: true); Get.put(LocalizationService(), permanent: true);

runApp(MyApp()); }

// With async initialization class AuthService extends GetxService { Future<AuthService> init() async { await _loadSavedAuth(); return this; } }

SmartManagement - Lifecycle Control

Control how GetX manages controller lifecycle.

SmartManagement Modes

void main() { runApp( GetMaterialApp( smartManagement: SmartManagement.full, // Default home: HomePage(), ), ); }

SmartManagement.full (Default):

  • Disposes controllers when routes are closed

  • Most memory efficient

  • Recommended for most apps

SmartManagement.onlyBuilder:

  • Only disposes controllers created with GetBuilder

  • Get.find() instances persist

  • Use when you need manual control

SmartManagement.keepFactory:

  • Keeps factory instances

  • Controllers can be recreated with same dependencies

  • Use for complex dependency graphs

Manual Controller Disposal

class ManualController extends GetxController { // This controller won't auto-dispose }

// Register without auto-dispose Get.put(ManualController(), permanent: true);

// Manually dispose when needed Get.delete<ManualController>();

// Or with tag Get.put(ManualController(), tag: 'unique-tag'); Get.delete<ManualController>(tag: 'unique-tag');

GetConnect - HTTP Client

GetConnect provides a powerful HTTP client with interceptors and base URL configuration.

Basic Setup

class ApiProvider extends GetConnect { @override void onInit() { // Base URL httpClient.baseUrl = 'https://api.example.com';

// Default timeout
httpClient.timeout = const Duration(seconds: 30);

// Request interceptor
httpClient.addRequestModifier&#x3C;dynamic>((request) {
  // Add auth token to all requests
  final token = Get.find&#x3C;AuthService>().token;
  if (token != null) {
    request.headers['Authorization'] = 'Bearer $token';
  }
  request.headers['Content-Type'] = 'application/json';
  return request;
});

// Response interceptor
httpClient.addResponseModifier((request, response) {
  // Log responses in debug mode
  if (kDebugMode) {
    print('Response: ${response.statusCode} ${response.bodyString}');
  }
  return response;
});

// Auth interceptor
httpClient.addAuthenticator&#x3C;dynamic>((request) async {
  // Refresh token if 401
  final token = await refreshToken();
  request.headers['Authorization'] = 'Bearer $token';
  return request;
});

}

// GET request Future<Response<List<Product>>> getProducts() { return get<List<Product>>( '/products', decoder: (data) => (data as List) .map((item) => Product.fromJson(item)) .toList(), ); }

// POST request Future<Response<User>> createUser(User user) { return post<User>( '/users', user.toJson(), decoder: (data) => User.fromJson(data), ); }

// PUT request Future<Response<User>> updateUser(String id, User user) { return put<User>( '/users/$id', user.toJson(), decoder: (data) => User.fromJson(data), ); }

// DELETE request Future<Response> deleteUser(String id) { return delete('/users/$id'); } }

Using GetConnect in Repository

class UserRepositoryImpl implements UserRepository { final ApiProvider apiProvider;

UserRepositoryImpl({required this.apiProvider});

@override Future<Either<Failure, List<User>>> getUsers() async { try { final response = await apiProvider.get<List<User>>( '/users', decoder: (data) => (data as List) .map((json) => User.fromJson(json)) .toList(), );

  if (response.hasError) {
    return Left(ServerFailure(response.statusText ?? 'Unknown error'));
  }

  return Right(response.body!);
} catch (e) {
  return Left(ServerFailure(e.toString()));
}

} }

Bindings Composition

Combine multiple bindings for complex features.

Creating Bindings

// Feature binding class ProductBinding extends Bindings { @override void dependencies() { Get.lazyPut(() => ProductRemoteDataSource(api: Get.find())); Get.lazyPut(() => ProductLocalDataSource(storage: Get.find())); Get.lazyPut<ProductRepository>( () => ProductRepositoryImpl( remoteDataSource: Get.find(), localDataSource: Get.find(), ), ); Get.lazyPut(() => GetProducts(repository: Get.find())); Get.lazyPut(() => ProductController(getProducts: Get.find())); } }

// Another feature binding class CartBinding extends Bindings { @override void dependencies() { Get.lazyPut(() => CartLocalDataSource(storage: Get.find())); Get.lazyPut<CartRepository>( () => CartRepositoryImpl(localDataSource: Get.find()), ); Get.lazyPut(() => AddToCart(repository: Get.find())); Get.lazyPut(() => RemoveFromCart(repository: Get.find())); Get.lazyPut(() => CartController( addToCart: Get.find(), removeFromCart: Get.find(), )); } }

// Combine bindings class ProductDetailsBinding extends Bindings { @override void dependencies() { // Register product dependencies ProductBinding().dependencies(); // Register cart dependencies CartBinding().dependencies(); // Register page-specific controller Get.lazyPut(() => ProductDetailsController( product: Get.find(), cart: Get.find(), )); } }

Using BindingsBuilder

GetPage( name: '/checkout', page: () => CheckoutPage(), binding: BindingsBuilder(() { // Quick inline bindings Get.lazyPut(() => CheckoutController()); Get.lazyPut(() => PaymentService()); Get.lazyPut(() => ShippingService()); }), )

// Or with multiple bindings GetPage( name: '/checkout', page: () => CheckoutPage(), bindings: [ CartBinding(), PaymentBinding(), ShippingBinding(), ], )

Rx Advanced Patterns

Custom Rx Classes

class RxUser extends Rx<User> { RxUser(User initial) : super(initial);

String get fullName => value.firstName + ' ' + value.lastName;

bool get isAdmin => value.role == 'admin';

void updateEmail(String email) { value = value.copyWith(email: email); } }

// Usage final user = RxUser(User(firstName: 'John', lastName: 'Doe')); user.updateEmail('john@example.com');

Rx Transformations

class DataController extends GetxController { final rawData = <Item>[].obs;

// Computed observable List<Item> get filteredData => rawData.where((item) => item.isActive).toList();

// Or use Rx.map late final activeItems = rawData.map((data) => data.where((item) => item.isActive).toList() ).obs; }

GetQueue - Background Tasks

class UploadQueue extends GetxService { final _queue = <UploadTask>[].obs;

void addTask(UploadTask task) { _queue.add(task); processQueue(); }

Future<void> processQueue() async { while (_queue.isNotEmpty) { final task = _queue.first; try { await _uploadFile(task); _queue.removeAt(0); } catch (e) { // Retry logic task.retryCount++; if (task.retryCount >= 3) { _queue.removeAt(0); // Give up after 3 retries } else { await Future.delayed(Duration(seconds: task.retryCount * 2)); } } } }

Future<void> _uploadFile(UploadTask task) async { // Upload logic } }

Testing GetX Controllers

Unit Testing

void main() { late ProductController controller; late MockGetProducts mockGetProducts;

setUp(() { mockGetProducts = MockGetProducts(); controller = ProductController(getProducts: mockGetProducts); });

tearDown(() { controller.dispose(); });

test('loads products successfully', () async { // Arrange final products = [Product(id: '1', name: 'Product 1')]; when(() => mockGetProducts()) .thenAnswer((_) async => Right(products));

// Act
await controller.loadProducts();

// Assert
expect(controller.products, products);
expect(controller.isLoading, false);
verify(() => mockGetProducts()).called(1);

});

test('handles failure when loading products', () async { // Arrange when(() => mockGetProducts()) .thenAnswer((_) async => Left(ServerFailure('Error')));

// Act
await controller.loadProducts();

// Assert
expect(controller.products, isEmpty);
expect(controller.error, isNotNull);
expect(controller.isLoading, false);

}); }

Widget Testing with GetX

testWidgets('displays products when loaded', (tester) async { // Mock controller final controller = ProductController(getProducts: mockGetProducts); Get.put(controller);

// Pump widget await tester.pumpWidget( GetMaterialApp( home: ProductListPage(), ), );

// Simulate loading controller.products.value = [Product(id: '1', name: 'Product 1')]; await tester.pumpAndSettle();

// Assert expect(find.text('Product 1'), findsOneWidget);

// Cleanup Get.delete<ProductController>(); });

Best Practices

Workers:

  • Use debounce for search inputs (800ms delay)

  • Use ever for side effects (analytics, navigation)

  • Use once for one-time actions (onboarding)

  • Use interval for periodic updates (30s+)

  • Always dispose workers in onClose()

GetxService:

  • Use for app-wide services (Auth, Theme, Localization)

  • Register with permanent: true

  • Initialize async operations in init() method

  • Services should be stateless or immutable where possible

SmartManagement:

  • Stick with SmartManagement.full (default) for most apps

  • Only change if you have specific memory management needs

  • Document why you're using non-default management

GetConnect:

  • Create one GetConnect instance per API

  • Use interceptors for auth, logging, error handling

  • Implement retry logic in addAuthenticator

  • Handle errors consistently in repositories

Bindings:

  • One binding per feature/route

  • Use lazyPut for dependencies (loaded when first used)

  • Use put for singletons needed immediately

  • Compose bindings for complex features

Testing:

  • Always dispose controllers in tearDown()

  • Use Get.reset() to clear all dependencies between tests

  • Mock use cases, not repositories

  • Test reactive state changes with pumpAndSettle()

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

flutter conventions & best practices

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

getx state management patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

ruby oop patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

rails localization (i18n) - english & arabic

No summary provided by upstream source.

Repository SourceNeeds Review