Cómo usar el patrón de proveedor en Flutter

En esta publicación, veremos el patrón de proveedor en Flutter. Algunos otros patrones, como BLoC Architecture, utilizan el patrón de proveedor internamente. Pero el patrón de proveedor es mucho más fácil de aprender y tiene mucho menos código repetitivo.

En esta publicación, tomaremos la aplicación Counter predeterminada proporcionada por Flutter y la refactorizaremos para usar el patrón del proveedor.

Si desea saber qué tiene que decir el equipo de Flutter en Google sobre el patrón de proveedores, consulte esta charla de 2019.

Si desea obtener más información sobre la arquitectura BLoC, consulte aquí.

Empezando

Crea un nuevo proyecto de Flutter y llámalo como quieras.

Primero, debemos eliminar todos los comentarios para que tengamos una pizarra limpia para trabajar:

import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Ahora agregue la dependencia para el patrón de proveedor en el pubspec.yamlarchivo. En el momento de redactar este informe, la última versión es 4.1.2.

Así es como pubspec.yamlse verá su archivo ahora:

name: provider_pattern_explained description: A new Flutter project. publish_to: 'none' version: 1.0.0+1 environment: sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter provider: ^4.1.2 cupertino_icons: ^0.1.3 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true 

La aplicación predeterminada es básicamente un widget con estado que se reconstruye cada vez que hace clic en FloatingActionButton(que llama setState()).

Pero ahora lo vamos a convertir en un widget sin estado.

Creando el Proveedor

Sigamos adelante y creemos nuestro proveedor. Esta será la única fuente de verdad para nuestra aplicación. Aquí es donde almacenaremos nuestro estado, que en este caso es el recuento actual.

Cree una clase nombrada Countery agregue la countvariable:

import 'package:flutter/material.dart'; class Counter { var _count = 0; } 

Para convertirlo en una clase de proveedor, amplíe ChangeNotifierel material.dartpaquete. Esto nos proporciona el notifyListeners()método y notificará a todos los oyentes cada vez que cambiemos un valor.

Ahora agregue un método para incrementar el contador:

import 'package:flutter/material.dart'; class Counter extends ChangeNotifier { var _count = 0; void incrementCounter() { _count += 1; } } 

Al final de este método, llamaremos notifyListeners(). Esto activará un cambio en toda la aplicación para cualquier widget que lo esté escuchando.

Esa es la belleza del patrón de proveedor en Flutter: no tiene que preocuparse por enviar manualmente a las transmisiones.

Finalmente, cree un captador para devolver el valor del contador. Usaremos esto para mostrar el último valor:

import 'package:flutter/material.dart'; class Counter extends ChangeNotifier { var _count = 0; int get getCounter { return _count; } void incrementCounter() { _count += 1; notifyListeners(); } } 

Escuchar los clics de los botones

Ahora que tenemos el proveedor configurado, podemos seguir adelante y usarlo en nuestro widget principal.

Primero, vamos a convertir MyHomePagea un widget sin estado en lugar de uno con estado. Tendremos que eliminar la setState()llamada ya que solo está disponible en StatefulWidget:

import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatelessWidget { int _counter = 0; final String title; MyHomePage({this.title}); void _incrementCounter() {} @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Una vez hecho esto, ahora podemos usar el patrón de proveedor en Flutter para establecer y obtener el valor del contador. En cada clic de botón, debemos incrementar el valor del contador en 1.

Entonces, en el _incrementCountermétodo (que se llama cuando se presiona el botón) agregue esta línea:

Provider.of(context, listen: false).incrementCounter();

Lo que está sucediendo aquí es que le has pedido a Flutter que suba al árbol de widgets y busque el primer lugar donde Counterse proporciona. (Te diré cómo proporcionarlo en la siguiente sección). Esto es lo que Provider.of()hace.

Los genéricos (valores dentro corchetes) le dicen a Flutter qué tipo de proveedor debe buscar. Luego, Flutter sube por el árbol de widgets hasta que encuentra el valor proporcionado. Si el valor no se proporciona en ninguna parte, se lanza una excepción.

Finalmente, una vez que tenga el proveedor, puede llamar a cualquier método. Aquí llamamos a nuestro incrementCountermétodo.

Pero también necesitamos un contexto, por lo que aceptamos el contexto como un argumento y modificamos el onPressedmétodo para pasar el contexto también:

void _incrementCounter(BuildContext context) { Provider.of(context, listen: false).incrementCounter(); } 

Nota: Hemos configurado listen como falso porque no necesitamos escuchar ningún valor aquí. Solo estamos enviando una acción para realizar.

Proporcionar al proveedor

El patrón de proveedor en Flutter buscará el último valor proporcionado. El siguiente diagrama le ayudará a comprender mejor.

En este diagrama, el objeto VERDE A estará disponible para el resto de los elementos debajo de él, es decir , B, C, D, E y F.

Ahora suponga que queremos agregar alguna funcionalidad a la aplicación y creamos otro proveedor, Z. Z es requerido por E y F.

Entonces, ¿cuál es el mejor lugar para agregar eso?

Podemos añadir a la raíz por encima de una . Esto funcionaría:

Pero este método no es muy eficaz.

Flutter revisará todos los widgets anteriores y finalmente irá a la raíz. Si tiene árboles de widgets muy largos, lo que definitivamente tendrá en una aplicación de producción, entonces no es una buena idea poner todo en la raíz.

En cambio, podemos mirar el denominador común de E y F. Eso es C. Entonces, si ponemos Z justo encima de E y F, funcionaría.

But what if we want to add another object X that'srequired by E and F? We'll do the same thing. But notice how the tree keeps growing.

There’s a better way to manage that. What if we provide all the objects at one level?

This is perfect, and is how we’ll eventually implement our provider pattern in Flutter. We’ll make use of something called MultiProviderwhich lets us declare multiple providers at one level.

We'll get MultiProvider to wrap the MaterialApp widget:

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider.value( value: Counter(), ), ], child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: "AndroidVille Provider Pattern"), ), ); } } 

With this, we’ve provided the provider to our widget tree and can use it anywhere below this level in the tree.

There’s just one more thing left: we need to update the value that's displayed.

Updating the text

To update the text, get the provider in the build function of your MyHomePage widget. We’ll use the getter we created to get the latest value.

Then just add this value to the text widget below.

And we’re done! This is how your final main.dart file should look:

import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:provider_pattern_explained/counter.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider.value( value: Counter(), ), ], child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: "AndroidVille Provider Pattern"), ), ); } } class MyHomePage extends StatelessWidget { final String title; MyHomePage({this.title}); void _incrementCounter(BuildContext context) { Provider.of(context, listen: false).incrementCounter(); } @override Widget build(BuildContext context) { var counter = Provider.of(context).getCounter; return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => _incrementCounter(context), tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Note: We haven’t set listen:false in this case because we want to listen to any updates in the count value.

Here's the source code on GitHub if you want to have a look: //github.com/Ayusch/Flutter-Provider-Pattern.

Let me know if you have any issues.

Welcome to AndroidVille :)

AndroidVille is a community of Mobile Developers where we share knowledge related to Android Development, Flutter Development, React Native Tutorials, Java, Kotlin and much more.

Click on this link to join the AndroidVille SLACK workspace. It’s absolutely free!

If you liked this article, feel free to share it on Facebook or LinkedIn. You can follow me on LinkedIn, Twitter, Quora, and Medium where I answer questions related to mobile development, Android, and Flutter.