Dependence managment from Flutter

Related tags

Templates injector_x
Overview

InjectorX

Dependence managment from Flutter

A ideia do InjectorX surgiu para facilitar o controle e a manutenção das injeções de dependências em um projeto flutter com o Clean Architecture. A principal diferença InjectorX para os principais packages já disponíveis é o controle de injeção por contexto, assim descentralizando as injeções e não instanciando o que não precisa fora daquele contexto. Nesse modelo o objeto em si é um service locator para suas próprias injeções, substituindo a necessidade de passar as injeções via controlador, contudo não perdendo o poder de desacoplamento de código, facilitando ainda mais a visualização do que é injetado naquele objeto.

Mind Map:

Mind Map

Passo a passo do que o diagrama representa:

Antes de tudo devemos definir nossos contratos da aplicação.

Em um contrato é estabelecido quais as regras que um objeto tem que ter ao ser implementado. Para que os objetos subjacentes não se acople a implementação em si e sim ao contrato, sendo assim independente da implementação qualquer objeto que siga as regras do contrato será aceito na injeção referenciada.

abstract class IApi {
  Future<dynamic> post(String url, dynamic data);
}

abstract class IUserRepo {
  Future<bool> saveUser(String email, String name);
}

abstract class IUserUsecase {
  Future<bool> call(String email, String name);
}

abstract class IViewModel {
  Future<bool> save(String email, String name);
  bool get inLoading;
}

/* 
Esse contrato é utilizando o flutter_tripple será exemplificado 
mais para frente 
*/
abstract class IViewModelTriple extends InjetorXViewModelStore<NotifierStore<Exception, int>> {
  Future<void> save(String email, String name);
}

/*
Nesse caso não preciso herdar de Inject, pois o contexto desse objeto 
não precisa controlar suas injeções, contudo o InjetorX poderá injetá-lo onde 
houver necessidade como no exemplo seguinte UserRepoImpl
*/
class ApiImpl implements IApi {
  @override
  Future post(String url, data) async {
    var httpClient = Dio();
    return await httpClient.post(url, data: data);
  }
}

/*
Como nossa implementação do repositório depende do contrato da api, devemos 
herdar da classe Inject para podermos manipular as injeções desse contexto 
separadamente.
 */

class UserRepoImpl extends Inject<UserRepoImpl> implements IUserRepo {
  /*
  No construtor dessa classe não é preciso passar as referências que precisam ser injetadas. 
  Isso é feito um pouco diferente agora, através de Needles 
  (Needle é agulha em inglês). Cada agulha (Ex: Needle<IApi>()) fará a referência 
  necessária ao contrato para o InjectorX saiba o que deve ser injetado no contexto desse
  objeto, pelo no método injector.
  */
  UserRepoImpl() : super(needles: [Needle<IApi>()]);
  /*
  Aqui é definido a variável do contrato da Api que o repositório aceitará para ser
  injetado em seu contexto.
  */
  late IApi api;

  /*
  Quando a classe herda de Inject automaticamente esse método será criado ele terá 
  objeto InjectorX que é um service locator para identificar e referenciar as 
  injeções ao contrato que o IUserRepoImpl precisa.
  */
  @override
  void injector(InjectorX handler) {
    /*
    Aqui de forma abstraída o handler do InjectorX 
    buscará a implementação registada para o contrato IApi
    */
    api = handler.get();
  }

  /*
  Aqui utilizaremos a implementação do contrato em sí, não sabemos qual é 
  a implementação e não precisamos, pois seguindo a regra do contrato imposto isso 
  fica irrelevante.
  */
  @override
  Future<bool> saveUser(String email, String name) async {
    try {
      await api
          .post("https://api.com/user/save", {"email": email, "name": name});
      return true;
    } on Exception {
      return false;
    }
  }
}

/*
Aqui tudo se repetirá como no exemplo anterior, contudo aqui não sabemos 
o que o UserRepoImpl injeta em seu contexto apenas referenciamos ao seu contrato
e o InjectorX saberá o que injetar em cada contexto etapa por etapa.
*/
class UserUsecaseImpl extends Inject<UserUsecaseImpl> implements IUserUsecase {
  UserUsecaseImpl() : super(needles: [Needle<IUserRepo>()]);

  late IUserRepo repo;
  /*
  O conceito de use case é para controlar a regra de negócio de um comportamento em 
  específico nesse caso só deixará salvar usuários com email do gmail. 
  */
  @override
  Future<bool> call(String email, String name) async {
    if (email.contains("@gmail.com")) {
      return await repo.saveUser(email, name);
    } else {
      return false;
    }
  }

