Business Logic Component (BLoC)

BLoC pode ser considerado um local onde você armazena o estado global do aplicativo .

O que é o BLoC?

Até um tempo atrás era muito difícil encontrar algum documento em português sobre o assunto, hoje, já há vídeos sobre o assunto, vejam em flutterando.com.br que encontrará muitos vídeos a respeito.

Mas o que seria BLoC? ele pode ser considerado um local onde você armazena o estado global do aplicativo. Ocorre quando você envia os dados provenientes de sua interface do usuário para o BLoC e usa fluxos para acessar dados do BLoC em qualquer widget. O envio dos dados para o BLoC é realizado usando o Subject do RxDart e a recuperação de dados do BLoC é obtida usando o RxDart's Observable .

Para simplificar:

  • Os dados inseridos pelo usuário a partir da Camada de exibição são adicionados ao fluxo usando o coletor do Assunto . Esta é a entrada para o BLoC.
  • Widgets na camada de visualização recuperam dados usando fluxos . Esta é a saída do BLoC.

     Figura 1: Terminologia usada no Dart and RxDart

 

Configuração da interface do usuário

Em Flutter, tudo é um widget, você já deve ter lido ou ouvido isso em muitos artigos. Assim, um bom lugar para começar seria primeiro ter nossos widgets renderizados na tela antes de adicionar qualquer funcionalidade.

Nota: Eu discutirei mais sobre Provider mais adiante neste artigo quando eu discutir sobre a implementação do BLoC .

Vamos começar?

Criando as pastas lib/src, lib/src/login, lib/src/home e após criaremos os seguintes arquivos dentro das respectivas pastas:

lib/main.dart

lib/src/app.dart

lib/src/login/login-bloc.dart

lib/src/login/login-widget.dart

lib/src/login/login-validators.dart

lib/src/home/home-widget.dart

Implementando o BLoC e Utilizando bloc_provider

Primeiro vamos criar o nosso BLoC, lib/src/login/login-bloc.dart, depois criar um StreamController. Utilizaremos também o BehaviorSubject que é um StreamController especial que captura o item mais recente que foi adicionado ao controlador e emite isso como o primeiro item para qualquer novo ouvinte.

É necessário também garantir que todos os widgets do nosso aplicativo possam ter acesso a esse bloco que iremos criar.

Para isso nós poderiamos criar uma classe Provider que estende o InheritedWidget fornecido pelo Flutter. Dependendo de onde você acessar a instância bloc na sua árvore de widgets do aplicativo, InheritedWidgets fornecerá todos os widgets sob o acesso ao bloco, porém, há um plugin desenvolvido pelo Jacob chamado de bloc_pattern que já faz isso para a gente (abra o link e veja como instalá-lo.

Isso pode ser conseguido da seguinte maneira:

import 'dart:async';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'package:rxdart/rxdart.dart';
import 'login-validators.dart';

class LoginBloc extends BlocBase with Validator implements Object {
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();


@override
void dispose() {
_emailController.close();
_passwordController.close();
}
}

Em seguida, precisamos criar um getter para adicionar e-mail e senha ao coletor e um getter para recuperar e-mail e senha que são enviados para o StreamTransformer. O getter é usado para melhor legibilidade do código.

import 'dart:async';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'package:rxdart/rxdart.dart';
import 'login-validators.dart';

class LoginBloc extends BlocBase with Validator implements Object {
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();

//Recebe dados do fluxo
Stream<String> get emailStream => _emailController.stream
.transform(performEmailValidation); //Retorna o fluxo que transformamos
Stream<String> get passwordStream =>
_passwordController.stream.transform(performPasswordValidation);

//Combinando os fluxos de email e senha
Stream<bool> get submitValid =>
Observable.combineLatest2(emailStream, passwordStream, (e, p) => true);

//Add dados ao fluxo
Function(String) get updateEmail => _emailController.sink.add;
Function(String) get updatePassword => _passwordController.sink.add;

@override
void dispose() {
_emailController.close();
_passwordController.close();
}
}

Executando Validação Usando o StreamTransformer

Agora, nós criamos um StreamTransformer para executar a validação do endereço de e-mail e um StreamTransformer para executar a validação da senha.

Criamos uma classe mista chamada Validator, que contém os transfomers de stream, para que possamos reutilizá-los onde quisermos.

Para isso editaremos o lib/src/login/login-validator.dart:

Dica: Pense em classes mixin sempre que você sentir que uma função específica será usada em vários lugares para obter uma melhor reusabilidade e manutenção de código.

import 'dart:async';

class Validator {
final performEmailValidation =
StreamTransformer<String, String>.fromHandlers(handleData: (email, sink) {
String emailValidationRule = r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(emailValidationRule);

if (regExp.hasMatch(email)) {
sink.add(email);
} else {
sink.addError('Favor, entrar com um e-mail válido');
}
});

final performPasswordValidation = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
String passwordValidationRule = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#%]).{6,10})';
RegExp regExp = new RegExp(passwordValidationRule);

if (regExp.hasMatch(password)) {
sink.add(password);
} else {
sink.addError(
'A senha tem que ter um número, uma letra minúscula, uma maiúscula, um caracter especial "@#%" e no min. 6 a max. 10 caracteres');
}
});
}

