Artigo original: How to Use the Provider Pattern in Flutter
Neste artigo, vamos conferir o padrão provider no Flutter. Alguns outros padrões, como a Arquitetura BLoC, usam o padrão provider internamente. Ele, no entanto, é muito fácil de aprender e possui muito menos código repetitivo.
Neste artigo, vamos usar o aplicativo padrão de Contador fornecido pelo Flutter e refatorá-lo para que use o padrão provider.
Se quiser saber o que a equipe do Flutter tem a dizer sobre o padrão provider, confira essa palestra de 2019 na sede do Google (vídeo em inglês).
Se quiser aprender mais sobre a arquitetura BLoC, confira aqui (texto em inglês).
Começando
Crie um projeto do Flutter e coloque nele o nome que quiser.
Primeiro, precisamos remover todos os comentários. Assim, teremos um código mais limpo para trabalhar:
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<MyHomePage> {
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: <Widget>[
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),
),
);
}
}
Agora, adicionamos a dependência para o padrão provider no arquivo pubspec.yaml
. Atualmente, a versão mais recente é a 4.1.2.
O arquivo pubspec.yaml
ficará assim:
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
O aplicativo padrão é basicamente um widget com monitoramento de estado, que é criado novamente toda vez que você clica em FloatingActionButton
(que, por sua vez, chama setState()
).
Agora, vamos convertê-lo em um widget sem monitoramento de estado.
Criando o Provider
Vamos criar nosso provider. Ele será a fonte da verdade do nosso aplicativo. É onde armazenaremos nosso state (estado) que, neste caso, é a contagem atual.
Crie uma classe chamada Counter
e adicione a variável count
:
import 'package:flutter/material.dart';
class Counter {
var _count = 0;
}
Para convertê-la em uma classe provider, estenda ChangeNotifier
, do pacote material.dart
. Isso nos fornece o método notifyListeners()
e notificará todas as funções ouvintes quando mudarmos um valor.
Depois, adicione um método para incrementar o contador:
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier {
var _count = 0;
void incrementCounter() {
_count += 1;
}
}
No fim desse método, chamaremos notifyListeners()
. Isso acionará uma mudança em todo o aplicativo para qualquer widget que esteja ouvindo.
Esta é a beleza do padrão provider no Flutter – você não precisa se importar com despachar manualmente para streams.
Por fim, crie uma função getter para retornar o valor do contador. Vamos usá-la para mostrar o valor mais atual:
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier {
var _count = 0;
int get getCounter {
return _count;
}
void incrementCounter() {
_count += 1;
notifyListeners();
}
}
Ouvindo cliques no botão
Agora que temos o provider configurado, podemos continuar e usá-lo em nosso widget principal.
Primeiramente, vamos converter MyHomePage
para um widget sem monitoramento de estado. Vamos ter que remover a chamada para a função setState()
, pois ela está disponível apenas em um StatefulWidget
(widget com monitoramento de estado):
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: <Widget>[
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),
),
);
}
}
Com isso feito, podemos usar o padrão provider no Flutter para indicar e obter o valor do contador. A cada clique no botão, precisamos incrementar o valor do contador em 1.
Então, no método _incrementCounter
(que é chamado quando o botão é pressionado), adicione esta linha:
Provider.of<Counter>(context, listen: false).incrementCounter();
O que acontece aqui? Você pediu para o Flutter subir na árvore de widget e encontrar o primeiro lugar onde Counter
é fornecido (mostrarei como fazer isso na próxima seção). É isso que Provider.of()
faz.
Os genéricos (valores dentro dos <>) dizem ao Flutter qual tipo de provider procurar. Então, o Flutter sobe na árvore do widget até encontrar o valor fornecido. Se o valor não estiver fornecido em nenhum lugar, então uma exceção é criada.
Finalmente, quando você já tem o provider, pode chamar qualquer método a partir dele. Aqui, chamamos nosso método de incrementCounter
.
Também precisamos de um contexto (em inglês, context). Então, aceitamos context
como argumento e modificamos o método onPressed
para que também passe context:
void _incrementCounter(BuildContext context) {
Provider.of<Counter>(context, listen: false).incrementCounter();
}
Observação: indicamos listen como falso, pois não precisamos escutar nenhum valor aqui. Estamos apenas despachando uma ação que será executada.
Fornecendo o Provider
O padrão provider no Flutter procurará o último valor fornecido. O diagrama abaixo ajudará você a entender melhor.

Neste diagrama, o objeto A VERDE estará disponível para o resto dos elementos abaixo dele, que são B, C, D, E, e F.
Agora, suponha que queremos adicionar funcionalidade ao aplicativo e criamos outro provider, Z. Z precisa ser usado por E e F.
Onde é o melhor lugar para adicioná-lo?
Podemos adicioná-lo na raiz abaixo de A. Ficaria assim:

Esse método, porém, não é muito eficiente.
O Flutter passará por todos os widgets abaixo e, então, finalmente, subirá até a raiz. Se você possui árvores de widget muito longas – algo que você provavelmente terá em um aplicativo em produção – não é uma boa ideia colocar tudo na raiz.
Ao invés disso, podemos olhar para o denominador comum de E e F, que é C. Se colocamos Z abaixo de E e F, dará certo.

Se quisermos adicionar outro objeto X, requerido por E e F? Faremos a mesma coisa. Note, porém, como a árvore continua crescendo.

Há uma maneira melhor de gerenciar isso. Que tal se providenciarmos todos os objetos em um nível?

Isso é perfeito. Será a maneira de implementarmos nosso padrão provider no Flutter. Usaremos algo chamado MultiProvider
, que nos deixa declarar diversos providers em um nível.
Deixaremos MultiProvider
cercar o widget MaterialApp
:
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"),
),
);
}
}
Com isso, fornecemos o provider para nossa árvore de widget e podemos usá-lo em qualquer lugar abaixo deste nível da árvore.
Há apenas mais uma coisa a fazer: precisamos atualizar o valor que é mostrado.
Atualizando o texto
Para atualizar o texto, obtenha o provider na função do widget MyHomePage
. Vamos usar o getter que criamos para obter o valor mais recente.
Então, simplesmente adicione esse valor ao widget de texto abaixo.
Terminamos! Nosso arquivo main.dart
ficará assim no final:
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<Counter>(context, listen: false).incrementCounter();
}
@override
Widget build(BuildContext context) {
var counter = Provider.of<Counter>(context).getCounter;
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
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),
),
);
}
}
Observação: não definimos listen:false
nesse caso, pois queremos ficar sabendo de qualquer atualização no valor do contador.
Aqui está o código fonte no GitHub se você quiser ver: https://github.com/Ayusch/Flutter-Provider-Pattern.
Peço que informe se tiver qualquer problema.
Boas-vindas à AndroidVille :)
A AndroidVille é uma comunidade de desenvolvedores para dispositivos móveis onde compartilhamos conhecimento relacionado com o desenvolvimento para Android, desenvolvimento em Flutter, tutoriais sobre React Native, Java, Kotlin e muito mais.
Clique neste link para juntar-se ao SLACK do AndroidVille. É totalmente gratuito!
Se você gostou deste artigo, fique à vontade para compartilhá-lo no Facebook ou LinkedIn. Você pode seguir o autor no LinkedIn, no Twitter, no Quora e no Medium, onde ele responde perguntas relacionadas ao desenvolvimento para dispositivos móveis, Android e Flutter.