  @override
  void injector(InjectorX handler) {
    repo = handler.get();
  }
}

 /*
  O ViewModel é responsável pelo controle de estado de uma tela, ou de um widget em específico, note que o 
  view model não controla regra de negócio e sim estado da tela qual for referenciado.
  Nesse caso o estado está sendo controlado por RxNotifier, contudo isso pode ser feito 
  com qualquer outro gerenciador de estado da sua preferência.
 */
class ViewModelImpl extends Inject<ViewModelImpl> implements IViewModel {
  ViewModelImpl() : super(needles: [Needle<IUserUsecase>()]);

  late IUserUsecase userUsecase;
  var _inLoading = RxNotifier(false);

  set inLoading(bool v) => _inLoading.value = v;
  bool get inLoading => _inLoading.value;

  @override
  void injector(InjectorX handler) {
    userUsecase = handler.get();
  }

  @override
  Future<bool> save(String email, String name) async {
    var _result = false;

    inLoading = true;
    _result = await userUsecase(email, name);
    inLoading = false;

    return _result;
  }
}

/* 
 O InjectorX também pode ser integrado com o flutter_triple de maneira simplificada
 facilitando ainda mais o controle de estado por fluxo.
 */
class PresenterViewModel extends NotifierStore<Exception, int>
    with InjectCombinate<PresenterViewModel>
    implements IPresenterViewModel {
  PresenterViewModel() : super(0) {
    /*
    Note que há uma pequena diferença agora temos um init() dentro da chamada do 
    contrutor. Isso ocorre porque ao herdar de InjectCombinate precisa ser iniciado para que o InjectorX saba quais  needles responsáveis pela gerência dos contratos de injeção .
    Para saber mais sobre o flutter_triple acesse: https://pub.dev/packages/flutter_triple
   */
    init(needles: [Needle<IUsecase>()]);
  }
  /*
  No te que referenciamos a dependência diferente agora, não é manipulado mais pelo injector(InjectorX hangles) sim dessa nova maneira referenciado pelo inject()
  */
  IUsecase get usecase => inject();

  @override
  bool increment() {
    update(usecase.increment(state));
    return true;
  }

  @override
  NotifierStore<Exception, int> getStore() {
    return this;
  }
}

/*
Agora partiremos para implementação de uma view para exemplificar o fluxo completo.
O InjectoX tem um recurso específico para lidar com a view.

Nesse primeiro exemplo será usado o ViewModel com RxNotifier;

Observe que agora não é mais implementado o método:
@override
void injector(InjectorX handler) {
  userUsecase = handler.get();
}

Se tratando de uma view isso é feito de maneira diferente. Olhem no intiState() a novo jeito proposto.
*/

class ScreenExample extends StatefulWidget
    with InjectCombinate<ScreenExample> {
  ScreenExample() {
     init(needles: [Needle<IViewModel>()])
  };
  @override
  _ScreenExampleState createState() => _ScreenExampleState();
}

class _ScreenExampleState extends State<ScreenExample> {
  late IViewModel viewModel;

  @override
  void initState() {
    super.initState();
    /*
    Aqui agora ao em vez de usar o handler do método injector como exemplificado anteriormente, 
    simplesmente chamamos widget.inject() que terá o service locator da view com os recursos do InjectorX
    */
    viewModel = widget.inject();
  }

  @override
  Widget build(BuildContext context) {
    return RxBuilder(
      builder: (_) => IndexedStack(
        index: viewModel.inLoading ? 0 : 1,
        children: [
          Center(child: CircularProgressIndicator()),
          Center(
            child: ElevatedButton(
              onPressed: () async {
                var success =
                    await viewModel.save("[email protected]", "Username");
                if (success) {
                  print("Users successful saved");
                } else {
                  print("Error on save user");
                }
              },
              child: Text("Salvar dados do usuário"),
            ),
          )
        ],
      ),
    );
  }
}

/*
Aqui outro exemplo de como podemos implementar com o flutter_triple não há muita diferença em essência
a não ser como lidamos com a mudança de estado.
*/
class ScreenTripleExample extends StatefulWidget
    with InjectCombinate<ScreenTripleExample> {
  ScreenTripleExample() {
     //Não se esqueça de iniciar o injectorX
     init(needles: [Needle<IViewModel>()])
  };
  @override
  _ScreenTripleExampleState createState() => _ScreenTripleExampleState();
}

class _ScreenTripleExampleState extends State<ScreenTripleExample> {
  late IViewModelTriple viewModel;