Fornecendo acesso ao bloco usando widgets herdados

Criamos o widget MaterialApp como um filho para o BlocProvider para que todos os widgets em nosso aplicativo possam ter acesso à instância bloc, para entender melhor sobre isso vocês podem ver o vídeo do Jacob, Recuperando BLoC em outra janela, no canal flutterando.com.br:

import 'package:flutter/material.dart';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'login/login-bloc.dart';
import 'package:login_bloc/src/login/login-widget.dart';
import 'package:login_bloc/src/home/home-widget.dart';

class App extends StatelessWidget {
@override
Widget build(context) {
return BlocProvider<LoginBloc>(
child: MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => LoginScreen(),
'/secondscreen': (context) => SecondScreen(),
},
),
bloc: LoginBloc(),
);
}
}

Em seguida, criamos uma instância bloc e a tornamos acessível por meio da função.

Nota: Eu discutirei mais sobre rotas mais adiante neste artigo quando configurarmos a navegação.

Finalmente, acessamos essa instância bloc em nosso widget LoginScreen, lib/src/login/login-widget.dart, da seguinte forma final LoginBloc bloc = BlocProvider.of<LoginBloc>(context); vejamos como ficará: 

import 'package:flutter/material.dart';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'login-bloc.dart';

class LoginScreen extends StatelessWidget {
@override
Widget build(context) {
final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);

return Scaffold(
appBar: AppBar(
title: Text('BLoC Login'),
),
body: Container()
);
}

Recuperando dados do bloco e reprocessando campos de texto

O widget TextField tem uma propriedade onChanged, em que nós chamamos o get updateEmail e updatePassword da instância bloc para adicionar email e senha ao sink.

Para recuperar email e senha do Bloc, precisamos usar widgets StreamBuilder fornecidos pelo Flutter que permitem que os widgets sejam renderizados novamente. O StreamBuilder possui uma propriedade chamada stream, na qual usamos o getStream do emailStream e o getter do passwordStream da instância bloc para recuperar email e senha do bloco.

Se a validação não foi bem sucedida, o erro é recuperada a partir do fluxo e processado no ErrorText propriedade no InputDecoration widget. a captura instantânea contém dados ou erros que foram recuperados do bloco.

Por fim, precisamos adicionar funcionalidade ao nosso botão de login, que deve ser ativado ou desativado somente quando os campos de email e senha forem válidos.

RxDart fornece um utilitário útil para combinar fluxos para conseguir isso. Adicionamos isso à nossa classe de bloco utilizando o StreamTransformer, conforme já exposto no tópico anterior.

Como fizemos para os campos de e-mail e senha acima, usando o widget StreamBuilder, recuperamos dados do fluxo. O botão será ativado somente se o instantâneo tiver dados válidos.

Depois de adicionar validações, é assim que nosso aplicativo agora se parece: 

import 'package:flutter/material.dart';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'login-bloc.dart';

class LoginScreen extends StatelessWidget {
@override
Widget build(context) {
final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);

return Scaffold(
appBar: AppBar(
title: Text('BLoC Login'),
),
body: Container(
margin: EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
emailField(bloc),
SizedBox(
height: 10.0,
),
passwordField(bloc),
SizedBox(
height: 40.0,
),
loginButton(bloc),
],
),
),
);
}

Widget emailField(bloc) {
return StreamBuilder(
stream: bloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: bloc.updateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'email@email.com',
labelText: 'Email',
errorText: snapshot.error,
),
);
},
);
}

Widget passwordField(bloc) {
return StreamBuilder(
stream: bloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: bloc.updatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Sua senha',
labelText: 'Senha',
errorText: snapshot.error,
),
);
},
);
}

Widget loginButton(LoginBloc stateMgmtBloc) {
return StreamBuilder(
stream: stateMgmtBloc.submitValid,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return RaisedButton(
child: Text('Entrae e navegar para a próxima tela'),
color: Colors.blue,
onPressed: (){},
);
},
);
}
}

