Cadastro em Flutter, integração Firebase e ESP32

Tempo de leitura: 12 minutes

Este artigo tem como objetivo apresentar o desenvolvimento de um programa mobile construído em Flutter, o qual recebe informações de um usuário, envia ao banco de dados e assim o Esp32 recebe essas informações e armazena em seu sistema para abrir uma fechadura.

 

  • Lógica Geral.
  • Preparação do ambiente estilização.
  • Componentes da tela de cadastro.
  • Configurando o Firebase.
  • Possiveis erros

Fluxograma

Pseudocódigo

Ao criar um projeto com Flutter terá como padrão um exemplo de código no arquivo main.dart o qual o conteúdo deve ser removido e substituído para chamar a página de cadastro.

void main() {
 runApp(MyApp());
}
class MyApp extends StatelessWidget {
//This widget is the root of your application.
@override
Widget build(BuildContext context) {
 return MaterialApp(
  title: ‘Flutter Demo’,
  theme: ThemeData(
   primarySwatch: Colors.green,
  ),
  home: InsertInfos(),
 );
}}

Ps: Mostrará um erro em “InsertInfos()”, pois o arquivo ainda não foi criado e será o próximo passo.

Dentro da pasta “lib” crie uma pasta chamada “insert_pessoa” e o arquivo “insert_pessoas page.dart”, nele estará toda a estrutura da página. Além disso, para estilização é necessário criar uma pasta dentro e “lib” chamada “themes” com os seguintes arquivos “app_colors.dart” ”app_text_styles.dart”, estarão definindo as cores e estilo de texto respectivamente. Ficando desse modo →

Em “app_colors.dart” deverá ser criada uma classe chamada “AppColors” e inserir os códigos das cores.

class AppColors {
 static final primary = Color(0xFFFF941A);
 static final secondary = Color(0xFF585666);
 static final grey = Color(0xFF585666);
 static final delete = Color(0xFFE83F5B);
 static final heading = Color(0xFF585666);
 static final body = Color(0xFF706E7A);
 static final stroke = Color(0xFFE3E3E6);
 static final shape = Color(0xFFFAFAFC);
 static final background = Color(0xFFFFFFFF);
 static final input = Color(0xFFB1B0B8);
}

“app_text_styles.dart” terá uma classe chamada “TextStyles” com as seguintes propriedades de fontes importadas direto do Google Fonts

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; //pacote do Google Fonts
class TextStyles {
static final titleHome = GoogleFonts.lexendDeca(
fontSize: 32,
fontWeight: FontWeight.w600,
color: AppColors.heading,
);
static final titleRegular = GoogleFonts.lexendDeca(
fontSize: 20,
fontWeight: FontWeight.w400,
color: AppColors.background,
);
static final titleBoldHeading = GoogleFonts.lexendDeca(
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColors.heading,
);
static final titleBoldBackground = GoogleFonts.lexendDeca(
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColors.background,
);
static final titleListTile = GoogleFonts.lexendDeca(
fontSize: 17,
fontWeight: FontWeight.w600,
color: AppColors.heading,
);
static final trailingRegular = GoogleFonts.lexendDeca(
fontSize: 16,
fontWeight: FontWeight.w400,
color: AppColors.heading,
);
static final trailingBold = GoogleFonts.lexendDeca(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.heading,
);
static final buttonPrimary = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w400,
color: AppColors.primary,
);
static final buttonHeading = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w400,
color: AppColors.heading,
);
static final buttonGray = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w400,
color: AppColors.grey,
);
static final buttonBackground = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w400,
color: AppColors.background,
);
static final buttonBoldPrimary = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w700,
color: AppColors.primary,
);
static final buttonBoldHeading = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w700,
color: AppColors.heading,
);
static final buttonBoldGray = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w700,
color: AppColors.grey,
);
static final buttonBoldBackground = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w700,
color: AppColors.background,
);
static final input = GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w400,
color: AppColors.input,
);
static final captionBackground = GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w400,
color: AppColors.background,
);
static final captionShape = GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w400,
color: AppColors.shape,
);
static final captionBody = GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w400,
color: AppColors.body,
);
static final captionBoldBackground = GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.background,
);
static final captionBoldShape = GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.shape,
);
static final captionBoldBody = GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.body,
);
}

Criando os componentes do formulário com as explicações dentro do código “//” Mostra as partes comentadas