  @override
  void initState() {
    super.initState();
    viewModel = widget.inject();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ScopedBuilder(
        //Note que agora é usado o getStore da implementação ViewModel com flutter_triple
        store: viewModel.getStore(),
        onState: (context, state) => Center(
          child: ElevatedButton(
            onPressed: () async {
              await viewModel.save("[email protected]", "Username");
            },
            child: Text("Salvar dados do usuário"),
          ),
        ),
        onError: (context, error) => Center(child: Text(error.toString())),
        onLoading: (context) => Center(child: CircularProgressIndicator()),
      ),
    );
  }
}

Referência de contratos

Para que o InjectorX saiba o que deve injetar em cada needle devemos na inicialização do app mostrar para o InjectorX qual a implementação de cada contrato. Note que em nenhum momento é passado implementação para o construtor de outra referência, por mais que a implementação tenha injeções em sua implementação. Isso fará toda a diferença no controle de injeção, pois fica mais simples a visualização e tudo não será carregado de uma vez em memória e sim por demanda, à medida que cada objeto necessite de uma injeção.

void _registerDependencies() {
  InjectorXBind.add<IApi>(() => ApiImpl());
  InjectorXBind.add<IUserRepo>(() => UserRepoImpl());
  InjectorXBind.add<IUserUsecase>(() => UserUsecaseImpl());
  InjectorXBind.add<IViewModel>(() => ViewModelImpl());
  InjectorXBind.add<IViewModelTriple>(() => ViewModelTriple());
}

Como ficaria isso com GetIt só um exemplo simples e fictício

Note que é passado as referências de injeção por construtor, aqui como é um exemplo pequeno ainda conseguimos visualizar facilmente, contudo à medida que precisar de várias injeções em um único construtor e aplicação for crescendo, isso virará um caos e ficará extremamente difícil de visualizar e controlar o que está injetando no que. E nesse caso todos os objetos foram subidos em memória mesmo que não precise daquela referência ela já está em memória.

void _setup() {
  GetIt.I.registerSingleton<IApi>(ApiImpl());
  GetIt.I.registerSingleton<IUserRepo>(UserRepoImpl( GetIt.I.get<IApi>() ));
  GetIt.I.registerSingleton<IUserUsecase>(UserUsecaseImpl( GetIt.I.get<IUserRepo>() ));
  GetIt.I.registerSingleton<IViewModel>(ViewModelImpl( GetIt.I.get<IUserUsecase>() ));
  GetIt.I.registerSingleton<IViewModelTriple>(ViewModelTriple( GetIt.I.get<IUserUsecase>() ));
}

O InjectoX não depende de uma chamada específica utilizando a referência do gerenciador de dependência no GetIt toda vez que precisamos recuperar um objeto que está registrado em seu pacote e feito como no exemplo abaixo:

 var viewModel = GetIt.I.get<IViewModel>();

Se não for feito como no exemplo acima todas as referências que precisam ser auto injetadas não funcionarão.

No injectorX posso ser livre e fazer de dois jeitos. Usando o gerenciador de dependência como abaixo:

 IViewModel viewModel = InjectorXBind.get();

Ou instanciando a classe diretamente:

 /* 
 ViewModel depende de IUserUsecase que é implementado por UserUsecaseImpl que
 por sua vez depende de IUserRepo que é implementado por UserRepoImpl que por
 sua vez depende de IApi que é implementado por ApiImpl. Controle de dependência
 é feito em etapas por cada contexto, por isso instanciar a classe diretamente não faz diferença.
 Que mesmo assim tudo que precisa ser injetado nesse contexto será injetado sem problemas.
 */
 
 var viewModel = ViewModelImpl();

Registrado singleton

void _registerDependencies() {
  InjectorXBind.add<IApi>(() => ApiImpl(), singleton: true);
  InjectorXBind.add<IUserRepo>(() => UserRepoImpl(), singleton: true);
  InjectorXBind.add<IUserUsecase>(() => UserUsecaseImpl(), singleton: true);
  InjectorXBind.add<IViewModel>(() => ViewModelImpl(), singleton: true);
  InjectorXBind.add<IViewModelTriple>(() => ViewModelTriple(), singleton: true);
}

Dessa maneira é referenciado o contrato ao singleton, contudo esse singleton só será gerado uma instância se algum objeto subjacente necessite do seu uso, caso contrário o objeto não será posto em memória.

Instanciando um novo objeto mesmo tendo registrado em singleton

Existem 2 maneira de fazer isso uma é pelo InjetorXBind como abaixo:

 IViewModel viewModel = InjectorXBind.get(newInstance: true);

Da maneira do exemplo acima, mesmo tendo feito o registo no InjetorXBind como singleton essa chamada trará um nova instância do objeto;

