Flutter: introducción a la gestión de estados con Riverpod

Riverpod es un marco de inyección de dependencia y administración de estado reactivo, utiliza diferentes proveedores para permitirnos acceder y escuchar los cambios de estado en nuestra aplicación, está construido por Remi Rousselet. Si no sabe qué es el estado, le recomiendo que lea este artículo primero, ya que le resultará un poco difícil entender Riverpod sin comprender el «estado» en sí.

¿Qué hace realmente Riverpod?

Riverpod es un ayudante de gestión estatal. Básicamente, hace que nuestro estado (en palabras claras, los valores de nuestras variables) sea accesible en todas las partes de la aplicación, coloca nuestro estado en la parte superior del árbol de widgets y nos permite escuchar esos cambios de estado y actualizar nuestra interfaz de usuario en consecuencia. 

Hay muchos tipos de proveedores que ofrece Riverpod. Los revisaremos uno por uno.

¿Qué son los proveedores?

Los proveedores son la parte más importante de una aplicación Riverpod, «Un proveedor es un objeto que encapsula un estado y permite escuchar ese estado».

Tipos de proveedores:

  • EstadoProveedor
  • FuturoProveedor
  • StreamProvider
  • Proveedor

¡Empecemos a construir!

1. Agregue Riverpod a nuestra aplicación:

Dart

dependencies:
  flutter_riverpod: ^0.14.0+3

2. Envolviendo nuestra aplicación:

Para que los proveedores funcionen, debemos agregar ProviderScope en la raíz de nuestras aplicaciones Flutter:

Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
  runApp(ProviderScope(child: MyApp()));
}

3. Cree el archivo proveedores.dart :

Definiremos todos nuestros proveedores globales en este archivo para que sea más fácil mantener el proyecto. Definamos un StateProvider.

Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
  
// Instead of String you can use other data types as well,
// we can also use custom data types.
final userNameProvider=StateProvider<String>((ref) {
  
   // we can also return an empty String here, for the sake of simplicity, 
  //  let's return a sample name 
  return "SomeName";
});

4. Proveedores de lectura:

Ahora que hemos declarado un StateProvider, aprendamos cómo podemos leer proveedores. Para leer cualquier proveedor, tenemos múltiples widgets, en este artículo, repasaremos ConsumerWidget() y Consumer().

Usando un ConsumerWidget():

Un widget de consumidor es un widget que podemos usar en lugar de nuestro widget Stateful/Stateless . Nos da la capacidad de leer/cambiar los estados de los proveedores y también escucharlos.

Dart

import 'Providers/providers.dart' as providers.dart; //for easy access
import 'package:flutter_riverpod/flutter_riverpod.dart';
  
class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
      
    // Listens to the value exposed by userNameProvider
    // ".state" method lets us get the state easily & directly
    String name = watch(providers.userNameProvider).state;
         
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Center(child: const Text('GFG 🙂 '))),
        body: Center(
            
          // displaying the value
          child: Text('$name'), 
        ),
      ),
    );
  }
}

Producción:

Bien y bueno. Así que ahora, cada vez que cambie el valor de userNameProvider , Text() se actualizará en consecuencia. Pero, ¿cómo vamos a actualizar el valor de userNameProvider?

5. Actualizar/Cambiar el valor de un StateProvider:

Para cambiar el valor de StateProvider, necesitamos un StateController . Así que vamos a crearlo.

Primero, eliminemos la variable «nombre». ̶

S̶t̶r̶i̶n̶g̶ ̶n̶a̶m̶e̶ ̶=̶ ̶w̶a̶t̶c̶h̶(̶p̶r̶o̶v̶i̶d̶e̶r̶s̶.̶u̶s̶e̶r̶N̶a̶m̶e̶P̶r̶o̶v̶i̶d̶e̶r̶)̶.̶s̶t̶a̶t̶e̶;̶

//removed as with it we can only listen/get the state not change/mutate the state.

//let's use this StateController instead.

StateController<String> nameController = watch(providers.userNameProvider);

// now we can get / set the state using name.state.

En aras de la simplicidad, usemos un FloatingActionButton, y usándolo, intentemos mutar (cambiar) el estado de userNameProvider.

Dart

// for easy access
import 'Providers/providers.dart' as providers.dart;
  
class Home extends ConsumerWidget {
  int n = 0;
  @override
  Widget build(BuildContext context, ScopedReader watch) {
      
    // Listens to the value exposed by userNameProvider
    StateController<String> nameController = watch(providers.userNameProvider);
  
    // ".state" method lets us get the state easily & directly
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            n++;
            nameController.state =
                
                // now we can set the state using the state setter.
                "New Name $n"; 
          },
        ),
        appBar: AppBar(title: Center(child: const Text('GFG 🙂 '))),
        body: Center(
          child: Text(
              
            // displaying the value
            '${nameController.state}',
          ), 
        ),
      ),
    );
  }
}

Producción:

Ahora, cuando presionemos el FloatingActionButton(), veremos que el nombre cambia cada vez con un número diferente. Eso es todo. Usando esta información, podemos usar fácilmente StateProviders, mutar sus valores y escucharlos.

