Comparación entre GetX y Riverpod en Flutter: Ejemplos de uso en View y Controller

Tanto GetX como Riverpod son bibliotecas de gestión de estado para Flutter que permiten separar la lógica de presentación de la lógica de negocio en una aplicación.

A continuación se presenta una comparativa de ambas bibliotecas utilizando ejemplos de una vista y un controlador:

1. Creación de una vista:

En GetX, la vista se crea utilizando el método GetView y se define la lógica de presentación en el método build.

class CounterView extends GetView<CounterController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Text('Count: ${controller.count}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

En Riverpod, la vista se crea utilizando el método Consumer y se define la lógica de presentación dentro de un widget hijo.

class CounterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Consumer(
          builder: (context, watch, child) {
            final count = watch(counterProvider).state;
            return Text('Count: $count');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read(counterProvider).increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

2. Creación de un controlador

En GetX, se define un controlador utilizando la clase GetxController y se definen los métodos de la lógica de negocio.

class CounterController extends GetxController {
  var count = 0;

  void increment() {
    count++;
    update();
  }
}

En Riverpod, se define un controlador utilizando el método StateNotifier y se definen los métodos de la lógica de negocio.

class CounterController extends StateNotifier<int> {
  CounterController() : super(0);

  void increment() {
    state++;
  }
}

3. Acceso al controlador desde el View

En GetX, se utiliza el método Get.put para instanciar el controlador en la vista y luego se accede a él utilizando la propiedad controller.

class CounterView extends GetView<CounterController> {
  @override
  Widget build(BuildContext context) {
    Get.put(CounterController());
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Text('Count: ${controller.count}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

En Riverpod, se utiliza un Provider para instanciar el controlador y se accede a él utilizando el método watch o read en la vista.

final counterProvider = StateNotifierProvider<CounterController>((ref) => CounterController());

class CounterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Consumer(
          builder: (context, watch, child) {
            final count = watch(counterProvider).state;
            return Text('Count: $count');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read(counterProvider).increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

4. Inyección de dependencias:

En GetX, la inyección de dependencias se realiza a través del método Get.put en la vista o en el controlador.

Get.put(MyDependency());

class MyController extends GetxController {
  final MyDependency dependency;

  MyController(this.dependency);
}

En Riverpod, la inyección de dependencias se realiza a través de los Provider y se puede acceder a ellos utilizando el método watch o read.

final myDependencyProvider = Provider((ref) => MyDependency());

class MyController {
  final MyDependency dependency;

  MyController(this.dependency);
}

final myControllerProvider = Provider((ref) => MyController(ref.watch(myDependencyProvider)));

5. Uso de Streams:

En GetX, el manejo de Streams se realiza a través de la clase Rx y sus derivados. Por ejemplo, se puede utilizar RxInt para manejar un contador que emita cambios.

class CounterController extends GetxController {
  var count = RxInt(0);

  void increment() {
    count.value++;
  }
}

En Riverpod, se puede utilizar el método StreamProvider para crear un Stream que emita cambios y luego acceder a él en la vista utilizando el método watch.

final counterStreamProvider = StreamProvider<int>((ref) {
  final controller = ref.watch(counterControllerProvider);
  return controller.counterStream;
});

class CounterController {
  var _counter = 0;
  final _counterController = StreamController<int>();

  Stream<int> get counterStream => _counterController.stream;

  void increment() {
    _counter++;
    _counterController.add(_counter);
  }
}

6. Uso de Future:

En GetX, el manejo de Futures se realiza a través de la clase FutureBuilder y su método obx.

class CounterView extends GetView<CounterController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: FutureBuilder<int>(
          future: controller.fetchCount(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return Text('Count: ${snapshot.data}');
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            }
            return CircularProgressIndicator();
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

class CounterController extends GetxController {
  var count = 0;

  Future<int> fetchCount() async {
    // Llamada a una API o cualquier otra operación asíncrona
    await Future.delayed(Duration(seconds: 2));
    return count;
  }
}

En Riverpod, se utiliza el método FutureProvider para crear un Future que emita cambios y luego acceder a él en la vista utilizando el método watch.

final counterFutureProvider = FutureProvider<int>((ref) {
  final controller = ref.watch(counterControllerProvider);
  return controller.fetchCount();
});

class CounterController {
  var count = 0;

  Future<int> fetchCount() async {
    // Llamada a una API o cualquier otra operación asíncrona
    await Future.delayed(Duration(seconds: 2));
    return count;
  }
}

class CounterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Consumer(
          builder: (context, watch, child) {
            final countFuture = watch(counterFutureProvider);
            return countFuture.when(
              data: (count) => Text('Count: $count'),
              loading: () => CircularProgressIndicator(),
              error: (error, stackTrace) => Text('Error: $error'),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read(counterControllerProvider).increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

En resumen, GetX y Riverpod ofrecen formas eficientes y sencillas de manejar Streams y Futures en una aplicación Flutter. La elección entre una u otra dependerá de las necesidades específicas de cada proyecto y de la preferencia del desarrollador. En general, si se necesita un enfoque más ligero y directo, GetX puede ser una buena opción, mientras que si se necesita una mayor modularidad y una mejor integración con el sistema de tipos de Dart, Riverpod puede ser más adecuado. En cualquier caso, ambas bibliotecas ofrecen una gran cantidad de funcionalidades útiles para la creación de aplicaciones Flutter modernas y eficientes.

Referencias:

Riverpod Documentación Oficial

GetX

CPU generated with https://stablediffusionweb.com IA