Contudo isso pode ser feito se o Needle de um objeto em específico precisar que toda as vezes a suas injeções sejam instanciadas novamente

Ex no caso do IUserRepo:

class UserRepoImpl extends Inject<UserRepoImpl> implements IUserRepo {
  /*
  Note o parâmetro newInstance: true na referência no Needle<IApi>
  isso quer dizer que mesmo que o InjectorXBind tenha feito o registro 
  desse contrato em singleton, nesse objeto isso será ignorado e sempre trará 
  uma nova instância de ApiImpl.
  */
  UserRepoImpl() : super(needles: [Needle<IApi>(newInstance: true)]);
  late IApi api;
  @override
  void injector(InjectorX handler) {
    api = handler.get();
  }
  @override
  Future<bool> saveUser(String email, String name) async {
    try {
      await api
          .post("https://api.com/user/save", {"email": email, "name": name});
      return true;
    } on Exception {
      return false;
    }
  }
}

Testes e injeção de mocks

Injetando ApiMock no UserRepoImp Existem duas maneiras de fazer isso, uma InjectorXBind.get e outra instanciando a classe diretamente. Nesse exemplo estou utilizando o Mockito para construção dos mocks

class ApiMock extends Mock implements IApi {}

void main() {

  _registerDependencies();
  
  late ApiMock apiMock;
  
  setUp(() {
     apiMock = ApiMock();
  });

   /*
   Ex com InjectorXBind.get;
   */
  test("test use InjectorXBind.get", () async {

    when(apiMock.post("", "")).thenAwswer((_) async => true);
    /*
    É utilizado injectMocks da implementação a qual quer testar para substituir as injeções dentro do seu 
    contexto testando e injetando unicamente só o que pertence ao objeto que está mesa de teste, 
    ignorando totalmente tudo que não faz parte desse contexto em específico.
    */
    var userRepoImp = (InjectorXBind.get<IUserRepo>() as UserRepoImpl).injectMocks([NeedleMock<IApi>(mock: apiMock)]);
    var res = await userRepoImp.saveUser("", "");
    expect(res, isTrue);
  });

   /*
   Exemplo por instância;
   */
  test("test use direct implement instance", () async {

    when(apiMock.post("", "")).thenAwswer((_) async => true);
    /*
    É utilizado injectMocks da implementação a qual quer testar para substituir as injeções dentro do seu 
    contexto testando e injetando unicamente só o que pertence ao objeto que está mesa de teste, 
    ignorando totalmente tudo que não faz parte desse contexto em específico.
    Assim a escrita fica mais simplificada contudo tem o mesmo resultado final
    */
    var userRepoImp = UserRepoImpl().injectMocks([NeedleMock<IApi>(mock: apiMock)]);
    var res = await userRepoImp.saveUser("", "");
    expect(res, isTrue);
  });
}

Este tipo de injeção de mock pode ser feito qual qualquer objeto relacionado ao InjectorX sendo eles InjectorViewModelTriple, StatefulWidgetInject e Inject, todos eles terão o mesmo comportamento e facilidade.

Caso queira ajudar dessa doc ou ficou com alguma dúvida deixe sua sugestão de melhoria

Email: [email protected]

Assunto: Help InjectorX

LinkedIn: https://linkedin.com/in/michaelslopes/

Obrigado a todos e espero que gostem dessa lib e tenham as mesmas vantagens que eu tive a usá-la.

You might also like...

Create a Flutter User Profile Page UI where you can access and edit your user's information within your Flutter app.

Create a Flutter User Profile Page UI where you can access and edit your user's information within your Flutter app.

Flutter Tutorial - User Profile Page UI #2 Create a Flutter User Profile Page UI where you can access and edit your user's information within your Flu

Dec 15, 2022

Let's create a selectable Flutter Navigation Drawer with routing that highlights the current item within the Flutter Sidebar Menu.

Let's create a selectable Flutter Navigation Drawer with routing that highlights the current item within the Flutter Sidebar Menu.

Flutter Tutorial - Sidebar Menu & Selectable Navigation Drawer Let's create a selectable Flutter Navigation Drawer with routing that highlights the cu

Dec 26, 2022

Components that optimize Flutter fluency.(Flutter 流畅度优化的通用方案,轻松解决卡顿问题)

Components that optimize Flutter fluency.(Flutter 流畅度优化的通用方案,轻松解决卡顿问题)

Flutter fluency optimization component "Keframe" Page switching fluency improved: How to use Project depend on: Quick learning Constructor Description

Dec 30, 2022

