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.