English | French | Português | 简体中文 | Español | 한국어 | বাংলা | 日本語 | Turkish | Italian
Um wrapper para o InheritedWidget tornando-os mais fáceis de usar e reutilizáveis.
Usando o provider
ao invés de usar InheritedWidget, você ganha:
- alocação/disposição simplificada de recursos
- lazy-loading
- redução considerável de código desnecessário toda vez que se cria uma class
- compatível com devtools
- uma maneira comum de consumir InheritedWidgets (consulte Provider.of/Consumer/Selector)
- maior escalabilidade para classes com um mecanismo de escuta que cresce exponencialmente em complexidade (como ChangeNotifier, que é O(N) para notificações de despacho).
Para ler mais sobre o provider
, veja a documentação.
-
initialData
paraFutureProvider
eStreamProvider
é mandatório.Para migrar, o que antes era:
FutureProvider<int>( create: (context) => Future.value(42), child: MyApp(), ) Widget build(BuildContext context) { final value = context.watch<int>(); return Text('$value'); }
E agora:
FutureProvider<int?>( initialValue: null, create: (context) => Future.value(42), child: MyApp(), ) Widget build(BuildContext context) { // be sure to specify the ? in watch<int?> final value = context.watch<int?>(); return Text('$value'); }
-
ValueListenableProvider
foi removidoPara migrar, você pode usar
Provider
combinado comValueListenableBuilder
:ValueListenableBuilder<int>( valueListenable: myValueListenable, builder: (context, value, _) { return Provider<int>.value( value: value, child: MyApp(), ); } )
Providers permitem não apenas expor um valor, mas também criar/escutar/descartar os mesmos.
Para expor um obejto criado, use o construtor padrão do provider.
Não use .value
se quiser criar um objeto ou terá efeitos indesejados.
Veja esta resposta no stackoverflow
que explica em detalhes porque usar o construtor .value
não é recomendado.
- CRIE um novo objeto dentro de
create
.
Provider(
create: (_) => MyModel(),
child: ...
)
- NÃO use
Provider.value
para criar o seu objeto.
ChangeNotifierProvider.value(
value: MyModel(),
child: ...
)
-
NÃO crie seu objeto de variáveis que podem mudar com o tempo.
Nestas situações, seu objeto nunca poderá ser atualizado quando um valor for alterado.
int count;
Provider(
create: (_) => MyModel(count),
child: ...
)
Se você quiser passar variáveis que pode mudar para seu objeto, use o ProxyProvider
:
int count;
ProxyProvider0(
update: (_, __) => MyModel(count),
child: ...
)
NOTA:
Quando usar o callback create
/update
de um provider, é importante salientar que este callback é chamado de forma lazy por padrão.
Isto significa que, até que o valor seja solicitado no mínimo uma vez, o create
/update
não será chamado.
Este comportamento pode ser desativado se você quiser computar previamente com alguma lógica, usando o parâmetro 'lazy':
MyProvider(
create: (_) => Something(),
lazy: false,
)
Se você já possui uma instância de um objeto e deseja expô-la, você deve usar o construtor .value
do provider.
Se não o fizer, o método dispose
poderá ser chamado mesmo que ainda esteja em uso.
- USE
ChangeNotifierProvider.value
para prover um ChangeNotifier existente
MyChangeNotifier variable;
ChangeNotifierProvider.value(
value: variable,
child: ...
)
- NÃO reuse um ChangeNotifier existente com o construtor padrão
MyChangeNotifier variable;
ChangeNotifierProvider(
create: (_) => variable,
child: ...
)
A forma mais fácil de ler um valor é usando os métodos extensões do [BuildContext]:
context.watch<T>()
, faz o widget escutar mudanças emT
context.read<T>()
, retornaT
sem escutarcontext.select<T, R>(R cb(T value))
, permite o widget escutar apenas uma pequena parte deT
Ou use o método estático Provider.of<T>(context)
, que é semelhante ao watch
e qunado passado false
para o parâmetro listen
como Provider.of<T>(context,listen: false)
se comporta de maneira similar ao read
.
É importante notar que context.read<T>()
não fará o widget reconstruir quando o valor for alterado e não podee estar dentro de StatelessWidget.build
/State.build
. Porém pode ser chamado fora destes métodos.
Esse método irá olhar na árvore de widgets acima começando pelo widget associado
ao BuildContext
passado e retornará a variável mais próxima do tipo
T
que foi encontrada (ou lançará uma exceção se nada for encontrado).
É importante notar que esta operação é O(1). Não envolve caminhar pela árvore de widget.
Combinado com o primeiro exemplo de expondo um valor, esse
widget irá ler a variável String
exposta e renderizar "Hello World."
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
// Don't forget to pass the type of the object you want to obtain to `watch`!
context.watch<String>(),
);
}
}
Ao invés de usar Provider.of
podemos usar Consumer e Selector.
Isto poder ser útil para performance ou quando for difícil de obter um descendente BuildContext
do provider.
Veja o FAQ ou a documentação do Consumer e Selector para mais informações.
Quando injetando vários valores em grandes aplicações, Provider
pode rapidamente ficar complexo com vários descendentes:
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
Para:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
O comportamento de ambos é exatamente o mesmo. MultiProvider
apenas muda o visual do código.
Desde o 3.0.0, há um novo tipo de provider: ProxyProvider
.
ProxyProvider
é um provider que combina vários valores de outros providers em um novo objeto e envia o resultado para o Provider
.
Este novo objeto irá ser atualizado quando o provider que depende dele for atualizado.
O exemplo abaixo usa ProxyProvider
para construir translações com base no contador de outro provider.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ProxyProvider<Counter, Translations>(
update: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
Vem com outras opções tais como:
-
ProxyProvider
vsProxyProvider2
vsProxyProvider3
, ...O digito depois da classe é o número de outros providers que no qual o
ProxyProvider
depende. -
ProxyProvider
vsChangeNotifierProxyProvider
vsListenableProxyProvider
, ...Funcionam da mesma maneira, mas ao invés de enviar o resultado para um
Provider
, oChangeNotifierProxyProvider
envia o valor paraChangeNotifierProvider
.
Flutter vem com devtool que mostra a árvore de widgets do momento.
Como os providers são widgets, eles também são visíveis no devtool:
Daqui, se clicar em um provider, você verá o valor que ele expõe:
(screenshot do devtools usando a pasta example
)
Por padrão, o devtool usa toString
, que tem como padrão "Instance of MyClass".
Para algo mais útil, existem duas soluções:
-
use API Diagnosticable do Flutter
Para a maioria dos casos, isso é feito usando DiagnosticableTreeMixin nos seus objetos, seguido de uma implementação customizada de debugFillProperties.
class MyClass with DiagnosticableTreeMixin { MyClass({this.a, this.b}); final int a; final String b; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); // list all the properties of your class here. // See the documentation of debugFillProperties for more information. properties.add(IntProperty('a', a)); properties.add(StringProperty('b', b)); } }
-
sobrescreva
toString
.Se não for possbile usar DiagnosticableTreeMixin (como se sua classe estiver em um pacote que não depende do Flutter), você pode simplesmente sobrescrever
toString
. Isto é mais fácil que usar DiagnosticableTreeMixin mas menos poderoso: Não será possível expandir/colapsar os detalhes de seus objetos.class MyClass with DiagnosticableTreeMixin { MyClass({this.a, this.b}); final int a; final String b; @override String toString() { return '$runtimeType(a: $a, b: $b)'; } }
Esta exceção acontece pois você está tentando escutar um provider de um ciclo-de-vida que nunca será chamado de novo.
Significa que você precisa usar um outro ciclo-de-vida (build
), ou explicitamente especificar que você nao importa com atualizações.
Como, ao invés de:
initState() {
super.initState();
print(context.watch<Foo>().value);
}
você pode:
Value value;
Widget build(BuildContext context) {
final value = context.watch<Foo>().value;
if (value != this.value) {
this.value = value;
print(value);
}
}
que ira imprimir value
toda vez que mudar (e somente quando mudar).
Você também pode:
initState() {
super.initState();
print(context.read<Foo>().value);
}
Que vai mostrar value
uma vez e ignorar atualizações.
Você pode fazer seu objeto provider implementar ReassembleHandler
:
class Example extends ChangeNotifier implements ReassembleHandler {
@override
void reassemble() {
print('Did hot-reload');
}
}
E usar normalmente com provider
:
ChangeNotifierProvider(create: (_) => Example()),
Eu uso ChangeNotifier e recebo uma exceção quando o atualizo, o que está acontecendo?
Possivelmente você esta tentando modificar o ChangeNotifier de um de seus descendentes enquanto a árvore de widgets está sendo construída.
Uma situação comum é quando se inicia uma solicitação http, onde o futuro é armazenado em um notifier:
initState() {
super.initState();
context.read<MyNotifier>().fetchSomething();
}
Isto não é permitido, pois a modificação precisa ser imediata.
O que significa que alguns widgets podem ser construídos antes da mutação acontecer (recebendo um valor antigo), equanto outros serão construídos depois da mutação se completar (recebendo um novo valor). Isto pode gerar inconsistências na sua interface gráfica e por isso não é permitido.
Ao invés disso, você pode fazer a mutação em algum lugar que afeta toda a árvore igualmente:
-
diretamente dentro de
create
do seu provider/construtor do seu modelo:class MyNotifier with ChangeNotifier { MyNotifier() { _fetchSomething(); } Future<void> _fetchSomething() async {} }
Isto pode ser útil quando nao há 'parametro externo'.
-
de forma assíncrona no final de um frame:
initState() { super.initState(); Future.microtask(() => context.read<MyNotifier>(context).fetchSomething(someValue); ); }
É menos ideal, mas permite passar parâmetros para a mutação.
Preciso usar ChangeNotifier for complex states?
Não.
Você pode utilizar qualquer objeto para representar o seu estado. Por exemplo, uma arquitetura
alternativa é usar o Provider.value()
combinado com um StatefulWidget
.
Aqui está um exemplo de contador usando essa arquitetura:
class Example extends StatefulWidget {
const Example({Key key, this.child}) : super(key: key);
final Widget child;
@override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this,
child: widget.child,
),
);
}
}
Onde podemos ler o estado usando:
return Text(context.watch<int>().toString());
e modificar o estado com:
return FloatingActionButton(
onPressed: () => context.read<ExampleState>().increment(),
child: Icon(Icons.plus_one),
);
Alternativamente, você pode criar o seu próprio provider.
Sim. O provider
expõe todos os pequenos componentes que tornam um provider completo.
Isso inclui:
-
SingleChildCloneableWidget
, para fazer com que qualquer widget funcione com oMultiProvider
. Esta interface é exposta como porte dopackage:provider/single_child_widget
-
InheritedProvider, o
InheritedWidget
generico é obtido usando oProvider.of
.
Aqui está um exempo de um provider cutomizado que usa o ValueNotifier
como estado:
https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91
Ao invés de usar context.watch
, pode usar context.select
para escutar algumas partes específicasa do objeto.
Por exemplo:
Widget build(BuildContext context) {
final person = context.watch<Person>();
return Text(person.name);
}
Fará o widget reconstruir quando algo que não seja 'nome' mudar.
Ao invés, você pode usar context.select
para escutar somente a propriedade name
:
Widget build(BuildContext context) {
final name = context.select((Person p) => p.name);
return Text(name);
}
Desta forma, o widget não ira necessariamente reconstruir se algo que não seja name
mudar.
De forma parecida, você pode usar Consumer/Selector.
O argumento opcional child
permite a reconstrução de uma parte específica da árvore de widgets:
Foo(
child: Consumer<A>(
builder: (_, a, child) {
return Bar(a: a, child: child);
},
child: Baz(),
),
)
Nesse exemplo, somente Bar
será reconstruído quando A
for atualizado. Foo
e Baz
não
serão reconstruídos desnecessariamente.
Não. Embora você possa ter vários providers compartilhando o mesmo tipo, um widget só ira conseguir obter apenas um deles: o ancestral mais próximo.
Ao invés disso, você deve dar explicitamente tipos diferentes a ambos providers.
Em vez de:
Provider<String>(
create: (_) => 'England',
child: Provider<String>(
create: (_) => 'London',
child: ...,
),
),
Prefira:
Provider<Country>(
create: (_) => Country('England'),
child: Provider<City>(
create: (_) => City('London'),
child: ...,
),
),
Sim, uma dica de tipo tem que ser especificada para o compilador para indicar que a interface será consumida, com a implmentação do create.
abstract class ProviderInterface with ChangeNotifier {
...
}
class ProviderImplementation with ChangeNotifier implements ProviderInterface {
...
}
class Foo extends StatelessWidget {
@override
build(context) {
final provider = Provider.of<ProviderInterface>(context);
return ...
}
}
ChangeNotifierProvider<ProviderInterface>(
create: (_) => ProviderImplementation(),
child: Foo(),
),
O provider
expõe alguns diferentes tipos de "provider" para diferentes tipos de objetos.
A lista completa de todos os objetos disponiveis está aqui
nome | descrição |
---|---|
Provider | A forma mais básica de provider. Ele pega um valor e o expõe, qualquer que seja o valor. |
ListenableProvider | Um provider especifico para objetos que possam ser ouvidos. O ListenableProvider irá ouvir o objetor e pedir para que os widgets que dependam dele sejam reconstruídos sempre que o ouvinte for chamado. |
ChangeNotifierProvider | Uma especificação do ListenableProvider para ChangeNotifier. Ele chama automaticamente o ChangeNotifier.dispose quando preciso. |
ValueListenableProvider | Escuta um ValueListenable e apenas expoe o ValueListenable.value . |
StreamProvider | Escuta uma Stream e expoe o ultimo valor emitido. |
FutureProvider | Recebe um Future e atualiza os depedentes quando o future for atualizado. |