class InsertInfos extends StatefulWidget {
  final String? barcode;
  const InsertInfos({
    Key? key,
    this.barcode,
  }) : super(key: key);
@override
  _InsertInfosState createState() => _InsertInfosState();
}
class _InsertInfosState extends State<InsertInfos> {
  //Variaveis de controle de entrada, recebem os dados do usuario
  final controller = InsertPessoasController();
  final validadeInputTextController = TextEditingController();
  final nomeInputTextController = TextEditingController();
  final senhaInputTextController = TextEditingController();
//Precisa ser Future pois tem validação dos dados antes de ser chamado o alerta de confirmação de envio dos dados (Ultimo passo)
  Future<void> _showMyDialog(String titulo) async {
    return showDialog<void>(
      context: context,
      barrierDismissible: false, // user must tap button!
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text(titulo),
          actions: <Widget>[
            TextButton(
              child: Text('Fechar'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }
//Interface propriamente dita
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.background,
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Padding(
                padding:
                    const EdgeInsets.symmetric(horizontal: 93, vertical: 24),
                child: Text(
                  "Preencha os dados da pessoa",
                  style: TextStyles.titleBoldHeading,
                  textAlign: TextAlign.center,
                ),
              ),
//Inicio do formulario e armazenamento dos valores em variaveis temporarias
              Form(
                key: controller.formKey,
                child: Column(
                  children: [
                    InputTextWidget(
                      label: "Nome",
                      icon: Icons.people_alt,
//Recebe a entrada do valor nome
                      onChanged: (value) {
                        controller.onChange(name: value);
                      },
                      controller: nomeInputTextController,
                      validator: controller.validateName,
                    ),
                    InputTextWidget(
                      label: "Senha",
                      icon: FontAwesomeIcons.key,
//Recebe a entrada do valor senha
                      onChanged: (value) {
                        controller.onChange(senha: value);
                      },
                      controller: senhaInputTextController,
                      validator: controller.validateSenha,
                    ),
                    InputTextWidget(
                      label: "validade",
                      icon: FontAwesomeIcons.calendar,
//Recebe a entrada do valor validade
                      onChanged: (value) {
                        controller.onChange(validade: value);
                      },
                      controller: validadeInputTextController,
                      validator: controller.validateValidade,
                    )
                  ],
                ),
              )
            ],
          ),
        ),
      ),
//Botões do fim da tela de confirmação ou recarregamento
      bottomNavigationBar: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Divider(
            height: 1,
            thickness: 1,
            color: AppColors.stroke,
          ),
          SetLabelButtons(
            enableSecondaryColor: true,
            //Botão de limpar
            labelPrimary: "Limpar",
//ao tocar apenas cria a mesma pagina e exclui a anterior "pushReplacement"
            onTapPrimary: () {
              Navigator.pushReplacement(
                  context,
                  new MaterialPageRoute(
                      builder: (BuildContext context) => new InsertInfos()));
            },
            //Botão cadastrar
            labelSecondary: "Cadastrar",
            onTapSecondary: () async {
              //Chama uma função para validar os dados
              try {
                await controller.cadastrar();
                return _showMyDialog("Usuário enviado com sucesso!");
              } catch (err) {
                return _showMyDialog("Algo deu errado, tente novamente");
              }
            },
          ),
        ],
      ),
    );
  }
}

Provavelmente o editor está mostrando alguns erros de importações, os que já estão prontos já podem ser importados

Agora é necessario criar a parte da validação dos dados, na pasta “insert_pessoa” inserir um novo arquivo chamado “insert_pessoa_controller.dart”. Nele terá a verificação se as entradas estão nulas ou não, estando com valores serão tratadas e enviadas ao Firebase.

class InsertPessoasController {
  final formKey = GlobalKey<FormState>();
  PessoaModel model = PessoaModel();
//Caso a String esteja vazia ela retorna ao campo um texto em vermelho "O nome não pode ser vazio"
String? validateName(String? value) =>
      value?.isEmpty ?? true ? "O nome não pode ser vazio" : null;
  String? validateValidade(String? value) =>
      value?.isEmpty ?? true ? "A data de vencimento não pode ser vazio" : null;
  String? validateSenha(String? value) =>
      value?.isEmpty ?? true ? "A senha não pode ser vazia" : null;
void onChange({String? name, String? senha, String? validade}) {
    model = model.copyWith(name: name, senha: senha, validade: validade);
  }
//É chamada depois de cadastrar se o form estiver ok
Future<void> savePessoa() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp();
    final fb = FirebaseDatabase.instance;
    final ref = fb.reference();
    print(ref.child("ARMARIO/Proprietario").set(model.name));
    print(ref.child("ARMARIO/Validade").set(model.validade));
    print(ref.child("ARMARIO/SenhaGerada").set(model.senha));
return;
  }
//Verifica se o Form esta ok e chama a função savePessoa
Future<void> cadastrar() async {
    final form = formKey.currentState;
    if (form!.validate()) {
      //Form ok
      return await savePessoa();
    } else {
      //Avisa que tem erro
      throw ("err");
    }
  }
}

Por fim, para adicionar ao Banco de dados é necessario fazer a conversão do tipo de variavel. Vá em “lib” crie uma pasta chamada “models” e um arquivo “pessoas_model.dart

import 'dart:convert';
class PessoaModel {
  final String? name;
  final String? senha;
  final String? validade;
PessoaModel({
    this.name,
    this.senha,
    this.validade,
  });
PessoaModel copyWith({
    String? name,
    String? senha,
    String? validade,
  }) {
    return PessoaModel(
      name: name ?? this.name,
      senha: senha ?? this.senha,
      validade: validade ?? this.validade,
    );
  }
Map<String, dynamic> toMap() {
    return {
      'name': name,
      'senha': senha,
      'validade': validade,
    };
  }
factory PessoaModel.fromMap(Map<String, dynamic> map) {
    return PessoaModel(
      name: map['name'],
      senha: map['senha'],
      validade: map['validade'],
    );
  }
String toJson() => json.encode(toMap());
factory PessoaModel.fromJson(String source) =>
      PessoaModel.fromMap(json.decode(source));
@override
  String toString() {
    return 'PessoaModel(name: $name, senha: $senha, validade: $validade,)';
  }
@override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
return other is PessoaModel &&
        other.name == name &&
        other.senha == senha &&
        other.validade == validade;
  }
@override
  int get hashCode {
    return name.hashCode ^ senha.hashCode ^ validade.hashCode;
  }
}

Agora, é necessário voltar nos arquivos criados e acabar as importações de todas as classes. Além disso, precisa configurar o aplicativo com o Firebase.