Ahora, repasemos los proveedores asincrónicos.

1. Futuro Proveedor:

Cuando trabajamos con código asincrónico, a menudo usamos algunas API basadas en Future, veamos cómo puede manejar eventos Future usando Riverpod. Para demostrarlo, crearemos un método que devuelva un futuro.

Dart

class FutureClass {
    
  // in production, the below method could be any network call
  Future<int> getData(String para) async {
    await Future.delayed(Duration(seconds: 5));
    return 25;
  }
}

Creando un proveedor para FutureClass:

final futureClass = Provider((ref) => FutureClass());

Ahora, creemos nuestro FutureProvider,

Dart

final response = FutureProvider<int>((ref) async {
  final client = ref.read(futureClass);
  return client.getData('some text as a parameter');
});

Ahora, podemos usar ConsumerWidget para escuchar los cambios de estado y actualizar nuestra interfaz de usuario en consecuencia.

Consumir un FutureProvider:

Además de ConsumerWidget(), también tenemos un widget Consumer(). La diferencia es que mientras ConsumerWidget reconstruye toda la pantalla cuando cambia el estado, Consumer() reconstruye solo su elemento secundario, asegurándose de que no tengamos problemas de rendimiento.

Usemos el widget Consumer() para observar (escuchar) los cambios de estado.

Dart

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(title: Text("FutureProvider Demo"),),
      body: Center(
        child: Column(
         children: [
            Consumer(
              builder: (context, watch, child) {
                final futureData = watch(response);               
              },
            ),
          ],
        ),
      ),
    );
  }
}

Como un futuro puede tener 3 estados, es decir, completado, en progreso y error, tenemos la capacidad de manejar esos estados por separado usando la función .map.

Dart

Consumer(
  builder: (context, watch, child) {
    final futureData = watch(response);
  
    return  futureData.map(
        
      ,// have data
      data: (data) => Text('${data.value}',)
        
      ,// in progress
      loading: (_) => CircularProgressIndicator()
        
      // has an error
      error: (message) => Text(message.error),
    );
  },
),

Si queremos pasar una variable/objeto al proveedor como argumento, entonces podemos hacer eso como-

Dart

final response=
    FutureProvider.autoDispose.family<int, String>((ref, i_am_a_param) async {
  final client = ref.read(futureClass);
  return client.getData(i_am_a_param);
});
  
// the family modifier is used to pass a String value,
// which is used to call the get() method.
// The modifier "autoDispose" destroys the state of a provider 
// when it is no longer used, even when the widget state is not yet dispose

Código completo:

Dart

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("FutureProvider Demo"),),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Consumer(
                  builder: (context, watch, child) {
                    final futureData = watch(providers.response);
  
                    return futureData.map(
                      data: (data) => Text(
                        '${data.value}',
                      ), // have data
                      loading: (_) => Column(
                        children: [
                          CircularProgressIndicator(),
                          Text(
                            'Fetching data',
                          ),
                        ],
                      ), // in progress
                      error: (message) =>
                          Text(message.error.toString()),
                      // has an error
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Producción:

2. Proveedor de transmisión:

A menudo usamos transmisiones en una aplicación flutter, ya sea para obtener datos de Firestore o leer datos de un archivo. Aprendamos cómo podemos manejar esos flujos con Riverpod.

Primero declaremos una corriente,

final streamProvider = StreamProvider<int>((ref) {
  return Stream.fromIterable([105, 50]);
  //in production this could be a stream of documents from Firestore
});

Consumo de StreamProvider:

Esta vez, usemos ConsumerWidget() para consumir los datos de StreamProvider, es bastante similar al de FutureProvider.

Dart

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("StreamProvider Demo"),
        ),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Consumer(
                  builder: (context, watch, child) {
                    final streamValue = watch(providers.streamProvider);
  
                    return streamValue.when(
                      data: (data) => Text(
                        '${data}',
                      ), // have data
                      loading: () => Column(
                        children: [
                          CircularProgressIndicator(),
                          Text(
                            'Fetching data',
                          ),
                        ],
                      ), // in progress
                      error: (message, e) =>
                          Text(message.toString()),
                      // has an error
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Ahora, cada vez que la transmisión tenga datos nuevos, nuestra interfaz de usuario se actualizará en consecuencia.

Observe cómo no tuvimos que administrar diferentes estados por nosotros mismos, sin Riverpod, el código anterior se vería así:

Dart

final stream = Stream.fromIterable([105, 50]);
  
StreamBuilder<int>(
  stream: stream,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.active) {
      if (snapshot.hasData) {
          
         // data
        return SomeWidget(snapshot.data);
      } else if (snapshot.hasError) {
          
        // error state
        return SomeErrorWidget(snapshot.error); 
      } else {
          
        // no data
        return Text('No data'); 
      }
    } else {
        
       // loading state
      return CircularProgressIndicator();
    }
  }
)

Eso es todo. Esto debería cubrir casi todo lo que necesitamos aprender para comenzar con Riverpod.

El código completo para el artículo anterior se puede encontrar aquí .

Publicación traducida automáticamente

Artículo escrito por madhavamshahi12 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *