Flutter y el patrón Provider

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

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

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:

import 'package:flutter/material.dart';

class FloatingAction extends StatelessWidget {
  
  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.

import 'package:flutter/material.dart';

class Body extends StatelessWidget {
  
  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:

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  
  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:

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

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

class MyApp extends StatelessWidget {
  
  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

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:

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 {
  
  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:

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

class FloatingAction extends StatelessWidget {
  
  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:

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

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

De esta manera podemos modifcar el estado global, ahora solamente debemos mostrar los valores, veamos como:

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 {
  
  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:

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

class Body extends StatelessWidget {
  
  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:

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

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
     title: 'Provider', 
     home: Home()
    );
  }
}

Percatarse aquí como MultiPorvider admite un arreglo de Provider:

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!!

17