Adicionando Navegação e Acessando Dados Através de Múltiplas Telas

Agora que temos nossas validações configuradas, vamos adicionar algumas funcionalidades ao botão, navegando o usuário para uma nova tela e exibindo o endereço de e-mail que o usuário inseriu na nova tela usando o BLoC.

Primeiro, você precisa configurar rotas para cada tela em seu aplicativo no widget MaterialApp da seguinte forma, em lib/src/app.dart:

import 'package:flutter/material.dart';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'login/login-bloc.dart';
import 'package:login_bloc/src/login/login-widget.dart';
import 'package:login_bloc/src/home/home-widget.dart';

class App extends StatelessWidget {
@override
Widget build(context) {
return BlocProvider<LoginBloc>(
child: MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => LoginScreen(),
'/secondscreen': (context) => SecondScreen(),
},
),
bloc: LoginBloc(),
);
}
}

Em seguida, na propriedade onPressed do RaisedButton , verificamos se o instantâneo possui dados válidos. Se isso acontecer, nós navegamos o usuário para a nova tela em um retorno de chamada da seguinte forma: () => Navigator.pushNamed (contexto, “/ secondscreeen”)

Se o instantâneo não tiver dados válidos, o botão será desativado, vejamos em lib/src/login/login-widget.dart.

import 'package:flutter/material.dart';
import 'package:bloc_pattern/bloc_pattern.dart';
import 'login-bloc.dart';

class LoginScreen extends StatelessWidget {
@override
Widget build(context) {
final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);

return Scaffold(
appBar: AppBar(
title: Text('BLoC Login'),
),
body: Container(
margin: EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
emailField(bloc),
SizedBox(
height: 10.0,
),
passwordField(bloc),
SizedBox(
height: 40.0,
),
loginButton(bloc),
],
),
),
);
}

Widget emailField(bloc) {
return StreamBuilder(
stream: bloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: bloc.updateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'email@email.com',
labelText: 'Email',
errorText: snapshot.error,
),
);
},
);
}

Widget passwordField(bloc) {
return StreamBuilder(
stream: bloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: bloc.updatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Sua senha',
labelText: 'Senha',
errorText: snapshot.error,
),
);
},
);
}

Widget loginButton(LoginBloc stateMgmtBloc) {
return StreamBuilder(
stream: stateMgmtBloc.submitValid,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return RaisedButton(
child: Text('Entrae e navegar para a próxima tela'),
color: Colors.blue,
onPressed: snapshot.hasData
? () => Navigator.pushNamed(context, "/secondscreen")
: null, //Disabilita o botão se houver erro
);
},
);
}
}

Vamos construir uma nova tela para exibir o endereço de e-mail digitado pelo usuário na tela de login e usando o widget StreamBuilder, recuperamos os dados de email do fluxo de email no BLoC e os exibimos na tela:

import 'package:flutter/material.dart';
import 'package:login_bloc/src/login/login-bloc.dart';
import 'package:bloc_pattern/bloc_pattern.dart';

class SecondScreen extends StatelessWidget {
Widget build(BuildContext context) {
final bloc = BlocProvider.of<LoginBloc>(context);

return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Container(
margin: EdgeInsets.all(40.0),
child: Column(
children: <Widget>[
buildText(bloc),
],
),
),
);
}
}

Widget buildText(LoginBloc bloc) {
return StreamBuilder(
stream: bloc.emailStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Text('Você está logado como ${snapshot.data}');
},
);
}

Em nossa main.dart iremos apagar todo o código ali presente e trocar por este:

import 'package:flutter/material.dart';
import 'src/app.dart';

void main() {
  runApp(new App());
}

OBS. lembrando que nosso objetivo é trabalhar com BLoC, então não irei explicar conceito mais básicos, embora eu seja iniciante aqui também, mas vocês podem ver os nossos artigos em flutterbrasil.com que já tem bastante coisa para poder iniciar em Flutter.

Resumo

É isso aí! Espero que você tenha uma ideia de como você pode aproveitar o poder da programação reativa usando o RxDart, vej também outros artigos no site que discorrem a respeito do assunto, para melhorar o desempenho do seu aplicativo Flutter. Embora o exemplo deste artigo tenha sido simples, você pode imaginar como o uso do padrão Bloc pode ser eficiente para melhorar o gerenciamento de estado à medida que seu aplicativo Flutter cresce.


O artigo é feito com base na tradução do artigo e com as devidas adaptações: https://hk.saowen.com/a/fbb6e484de022173fe85248875286060ce40d069c97420bc0be49d838e19e372 
211 Visualizações