DEV Community

Marcelo Sebastián
Marcelo Sebastián

Posted on

Manejo de estados en flutter, Provider

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

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

GitHub logo marcedroid / Flutter-State-Management-Demos

Manejo de estados en Flutter

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

https://pub.dev/packages/provider

Para este ejemplo se ha utilizado la versión 4.3.2+2
pubspec.yaml

dependencies:
  provider: ^4.3.2+2

Provider para nombre de usuario

user_provider.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

import 'package:flutter/material.dart';

Crear la clase UserProvider y utilizar como mixin la clase ChangeNotifier

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.

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.

void onChange(value) {
  _username = value;

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

Provider para catálogo

catalog_provider.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.

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

Crear la clase CatalogProvider y utilizar como mixin la clase ChangeNotifier

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.

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.

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.

void removeFromCatalog(ItemModel itemModel) {
  _catalog.remove(itemModel);

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

Modificar main.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.

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.

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

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

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.

@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

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

Agregar el nombre de usuario al título del AppBar usando _userProvider.username

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

Llamar el método onChange de UserProvider al momento de hacer un cambio en el campo de texto.

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

catalog.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

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

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.

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

Utilizar _catalogProvider para remover o agregar items al listado de items.

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

cart.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

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.

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

Actualizar ListView.builder

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.

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.

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


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

GitHub logo marcedroid / Flutter-State-Management-Demos

Manejo de estados en Flutter

Top comments (0)