Challenge yourself every weekend with flutter. Join me to implement challenging UI & digital designs using Flutter.

Challenge yourself every weekend with flutter. Join me to implement challenging UI & digital designs using Flutter.

Weekend With Flutter This is my new challenge. Every weekend, I want to implement challenging UI & digital designs using Flutter. you can join me with

Feb 24, 2022

Let's create a complete Flutter User Profile Page with SharedPreferences to persist the user's information in Flutter.

Let's create a complete Flutter User Profile Page with SharedPreferences to persist the user's information in Flutter.

Flutter Tutorial - User Profile & SharedPreferences Let's create a complete Flutter User Profile Page with SharedPreferences to persist the user's inf

Dec 3, 2022

Let's create a Flutter Collapsible Sidebar Menu that can collapse and expand the Navigation Drawer in Flutter.

Let's create a Flutter Collapsible Sidebar Menu that can collapse and expand the Navigation Drawer in Flutter.

Flutter Tutorial - Collapsible Sidebar Menu & Navigation Drawer Let's create a Flutter Collapsible Sidebar Menu that can collapse and expand the Navig

Jan 3, 2023

🚗 Apple CarPlay for Flutter Apps. Aims to make it safe to use apps made with Flutter in the car by integrating with CarPlay.

🚗 Apple CarPlay for Flutter Apps. Aims to make it safe to use apps made with Flutter in the car by integrating with CarPlay.

CarPlay with Flutter 🚗 Flutter Apps now on Apple CarPlay! flutter_carplay aims to make it safe to use iPhone apps made with Flutter in the car by int

Dec 26, 2022

Flutter ui boilerplate is easiest way to create new flutter project with clean code and well organized file folder.

Flutter ui boilerplate is easiest way to create new flutter project with clean code and well organized file folder.

Flutter UI Boilerplate "Sharing for fun" Flutter ui boilerplate is easiest way to create new flutter project with clean code and well organized file f

Dec 1, 2022
Owner
Michael S. Lopes
Michael S. Lopes
clean architecture and clean code with flutter , with bloc and getx state managment .

example_bloc A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if

Khaled ElTohamy 6 Aug 22, 2022
Movie App used MVC pattern, Getx for state managment, sqflite for backend database

movie_app A new Flutter application. Getting Started This project used MVC pattern, Getx for state managment, sqflite for backend database, firebase/W

HM Badhon 3 Sep 13, 2022
🆙🚀 Flutter application upgrade/ Flutter App Upgrade /Flutter App Update/Flutter Update / download Plug-in

???? Flutter application upgrade/ Flutter App Upgrade /Flutter App Update/Flutter Update / download Plug-in (with notice bar progress), supports full upgrade, hot update and incremental upgrade

PengHui Li 344 Dec 30, 2022
ABC of Flutter widgets. Intended for super beginners at Flutter. Play with 35+ examples in DartPad directly and get familiar with various basic widgets in Flutter

Basic Widgets Examples This is aimed for complete beginners in Flutter, to get them acquainted with the various basic widgets in Flutter. Run this pro

Pooja Bhaumik 815 Jan 3, 2023
Minha primeira aplicação android utilizando Flutter feito no curso de Flutter da Cod3r Cursos Online. O foco dessa aplicação foi um contato inicial com o Flutter.

expenses Expenses é uma aplicação android simples feita em Flutter para controlar despesas pessoais. A aplicação consiste em: Listar transações feitas

Guilherme Teixeira Ais 2 Apr 19, 2022
Flutter Github Following Application, Using Flutter Provider and Flutter HTTP to get data from Github API.

Flutter Github Following Application Watch it on Youtube Previous Designs Checkout my Youtube channel Installation Please remember, after cloning this

Mohammad Rahmani 110 Dec 23, 2022
Flutter RSS feed parsing - A demo application of flutter which parse RSS XML contents to the flutter application

Flutter RSS feed parsing demo This is demo application of flutter which shows ho

Nyakuri Levite 3 Nov 15, 2022
Boris Gautier 1 Jan 31, 2022
Code for Flutter Talk from Flutter Vikings 2022: Custom User Interactions in Flutter

Custom User Interactions - Flutter Vikings 2022 A companion app for the Flutter Vikings 2022 talk - Custom User Interactions with Shortcuts, Intents,

Justin McCandless 9 Sep 16, 2022
Create a Flutter User Profile Page UI where you can access and edit your user's information within your Flutter app.

Flutter Tutorial - User Profile Page UI 1/2 Create a Flutter User Profile Page UI where you can access and edit your user's information within your Fl

Johannes Milke 46 Dec 6, 2022