Manejo de estados en flutter, Provider
Published: 2020-09-11 22:34:45

En esta guía se mostrará el uso de Provider para el manejo de estados en Flutter.

![Manejo de estados en flutter, BLoC Pattern sin librerías](https://user-images.githubusercontent.com/2531939/92287705-259ee500-eed0-11ea-8c3a-0d027aec1a6f.gif)

El código de esta guía se lo puede encontrar en GitHub.

{% github marcedroid/Flutter-State-Management-Demos no-readme %}

## Importante

La rama **provider** es la que tiene la implementación de esta guía.
La rama **master** tiene el código base de la aplicación.

------------

La aplicación cuenta con tres vistas:

- **User:** En esta vista al cambiar los valores del campo de texto también se actualizará el título del AppBar, además se actualizará en nombre de usuario en la vista cart.

- **Catalog:** En esta vista se muestra un listado de Items y al interactuar con cada uno de ellos se puede ver que el ícono para agregar/eliminar cambia, también se actualiza el contador de items del AppBar y el listado de items de la vista cart.

- **Cart:** En esta vista se muestra un listado de los productos que se han seleccionado en la vista de catalog también se muestra el precio total y el nombre de usuario junto con las iniciales del mismo en un CircleAvatar.

------------

Para mantener el código ordenado se creó la carpeta **providers** y dentro de esta los archivos **catalog_provider.dart**, **user_provider.dart**

- providers
  - catalog_provider.dart
  - user_provider.dart

------------

## Índice
- [Instalar Provider] (#instalar-provider)
- [Provider para nombre de usuario] (#provider-para-nombre-de-usuario)
- [Provider para catálogo] (#provider-para-catálogo)
- [Modificar main.dart] (#modificar-maindart)
- [Vistas] (#vistas)
  - [user.dart] (#userdart)
  - [catalog.dart] (#catalogdart)
  - [cart.dart] (#cartdart)

------------

## Instalar Provider
*https://pub.dev/packages/provider*

Para este ejemplo se ha utilizado la versión `4.3.2+2`
*pubspec.yaml*
```yaml
dependencies:
  provider: ^4.3.2+2
```
------------

## Provider para nombre de usuario

### user_provider.dart

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

class UserProvider with ChangeNotifier {
  String _username = 'Guest';

  String get username => _username;

  void onChange(value) {
    _username = value;
    notifyListeners();
  }
}
```

**Explicación:**

Importar la librería material.dart ya que se va a usar como mixin **ChangeNotifier** en la clase **UserProvider**
```dart
import 'package:flutter/material.dart';
```

Crear la clase **UserProvider** y utilizar como mixin la clase **ChangeNotifier**
```dart
class UserProvider with ChangeNotifier {
  ...
}
```

Crear una variable privada que va a contener el nombre de usuario, también agregar un getter para que se pueda obtener este valor fuera de la clase UserProvider.
```dart
String _username = 'Guest';

String get username => _username;
```

Crear un método que va a actualizar el nombre de usuario y a su vez va a notificar los cambios.
```dart
void onChange(value) {
  _username = value;

// Notificar los cambios con notifyListeners();
  notifyListeners();
}
```

------------

## Provider para catálogo

### catalog_provider.dart

```dart
import 'package:flutter/material.dart';
import 'package:state_management/models/item_model.dart';

class CatalogProvider with ChangeNotifier {
  List<ItemModel> _catalog = [];

  List<ItemModel> get catalog => _catalog;

  void addToCatalog(ItemModel itemModel) {
    _catalog.add(itemModel);
    notifyListeners();
  }

  void removeFromCatalog(ItemModel itemModel) {
    _catalog.remove(itemModel);
    notifyListeners();
  }
}
```

**Explicación:**

Al igual que con user_provider.dart es necesario importar la librería material.dart, pero esta vez también va a ser necesario item_model.dart ya que se van a usar objetos de tipo ItemModel.
```dart
import 'package:flutter/material.dart';
import 'package:state_management/models/item_model.dart';
```

Crear la clase **CatalogProvider** y utilizar como mixin la clase **ChangeNotifier**
```dart
class CatalogProvider with ChangeNotifier {
  ...
}
```

Crear una variable privada que va a contener el listado de items, también agregar un getter para que se pueda obtener este listado fuera de la clase CatalogProvider.
```dart
List<ItemModel> _catalog = [];

List<ItemModel> get catalog => _catalog;
```

Crear un método para agregar un nuevo item al listado y a su vez va a notificar los cambios.
```dart
void addToCatalog(ItemModel itemModel) {
  _catalog.add(itemModel);

// Notificar los cambios con notifyListeners();
  notifyListeners();
}
```

Crear otro método para eliminar un nuevo item del listado y a su vez va a notificar los cambios.
```dart
void removeFromCatalog(ItemModel itemModel) {
  _catalog.remove(itemModel);

// Notificar los cambios con notifyListeners();
  notifyListeners();
}
```

------------

## Modificar main.dart
```dart
import 'package:provider/provider.dart';
import 'package:state_management/providers/catalog_provider.dart';
import 'package:state_management/providers/user_provider.dart';

...

void main() => runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (_) => UserProvider()),
          ChangeNotifierProvider(create: (_) => CatalogProvider())
        ],
        child: MyApp(),
      ),
    );
```

**Explicación:**

Importar la librería provider.dart además de los archivos catalog_provider.dart y user_provider.dart.
```dart
import 'package:provider/provider.dart';
import 'package:state_management/providers/catalog_provider.dart';
import 'package:state_management/providers/user_provider.dart';
```

Modificar el método original `void main() => runApp(MyApp());`
Porque se deben crear los providers el nivel más alto posible en la jerarquía de widgets.
```dart
void main() => runApp(
// Agregar un MultiProvider ya que se van a utilizar varios providers
      MultiProvider(
        providers: [

// Crear el CatalogProvider
          ChangeNotifierProvider(create: (_) => UserProvider()),

// Crear el UserProvider
          ChangeNotifierProvider(create: (_) => CatalogProvider())
        ],

// Pasar MyApp() como widget hijo
        child: MyApp(),
      ),
    );
```

------------

## Vistas
En esta sección se mostrará los cambios necesarios a realizar para que las vistas se actualicen usando providers.

------------

### user.dart
```dart
import 'package:provider/provider.dart';
import 'package:state_management/providers/user_provider.dart';

...

@override
void initState() {
  super.initState();

  _textEditingController =
      TextEditingController(text: context.read<UserProvider>().username);
}

...

final _userProvider = Provider.of<UserProvider>(context);

...

title: Text('User - ${_userProvider.username}'),

...

onChanged: (value) {
  _userProvider.onChange(value);
},
```

**Explicación:**

Importar la librería provider.dart y el archivo user_provider.dart
```dart
import 'package:provider/provider.dart';
import 'package:state_management/providers/user_provider.dart';
```

Sobrescribir el método `initState` para pasar el nombre de usuario al **TextEditingController**, esto se hace para tener un valor por default en el campo de texto.
```dart
@override
void initState() {
  super.initState();

// Obtener el nombre de usuario fuera de una vista con
// context.read<UserProvider>().username
  _textEditingController =
      TextEditingController(text: context.read<UserProvider>().username);
}
```

Agregar una variable privada en el método `build` que contenga UserProvider
```dart
final _userProvider = Provider.of<UserProvider>(context);
```

Agregar el nombre de usuario al título del AppBar usando `_userProvider.username`
```dart
title: Text('User - ${_userProvider.username}'),
```

Llamar el método `onChange` de UserProvider al momento de hacer un cambio en el campo de texto.
```dart
onChanged: (value) {
  _userProvider.onChange(value);
},
```

------------

### catalog.dart

```dart
import 'package:provider/provider.dart';
import 'package:state_management/providers/catalog_provider.dart';

...

child: CircleAvatar(
  child: Text(
    '${context.watch<CatalogProvider>().catalog.length}',

...

@override
Widget build(BuildContext context) {
  final _catalogProvider =
      Provider.of<CatalogProvider>(context, listen: false);

...

trailing: IconButton(
  onPressed: () {
    if (items[index].addedToCart) {
      _catalogProvider.removeFromCatalog(items[index]);
    } else {
      _catalogProvider.addToCatalog(items[index]);
    }
```

**Explicación:**

Importar la librería provider y el archivo catalog_provider.dart
```dart
import 'package:provider/provider.dart';
import 'package:state_management/providers/catalog_provider.dart';
```

Actualizar el número de items en el AppBar utilizando `context.watch`
```dart
child: CircleAvatar(
  child: Text(
    '${context.watch<CatalogProvider>().catalog.length}',
```

Crear una variable privada de tipo provider, pero creada con el parámetro `listen` en false, ya que se va a utilizar para actualizar datos del provider no para obtenerlos.
```dart
@override
Widget build(BuildContext context) {
  final _catalogProvider =
      Provider.of<CatalogProvider>(context, listen: false);
```

Utilizar `_catalogProvider` para remover o agregar items al listado de items.
```dart
trailing: IconButton(
  onPressed: () {
    if (items[index].addedToCart) {
      _catalogProvider.removeFromCatalog(items[index]);
    } else {
      _catalogProvider.addToCatalog(items[index]);
    }
```

------------

### cart.dart

```dart
import 'package:state_management/providers/catalog_provider.dart';
import 'package:state_management/providers/user_provider.dart';
import 'package:provider/provider.dart';

...

@override
  Widget build(BuildContext context) {
    final _userProvider = Provider.of<UserProvider>(context);
    final _catalogProvider = Provider.of<CatalogProvider>(context);

...

child: ListView.builder(
  itemCount: _catalogProvider.catalog.length,
  itemBuilder: (context, index) => ListTile(
    title: Text(
      '${_catalogProvider.catalog[index].name}'.toUpperCase(),
      style: TextStyle(fontWeight: FontWeight.bold),
    ),
    trailing: Text(
      '\$${_catalogProvider.catalog[index].price.toStringAsFixed(2)}',
    ),
  ),
),

...

Column(
  children: [
    CircleAvatar(
      child: Text(
          getInitials(_userProvider.username).toUpperCase()),
    ),
    Text(
      '${_userProvider.username}',
      style: TextStyle(
        fontWeight: FontWeight.bold,
      ),
    ),
  ],
),

...

Text(
  '\$${formatTotal(_catalogProvider.catalog)}',
```

**Explicación:**

Importar la librería provider y los archivos catalog_provider.dart y user_provider.dart
```dart
import 'package:state_management/providers/catalog_provider.dart';
import 'package:state_management/providers/user_provider.dart';
import 'package:provider/provider.dart';
```

Crear dos variables privadas, para obtener los datos del provider de user y catalog.
```dart
@override
  Widget build(BuildContext context) {
    final _userProvider = Provider.of<UserProvider>(context);
    final _catalogProvider = Provider.of<CatalogProvider>(context);
```

Actualizar `ListView.builder`
```dart
child: ListView.builder(

// Actualizar el contador de items.
  itemCount: _catalogProvider.catalog.length,
  itemBuilder: (context, index) => ListTile(
    title: Text(

// Mostrar el nombre del item.
      '${_catalogProvider.catalog[index].name}'.toUpperCase(),
      style: TextStyle(fontWeight: FontWeight.bold),
    ),
    trailing: Text(

// Mostrar el precio del producto con dos decimales.
'\$${_catalogProvider.catalog[index].price.toStringAsFixed(2)}',
    ),
  ),
),
```

Actualizar el nombre de usuario.
```dart
Column(
  children: [
    CircleAvatar(
      child: Text(

// Mostrar las iniciales del nombre de usuario
          getInitials(_userProvider.username).toUpperCase()),
    ),
    Text(

// Mostrar el nombre de usuario
      '${_userProvider.username}',
      style: TextStyle(
        fontWeight: FontWeight.bold,
      ),
    ),
  ],
),
```

Actualizar el valor total de los items.
```dart
Text(
  '\$${formatTotal(_catalogProvider.catalog)}',
```

------------
------------

Eso es todo, recuerda que código de esta guía se lo puede encontrar en GitHub.

{% github marcedroid/Flutter-State-Management-Demos no-readme %}