---
description: "GetX conventions for LegacyApp (legacy — migration available)"
alwaysApply: true
---

# GetX Standards — LegacyApp
> ⚠️ This project uses GetX. See `migration-agent` for incremental migration to Riverpod.

## Controller structure
```dart
class ProductsController extends GetxController {
  final ProductRepository _repo;
  ProductsController(this._repo);

  final RxList<Product> products = <Product>[].obs;
  final RxBool isLoading = false.obs;
  final Rx<String?> error = Rx(null);

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

  Future<void> fetchProducts() async {
    isLoading.value = true;
    error.value = null;
    try {
      products.value = await _repo.getProducts();
    } catch (e) {
      error.value = e.toString();
    } finally {
      isLoading.value = false;
    }
  }
}
```

## View pattern
```dart
// Views extend GetView<Controller> — never GetWidget or raw StatelessWidget
class ProductsView extends GetView<ProductsController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(() {
        if (controller.isLoading.value) return const ProductShimmer();
        if (controller.error.value != null) return ErrorWidget(controller.error.value!);
        return ProductList(controller.products);
      }),
    );
  }
}
```

## Rules
- **NEVER** pass `BuildContext` into a controller
- Use `Binding` classes for dependency injection — never `Get.put()` in a widget
- Use `.obs` for all reactive state — never call `update()` on non-observable state
- Use `Get.find<Controller>()` only in `Binding` classes, not in widgets
- **No business logic in Views** — controllers handle all logic

## File locations in LegacyApp
- `lib/features/[feature]/views/[feature]_view.dart`
- `lib/features/[feature]/controllers/[feature]_controller.dart`
- `lib/features/[feature]/bindings/[feature]_binding.dart`
- `lib/features/[feature]/models/[feature]_model.dart`
