Flutter y el patrón Provider
Tags: flutter, provider, pattern
Published: 2021-06-25 21:39:06

**Hola mundo!!** 

##Introducción
Tanto si desarrolla una aplicación nativa como una híbrida, el manejo del estado es un problema que debemos enfrentar y tener bajo control. Para administrar el estado global de una aplicación en `Flutter` existen varias maneras, en este post abordaremos `Provider`, una biblioteca bastante popular que nos ayudará con este trabajo.

##Aplicación de ejemplo
Para ver el funcionamiento de `Provider`, vamos a realizar una sencilla aplicación que nos ejemplifique. 
Esta app muestra una pantalla con un `AppBar` y su título, en el centro un `Text`, y dos botones flotantes en la esquina inferior derecha, que tendrán como función cambiar el valor del texto que será mostrado tanto en el título del `AppBar` como en el `Text` del body de la app.
Tenemos en cuenta que el valor del texto será administrado de manera global.

Dejo el link del ejercicio para quien desee descargarlo:
[https://github.com/hextiandro/flutter_provider_pattern](https://github.com/hextiandro/flutter_provider_pattern)

##Manos a la obra
Primeramente instalaremos la biblioteca: `provider: ^5.0.0` (En el momento que escribí el post esta era la versión). 
La podemos encontrar en: [https://pub.dev/packages/provider](https://pub.dev/packages/provider)

Dentro de lib creamos una carpeta llamada widgets y dentro un archivo llamado floating_actions.dart, `lib/widgets/floating_actions.dart` para crear el widget con los dos botones flotantes:

```dart
import 'package:flutter/material.dart';

class FloatingAction extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
 (context);
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        FloatingActionButton(
            child: Icon(Icons.wifi),
            backgroundColor: Colors.orange,
            onPressed: () {}),
        SizedBox(
          height: 10.0,
        ),
        FloatingActionButton(
            child: Icon(Icons.bluetooth),
            backgroundColor: Colors.blue,
            onPressed: () {})
      ],
    );
  }
}
```
Notemos que el `onPressed` lo tenemos sin acción, más adelante agregaremos su funcionalidad.

Ahora dentro de `lib/widgets` crearemos otro archivo llamado body, `lib/widgets/body.dart` para crear el `widget` con el texto centralizado.

```dart
import 'package:flutter/material.dart';

class Body extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    (context);
    return Center(
      child: Text("",
        style: TextStyle(fontSize: 20),
      ),
    );
  }
}
```
El texto mostrado por ahora es la cadena vacía.
Crearemos otro `widget` dentro de lib, llamado home `lib/Home.dart`:

```dart
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Center(child: Text("")),
      ),
      body: Body(),
      floatingActionButton: FloatingAction(),
    );
  }
}
```
Aquí estamos renderizando un `Scaffold`, con un `AppBar` y su título vacío por el momento, además renderizamos los `widgets` `Body` y `FloatingAction` creados anteriormente.
Nada de `Provider` aún, solo vamos armando la interfaz gráfica de la app, pasemos a modificar el `main.dart`.

Nuestro archivo `main.dart` debería lucir así por el momento: 

```dart
import 'package:flutter/material.dart';
import 'package:provider_pattern/home.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider',
      home: Home()
    );
  }
}
```
Con esto tenemos todo listo para comenzar a construir nuestro `Provider`; hagamos lo siguiente:

Dentro de lib creamos otro directorio llamdo provider y dentro un archivo llamado signal_mode.dart, `lib/provider/signal_mode.dart`

```dart
import 'package:flutter/material.dart';

class SignalMode extends ChangeNotifier {
  String _signalMode = 'Wifi mode';

  String get signalMode => _signalMode;

  set signalMode(String mode) {
    this._signalMode = mode;
    notifyListeners();
  }
}
```
Listo, este es nuestro `Provider`, es la clase que se ocupa de centralizar el valor de la variable privada `_signalMode` de tipo `String` que es el texto que se mostrará tanto en el título del `AppBar` como en el centro de la pantalla.

Notemos el primer detalle que extiende de `ChangeNotifier` clase encargada de exponer los eventos de notificación,
veamos que tenemos un método `get` para obtener el valor de `_signalMode`, y un método `set` para modificarlo, dentro de este método vemos la llamada a `notifyListeners()` que es quien va a notificar a todos los `widgets` que escuchen a este provider del cambio, para asi actualizar su estado.

Es momento de encapsular aquellos `widgets` que van a escuhar los cambios. 
Como queremos tener el control del estado en todos los niveles de la aplicación lo haremos en el `main`. 
Tenga en cuenta que no necesariamente se debe envolver en el nivel más alto de la aplicación en caso que desees compartir determinado estado para determinado nivel.
Si modificamos nuestro `main` quedaría de la siguiente forma:

```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_pattern/home.dart';
import 'package:provider_pattern/provider/signal_mode.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => SignalMode(),
      child: MaterialApp(
          title: 'Provider', 
          home: Home()),
    );
  }
}
```
De esta manera toda la app está en condiciones de escuchar los cambios de la clase `SignalMode`

Desde ahora ya podemos integrar los distintos `widgets` de nuestra app. Modifiquemos la clase `FloatingAction` para conectarla con nuestro `Provider` quedando de la siguiente forma:

```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_pattern/provider/signal_mode.dart';

class FloatingAction extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final signal = Provider.of<SignalMode>(context);

    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        FloatingActionButton(
            child: Icon(Icons.wifi),
            backgroundColor: Colors.orange,
            onPressed: () {
              signal.signalMode = "Wifi mode";
            }),
        SizedBox(
          height: 10.0,
        ),
        FloatingActionButton(
            child: Icon(Icons.bluetooth),
            backgroundColor: Colors.blue,
            onPressed: () {
              signal.signalMode = "Bluetooth mode";
            })
      ],
    );
  }
}
```
Notemos aquí en la siguiente línea:
```dart
  final signal = Provider.of<SignalMode>(context);
```
de esta manera obtenemos una instancia de nuestro `Provider` especificando que es de tipo `SignalMode`. 

El otro punto a notar aquí es en el `onPressed` de los botones, cada uno hace una llamada modificando el valor de `signalMode`

```dart
onPressed: () {
 signal.signalMode = "Wifi mode";
})
```

```dart
onPressed: () {
 signal.signalMode = "Bluetooth mode";
})
```
De esta manera podemos modifcar el estado global, ahora solamente debemos mostrar los valores, veamos como:

```dart
import 'package:flutter/material.dart';
import 'package:provider_pattern/provider/signal_mode.dart';
import 'package:provider_pattern/widgets/body.dart';
import 'package:provider_pattern/widgets/floating_actions.dart';
import 'package:provider/provider.dart';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final signal = Provider.of<SignalMode>(context);

    return Scaffold(
      appBar: AppBar(
        title: Center(child: Text(signal.signalMode)),
      ),
      body: Body(),
      floatingActionButton: FloatingAction(),
    );
  }
}
```
En la clase `Home` igualmente obtenemos la instancia del `Provider` y leemos la propiedad `signalHome`.

De la misma forma lo hacemos en la clase `Body`:
```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_pattern/provider/signal_mode.dart';

class Body extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final signal = Provider.of<SignalMode>(context);
    return Center(
      child: Text(
        signal.signalMode,
        style: TextStyle(fontSize: 20),
      ),
    );
  }
}
```
**Ya, es todo!!** 

Hemos finalizado nuestro ejemplo, pero les dejaré una última consideración para la utilización de múltiples providers tendríamos que modificar nuevamente nuestro `main` de la siguiente forma:

```dart
void main() => runApp(AppState());

class AppState extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
       ChangeNotifierProvider(create: (_) => SignalMode())
       ],
      child: MyApp(),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
     title: 'Provider', 
     home: Home()
    );
  }
}
```
Percatarse aquí como `MultiPorvider` admite un arreglo de `Provider`:

```dart
providers: [
 ChangeNotifierProvider(create: (_) => SignalMode())
],
```
##Conclusiones
Hasta aquí este post, espero haberme explicado bien y de alguna manera haberle sido útil, nos vemos en la próxima!!

**Chao Mundo!!**