Flutter - Injeção de dependência - I


Hoje veremos o conceito de injeção de dependência(ID) no Flutter e as abordagens para usar este recurso e obter um baixo acoplamento.

O que é o Flutter ?

O Flutter é um SDK de aplicativo móvel do Google que ajuda a criar aplicativos móveis modernos para iOS e Android usando uma única (quase) base de código.

Se você não conhece o Flutter veja o meu artigo :  Flutter - Primeiros contatos e impressões

Injeção de dependência no Flutter

Neste artigo vamos tratar das 3 formas mais usadas para realizar a injeção de dependência no Flutter :

  1. InheritedWidgets
  2. get_it
  3. provider

A injeção de dependência(DI) é um padrão de projeto cujo objetivo é manter um baixo acoplamento entre diferentes módulos de um sistema. Assim ao realizar a injeção de dependência estamos fornecendo aos objetos do nosso sistema outros objetos dos quais eles dependem com baixo acoplamento.

Nota:  Uma forma de aplicar a Injeção de Dependência é definir uma configuração usando um framework ou programa (container) que fica responsável por "injetar" em cada componente suas dependências declaradas.

Veja esse código:

class LoginService {
  Api api;  
}

Aqui vemos que LoginService depende do objeto api ou seja depende da classe Api.

A forma mais usual de obter essa dependência em uma classe é através do seu construtor injetando a instância no construtor:

class LoginService {
  Api api;  
  // Injeta a api no construtor
  LoginService(this.api);
}

Agora vamos pensar em termos de Widgets no Flutter. Considere um simples Statelesswidget que dependa de uma instância de uma classe Produto.

Podemos, da mesma forma injetar essa instância no construtor do widget:

import 'package:flutter/material.dart';
import 'model/produto.dart';
class HomePage extends StatelessWidget {
  final Produto produto;
  HomePage(this.produto)
  @override
  Widget build(BuildContext context) {
    return Container(      
    );
  }
}

Então tudo certo !!!!

Calma, no Flutter o problema é um pouco mais complexo...

Devemos lembrar que no Flutter tudo são Widgets, e que as aplicações são construidas usando árvores de widgets onde temos uma hierarquia de diversos níveis de widgets.

Assim, passar dependências através de um construtor é perfeitamente adequado para acessar dados um nível abaixo, talvez até dois níveis.

E se você tiver quatro níveis na árvore de widgets e precisar dos dados de um objeto no seu código ?

Agora imagine que todos esses widgets são widgets em arquivos separados com sua própria lógica:

Complicou , não é mesmo !!!!

Agora se você quiser  passar o Produto para o MeuText para exibir a informações do produto, seria necessário criar e adicionar o construtor e manter a variável de membro local apenas para passar para o próximo construtor do widget.

Imagine agora, e se você trocar o widget MeuText por um outro widget de pai diferente. Você precisaria remover todo o código desnecessário e adicioná-lo a todos os novos pais dos widgets que exigem seus dados.

Deu para sentir o drama ???

É por isso que usamos a injeção de dependência, para garantir que se os dados forem necessários em qualquer lugar da árvore de widgets, por qualquer widget, poderemos recuperá-los facilmente.

Vamos começar com o InheritedWidget.

1- Injeção de dependência com InheritedWidget

A classe InheritedWidget é uma classe Base para widgets que propagam informações com eficiência na árvore de widgets.

De forma bem simples, um widget herdado permite efetivamente fornecer acesso, através do BuildContext, a todas as suas propriedades, a todos os widgets da subárvore.

É muito comum no Flutter e é usado para o Theme, MediaQueries e tudo o mais que o aplicativo base fornece.

Se você já usou o Flutter antes, provavelmente já encontrou o método 'of' em diferentes classes aqui e ali, em um código parecido com esse abaixo:

Theme.of (context) .textTheme
MediaQuery.of (context) .size

 

