Skip to content

Latest commit

 

History

History
656 lines (473 loc) · 22 KB

README.md

File metadata and controls

656 lines (473 loc) · 22 KB

English | French | Português | 简体中文 | Español | 한국어 | বাংলা | 日本語 | Turkish | Italian

Build Status pub package codecov Gitter

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.

Migrando de 4.x.x para 5.0.0-nullsafety

  • initialData para FutureProvider e StreamProvider é 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 removido

    Para migrar, você pode usar Provider combinado com ValueListenableBuilder:

    ValueListenableBuilder<int>(
      valueListenable: myValueListenable,
      builder: (context, value, _) {
        return Provider<int>.value(
          value: value,
          child: MyApp(),
        );
      }
    )

Uso

Expondo um valor

Expondo a instância de um novo objeto

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,
)

Reutilizar uma instância de um objeto existente.

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: ...
)
MyChangeNotifier variable;

ChangeNotifierProvider(
  create: (_) => variable,
  child: ...
)

Lendo um valor

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 em T
  • context.read<T>(), retorna T sem escutar
  • context.select<T, R>(R cb(T value)), permite o widget escutar apenas uma pequena parte de T

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.

MultiProvider

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.

ProxyProvider

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 vs ProxyProvider2 vs ProxyProvider3, ...

    O digito depois da classe é o número de outros providers que no qual o ProxyProvider depende.

  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider, ...

    Funcionam da mesma maneira, mas ao invés de enviar o resultado para um Provider, o ChangeNotifierProxyProvider envia o valor para ChangeNotifierProvider.

FAQ

Posso inspecionar o conteúdo dos meus objetos?

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)

O devtool só mostra "Instance of MyClass". O que fazer?

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)';
      }
    }

Recebo uma exceção quando obtenho Providers dentro de um initState. O que fazer?

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.

Como lidar com hot-reload nos meus objetos?

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.

Posso criar o meu 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 o MultiProvider. Esta interface é exposta como porte do package:provider/single_child_widget

  • InheritedProvider, o InheritedWidget generico é obtido usando o Provider.of.

Aqui está um exempo de um provider cutomizado que usa o ValueNotifier como estado: https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91

Meu widget é reconstruído com frequência, o que posso fazer?

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.

Posso obter dois providers diferentes usando o mesmo tipo?

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: ...,
  ),
),

Posso consumir uma interface e fazer a implementação?

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(),
),

Providers existentes

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.