Padrão Observer e MobX
Introdução ao Conceito de Observabilidade
Observabilidade é um conceito multifacetado na computação. Em sua essência, observabilidade é a capacidade de observar ou monitorar o estado interno de um sistema. Em sistemas de software, a observabilidade é a capacidade de observar o estado interno de um sistema em execução. A observabilidade é uma propriedade de um sistema que permite que seus estados internos sejam observados e compreendidos de forma clara e precisa.
Pessoal sugiro fortemente a leitura do capítulo 1 do livro Observability Engineering para entender melhor o conceito de observabilidade.
Padrão Observer
Implementar a observabilidade em um sistema é uma tarefa que já foi demandada por muitos desenvolvedores. Toda vez que temos uma tarefa sendo implementada diversas vezes, é sinal de que talvez tenhamos um padrão de projeto. O padrão Observer é um padrão de projeto de software que define uma dependência um-para-muitos entre objetos, de modo que quando um objeto muda de estado, todos os seus dependentes são notificados e atualizados automaticamente.
O padrão Observer é um dos padrões de projeto mais utilizados no desenvolvimento de software. Ele é utilizado para implementar a comunicação entre objetos em um sistema. O padrão Observer é composto por três elementos principais: o sujeito, o observador e a notificação. O sujeito é o objeto que é observado. O observador é o objeto que observa o sujeito. A notificação é o mecanismo que o sujeito utiliza para notificar os observadores de que houve uma mudança de estado.
Vamos verificar duas referências aqui sobre o padrão Observer:
Se quiser estudar mais padrões de projeto?
MobX
Legal, agora que já discutimos o padrão Observer, vamos falar sobre o MobX. O MobX é uma biblioteca que permite a criação de observáveis e reações de forma simples e eficiente. O MobX é uma biblioteca de gerenciamento de estado que foi inspirada no padrão Observer. A documentação do MobX pode ser encontrada aqui.
O MobX foi construído utilizando a seguinte filosofia: "Torne o que é observável, reaja a isso e torne isso observável".
O que está figura diz para nós? Na verdade bastante sobre o funcionamento do MobX. Quando uma variável é observável, ela pode ser observada por outras variáveis. Quando uma variável observável é alterada, as variáveis que a observam são notificadas e reagem a essa mudança. Essa é a essencia do MobX.
Implementando o MobX
Vamos criar um exemplo básico de modificação do MobX. Para isso, vamos primeiro criar um projeto base para a nossa aplicação. Para isso, vamos utilizar o comando flutter create mobx_example.
flutter create mobx_example
Agora vamos adicionar as dependências do MobX e do Flutter MobX. Para isso, vamos utilizar o comando flutter pub add mobx e flutter pub add flutter_mobx.
flutter pub add mobx
flutter pub add flutter_mobx
Agora vamos construir nossa aplicação. Primeiro, vamos criar a estrutura de pastas do nosso projeto. Vamos criar as pastas models, controllers e views. De acordo com o avanço da aplicação, vamos criando os arquivos necessários. Agora, dentro do nosso diretório de views, vamos criar um outro diretório com o nome de screens. Esse diretório vai conter as telas da nossa aplicação. Nossa aplicação vai possuir apenas duas telas: a tela que exibe a quantidade de vezes que um botão foi clicado e a tela que permite clicar no botão.
Murilo, que exemplo mais besta é esse?
Eu sei que esse exemplo é simples pessoal, mas tem vários conceitos do MobX que precisamos compreender antes de avançar na construção de soluções mais robustas. Vamos absorver esses conceitos e avançar para soluções mais complexas.
Vamos criar os arquivos clicks_screen.dart e action_screen.dart dentro do diretório screens. Vamos começar a escrever o código da nossa clicks_screen.dart.
// clicks_screen.dart
// Tela que vai exibir a quantidade total de clicks que foram acionados no projeto
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx_example/controllers/clicks_controller.dart';
class ClicksScreen extends StatelessWidget {
final ClicksController controller;
const ClicksScreen({Key? key, required this.controller}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Clicks"),
),
body: Center(
child: Observer(
builder: (_) {
return Text(
"Total de clicks: ${controller.totalClicks}",
style: TextStyle(fontSize: 24),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.pushNamed(context, "/action");
},
tooltip: "Trocar de Tela",
child: const Icon(Icons.arrow_circle_right_outlined),
)
);
}
}
Vamos avaliar o código anterior:
- Importamos o pacote
flutter_mobxpara utilizar oObserverque é um widget que observa umObservablee automaticamente reconstrói o widget quando oObservablemuda. Importante: essa reconstrução acontece mesmo se oObservablefor alterado em outro lugar da aplicação e em umStatelessWidget. - Importamos o
ClicksControllerque é o controlador que vai gerenciar o estado da nossa aplicação. - Criamos a classe
ClicksScreenque é umStatelessWidgetque vai exibir a quantidade total de clicks que foram acionados no projeto. - Criamos o método
buildque é o método que vai construir a nossa tela. Dentro dele, temos:- Um
Scaffoldque é o widget que implementa o layout básico de uma tela. Ele possui umaAppBarque é a barra superior da tela e umbodyque é o corpo da tela. - No corpo da tela, temos um
Centerque é um widget que centraliza o seu filho na tela. Dentro dele, temos umObserverque é um widget que observa umObservablee automaticamente reconstrói o widget quando oObservablemuda. Dentro doObserver, temos umTextque é um widget que exibe um texto na tela. O texto exibido é a quantidade total de clicks que foram acionados no projeto. - No
floatingActionButton, temos umFloatingActionButtonque é um botão flutuante que vai permitir a navegação para a próxima tela.
- Um
Nunca é demais avisar, nossa aplicação ainda não pode ser executada sem esses arquivos faltantes!
Agora vamos criar o nosso ClicksController. Vamos criar o arquivo clicks_controller.dart dentro do diretório controllers.
// clicks_controller.dart
// Controller que vai gerenciar a quantidade total de clicks que foram acionados no projeto
import 'package:mobx/mobx.dart';
part 'clicks_controller.g.dart';
class ClicksController = _ClicksControllerBase with _$ClicksController;
abstract class _ClicksControllerBase with Store {
int totalClicks = 0;
void incrementClicks() {
totalClicks++;
}
}
Vamos avaliar o código anterior:
- Importamos o pacote
mobxpara utilizar os decoradoresobservableeaction. - Importamos o arquivo
clicks_controller.g.dartque é o arquivo gerado pelo MobX que contém a implementação dos métodosincrementClicksetotalClicks. - Criamos a classe
ClicksControllerque é uma classe que vai gerenciar a quantidade total de clicks que foram acionados no projeto. - Criamos a classe
_ClicksControllerBaseque é uma classe abstrata que contém os métodos e atributos que vão ser utilizados peloClicksController. - Criamos o atributo
totalClicksque é um inteiro que vai armazenar a quantidade total de clicks que foram acionados no projeto. - Criamos o método
incrementClicksque é um método que incrementa a quantidade total de clicks que foram acionados no projeto.
Espera um pouco Murilo, como essa part 'clicks_controller.g.dart'; foi parar aí?
Quando utilizamos o MobX, nós utilizamos alguns decoradores para indicar que um atributo ou método é observável ou uma ação. O MobX utiliza um gerador de código para gerar o código que vai ser utilizado para observar e reagir a mudanças no estado da aplicação. Para isso, precisamos adicionar a dependência build_runner e a dependência mobx_codegen ao nosso projeto. Para isso, vamos utilizar o comando:
flutter pub add build_runner
flutter pub add mobx_codegen
Mas espera denovo! Nós vamos ter que criar esse arquivo clicks_controller.g.dart? Não! O MobX vai criar esse arquivo para nós. Para isso, primeiro vamos navegar até a raiz do nosso projeto e executar o comando:
flutter packages pub run build_runner build
NÃO VAMOS ALTERAR NENHUM ARQUIVO GERADO PELO MOBX! Eles são gerados automaticamente e qualquer alteração neles pode causar problemas na aplicação. Quando alguma alteração for realizada nos arquivos que utilizam MobX, precisamos executar o comando acima para que os arquivos gerados sejam atualizados.
Agora vamos criar o nosso ActionScreen. Vamos criar o arquivo action_screen.dart dentro do diretório screens.
// action_screen.dart
// Tela que vai alterar o valor de totalClicks
import 'package:flutter/material.dart';
import 'package:mobx_example/controllers/click_action_controller.dart';
class ActionScreen extends StatelessWidget {
final ClickActionController controller;
const ActionScreen({Key? key, required this.controller}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Action"),
),
body: Center(
child: Column(children: [
const Text(
"Pressione o botão para incrementar o total de clicks",
style: TextStyle(fontSize: 24),
),
OutlinedButton(
onPressed: controller.incrementClicks,
child:
const Text("Incrementar Clicks", style: TextStyle(fontSize: 24))),
]),
),
);
}
}
Vamos avaliar o código anterior:
- Importamos o pacote
flutter/material.dartpara utilizar os widgets do Material Design. - Importamos o
ClicksControllerque é o controlador que vai gerenciar o estado da nossa aplicação. - Criamos a classe
ActionScreenque é umStatelessWidgetque vai alterar o valor detotalClicks.
Agora vamos criar o nosso ClickActionController. Vamos criar o arquivo click_action_controller.dart dentro do diretório controllers.
// click_action_controller.dart
// Controller que vai gerenciar a ação de incrementar o total de clicks
import 'package:mobx/mobx.dart';
import 'package:mobx_example/controllers/clicks_controller.dart';
part 'click_action_controller.g.dart';
class ClickActionController = _ClickActionControllerBase with _$ClickActionController;
abstract class _ClickActionControllerBase with Store {
final ClicksController clicksController;
_ClickActionControllerBase(this.clicksController);
void incrementClicks() {
clicksController.incrementClicks();
}
}
Vamos avaliar o código anterior:
- Importamos o pacote
mobxpara utilizar o decoradoraction. - Importamos o arquivo
clicks_controller.dartque é o controlador que vai gerenciar a quantidade total de clicks que foram acionados no projeto. - Importamos o arquivo
click_action_controller.g.dartque é o arquivo gerado pelo MobX que contém a implementação do métodoincrementClicks. - Criamos a classe
ClickActionControllerque é uma classe que vai gerenciar a ação de incrementar o total de clicks. - Criamos a classe
_ClickActionControllerque é uma classe abstrata que contém os métodos e atributos que vão ser utilizados peloClickActionController. - Criamos o atributo
clicksControllerque é umClicksControllerque vai gerenciar a quantidade total de clicks que foram acionados no projeto. - Criamos o método
incrementClicksque é um método que incrementa a quantidade total de clicks que foram acionados no projeto.
Agora vamos criar o arquivo click_action_controller.g.dart dentro do diretório controllers. Para isso, vamos utilizar o comando:
flutter packages pub run build_runner build
Murilo eu já havia reparado antes, mas o que é esse Store que aparece nas classes que utilizam MobX?
O Store é uma classe que contém os Observables e Actions que vão ser utilizados por um controlador. O MobX utiliza o Store para gerenciar o estado da aplicação. Cada Store pode ser visto como um módulo da aplicação que contém os Observables e Actions que vão ser utilizados por um controlador.
Agora vamos criar o arquivo main.dart dentro do diretório lib.
// main.dart
// Arquivo principal da aplicação
import 'package:flutter/material.dart';
import 'package:mobx_example/controllers/click_action_controller.dart';
import 'package:mobx_example/controllers/clicks_controller.dart';
import 'package:mobx_example/views/screens/action_screen.dart';
import 'package:mobx_example/views/screens/clicks_screen.dart';
void main() {
final ClicksController clicksController = ClicksController();
final ClickActionController actionController = ClickActionController(clicksController);
runApp(MyApp(clicksController: clicksController, actionController: actionController));
}
class MyApp extends StatelessWidget {
final ClicksController clicksController;
final ClickActionController actionController;
const MyApp({Key? key, required this.clicksController, required this.actionController}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: 'MobX Example',
initialRoute: "/clicks",
routes: {
"/clicks": (context) => ClicksScreen(controller: clicksController),
"/action": (context) => ActionScreen(controller: actionController),
},
);
}
}
Agora vamos compreender esse código:
- Na nossa função
main, criamos uma instância doClicksControllere doClickActionController. Elas são responsáveis por gerenciar o estado da nossa aplicação. - Criamos a classe
MyAppque é umStatelessWidgetque vai construir a nossa aplicação. Dentro dela, temos:- Um
MaterialAppque é o widget que implementa o layout básico de uma aplicação. Ele possui umtitleque é o título da aplicação, uminitialRouteque é a rota inicial da aplicação e umroutesque é um mapa de rotas que a aplicação pode acessar. - No
routes, temos duas rotas: a rota/clicksque vai exibir a quantidade total de clicks que foram acionados no projeto e a rota/actionque vai permitir a ação de incrementar o total de clicks. - Quando damos nome a uma rota, podemos acessá-la utilizando o método
Navigator.pushNamed(context, "/nome_da_rota").
- Um
Agora podemos executar nosso programa e vamos ver que nosso App tem duas telas. A primeira tela exibe a quantidade total de clicks que foram acionados no projeto e a segunda tela permite a ação de incrementar o total de clicks.
Pessoal, observem que o MobX faz muitas automações para nós. Ele é uma biblioteca que facilita a implementação de observáveis e reações em uma aplicação. Com o MobX, podemos criar aplicações mais reativas e responsivas de forma simples e eficiente.
Minha sugestão para vocês nesse momento: façam modificações nesse código!
- Modifiquem a quantidade de clicks que são incrementados a cada clique;
- Modifiquem a mensagem que é exibida na tela;
- Adicionem outras telas e ações..
Utilizar o MobX é uma tarefa que demanda prática. Então, mãos à obra!