Esses widgets (Theme, MediaQuery) são widgets herdados. Em praticamente qualquer lugar do seu aplicativo, você pode acessar o seu tema, porque ele é herdado.

A seguir temos um esqueleto de um InheritedWidget chamado MeuWidget:

class MeuWidget extends InheritedWidget {
  MeuWidget({Key key, this.child}) : super(key: key, child: child);
  final Widget child;

  static MeuWidget of(BuildContext context) {
     return (context.inheritFromWidgetOfExactType(MeuWidget)as MeuWidget);
  }
 @override
   bool updateShouldNotify( MeuWidget oldWidget) {
     return true;
   }
}

Nota: Para criar o código acima no VS Code instale a extensão Awesome Flutter Snippets e digite : inheritedW

Agora vamos adicionar a referência ao nosso Produto como uma propriedade no Widget. Manteremos uma instância final de longa duração e a retornaremos por meio de um getter :

class MeuWidget extends InheritedWidget {
  MeuWidget({Key key, this.child}) : super(key: key, child: child);
  final Widget child;
  final Produto _produto = Produto();
  Produto get produto => _produto;
  static MeuWidget of(BuildContext context) {
     return (context.inheritFromWidgetOfExactType(MeuWidget)as MeuWidget);
  }
 @override
   bool updateShouldNotify( MeuWidget oldWidget) {
     return true;
   }
}

Pronto ! já temos um widget herdado.

Agora vejamos como usar o widget MeuWidget que é herda de InheritedWidget.

A maneira como os widgets herdados são usados é envolvendo a árvore que você deseja no widget herdado.

Se quisermos que esse widget seja fornecido a todo o nosso aplicativo, basta envolver o MaterialApp com o widget MeuWidget.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MeuWidget(
      child: MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: HomeView()),
    );
  }
}

Simples...

Agora como podemos acessar o nosso widget MeuWidget ?

A forma como acessamos o widget herdado em nosso código é usando a chamada .of e passando o contexto. Podemos atualizar nosso widget HomePage e remover o Produto passado pelo construtor, bem como a variável de classe que mantivemos.

Agora podemos fazer com que nosso HomePage fique assim :

import 'package:flutter/material.dart';
import 'model/produto.dart';
class HomePage extends StatelessWidget {
  HomePage()
  @override
  Widget build(BuildContext context) {   
       var produto = MeuWidget.of(context).produto;
    return Container(      
    );
  }
}

Assim, em qualquer lugar do aplicativo em que você deseja usar o objeto Produto, tudo o que você fará é declarar:

var produto = MeuWidget.of (context).produto;

Maravilha...

A favor desta abordagem temos que ela força um fluxo de dados direcional e usa a abordagem de construção do Flutter.

Como desvantagem temos que ela é um tanto verbosa (muito código) e além disso qualquer widget descendente de InheritedWidget deve ser imutável, o que significa que você não pode alterar seus dados, mas deve criar uma nova instância com novos dados. Para poder fazer isso dentro da árvore de widgets, você sempre precisa envolvê-lo em um StatefulWidget.

Outro fator importante é que se um InheritedWidget sofrer uma alteração, não somente os Widgets que fazem referência a ele serão atualizados,  mas o contexto circundante, oque significa que esse contexto completo, incluindo todos os seus filhos, será reconstruído. Ou seja, se você colocar o seu widget herdado na base da sua árvore, ele reconstruirá a árvore inteira.

Para minimizar esse trabalho de reconstrução você teria que adicionar diferentes widgets herdados em diferentes locais da sua árvore.

Na próxima parte do artigo veremos a injeção de dependência usando o get_it.

"Porque Deus não nos destinou para a ira, mas para a aquisição da salvação, por nosso Senhor Jesus Cristo,
Que morreu por nós, para que, quer vigiemos, quer durmamos, vivamos juntamente com ele."

1 Tessalonicenses 5:9,10

Referências:


José Carlos Macoratti