Flutter - Login com Web API


No artigo de hoje veremos como criar uma tela de Login no Flutter e fazer o login usando uma Web API.

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

Criando uma interface de Login

Eu já mostrei como criar uma tela de login em outro artigo, por isso neste artigo vou adotar outra abordagem diferente para criar o login.

Vamos começar criando uma tela de login cujo layout pode ser visto abaixo:

Destaques da tela de login :

- AppBar com título;

- Utilização do widget Form;

- Dois widgets TextFormField para entrada do usuário com textos;

- Nos textos é exibido o teclado numérico;

- Um RaisedButton contido em um Container com texto;

 

 Criando o projeto : Tela de Login

No Visual Studio Code tecle CTRL+ SHIFT+P para abrir a paleta de comandos e a seguir selecione a opção : Fluter:New Project

A seguir informe o nome do projeto : flutter_login e tecle ENTER

Na janela de diálogo a seguir selecione a pasta onde o projeto vai ser salvo e clique em :
Select a folder to create the project in

O Flutter vai criar um projeto padrão onde todo o código da aplicação vai estar no arquivo main.dart dentro da pasta lib do projeto.

Vamos selecionar o arquivo main.dart e apagar todo o código gerado deixando o arquivo vazio.

A pasta lib é a pasta principal para escrever o código do aplicativo. Atualmente, o modelo de projeto padrão contém apenas um arquivo main.dart que é essencialmente um ponto de entrada para o aplicativo Flutter.

A seguir vamos criar dentro da pasta lib outra pasta chamada pages onde vamos criar o widget da nossa tela de login.

Dentro da pasta pages vamos criar o arquivo login_page.dart que representa o widget da nossa tela de login.

Assim, no momento, a estrutura do nosso projeto esta assim:

Definindo a entrada da aplicação no widget StatelessWidget

Na pasta lib vamos abrir o arquivo main.dart,e criar um widget sem estado ou StatelessWidget.

No Visual Studio Code podemos a começar a digitar st , e, poderemos usar a opção do menu de atalho selecionando : Flutter stateless widget

Isso irá gerar o código padrão para criar o widget sem estado. Basta digitar a seguir o nome do widget.

Vamos definir o nome MyApp, incluir a definição do método main() invocando MyApp() e definir a referência ao pacote: flutter/material.dart:

import 'package:flutter_login/pages/login_page.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor : Colors.blue
       ),
       home: LoginPage()
     );
  }
}

Neste código estamos retornando um MaterialApp e definindo os argumentos title, theme e home.

Nota: Veja o meu artigo sobre MaterialApp :  Apresentando o widget MaterialApp

No argumento home definimos um novo Widget chamado LoginPage() que será criado no arquivo login_page.dart e por isso temos uma referência a esse arquivo no import.

Assim a nossa tela de login será definida no arquivo login_page.dart.

Para iniciar vamos incluir o código abaixo neste arquivo:

import 'package:flutter/material.dart';

class LoginPage extends StatelessWidget {

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Fazer o Login"),
       ),
       body: _body(),
     ),
    );
  }

No código acima estamos usando o widget Scaffold e usando o argumento body para definir o método _body() onde vamos a seguir criar a tela de login.

Para criar a tela de login vamos usar um widget Form tendo como filho um widget ListView e neste widget usar um array de widgets para empilhar os widgets TextFormFields e RaisedButton.

Nota:  Para saber mais sobre o widget Form, veja meu artigo : Criando formulários com validação

 _body(BuildContext context) {
    return Form(
       child: ListView(
         children: <Widget>[
           textFormFieldLogin(),
           textFormFieldSenha(),
           containerButton(context)
         ],
        )
      );
  }

Para organizar o código e torná-lo mais legível definimos 3 métodos como filhos do ListView onde vamos implementar as funcionalidades.

1- textFormFieldLogin - Vamos implementar a entrada do usuário para o Login

textFormFieldLogin() {
     return TextFormField(
           keyboardType: TextInputType.text,
           style: TextStyle(color: Colors.black),
           decoration: InputDecoration( 
             labelText: "Login",
             labelStyle: TextStyle(fontSize:20.0, color: Colors.black),
             hintText: "Informe o login"
            )
         );
   }

Aqui usamos o widget TextFormField definindo o tipo de teclado e usando um InputDecoration para criar uma Label com um estilo e uma dica de texto.(hintText)

2- textFormFieldSenha - Vamos implementar a entrada do usuário para a Senha

textFormFieldSenha() {
     return TextFormField( 
           obscureText: true,
           keyboardType: TextInputType.text,
           style: TextStyle(color: Colors.black),
            decoration: InputDecoration( 
             labelText: "Senha",
             labelStyle: TextStyle(fontSize:20.0, color: Colors.black),
             hintText: "Informe a senha"
            )
         );
   }

Repetimos aqui o código acima, a diferença é o uso do obscureText para ocultar a senha.

3- containerButton(context)

 containerButton(BuildContext context) {
     return Container(
           height: 40.0,
           margin: EdgeInsets.only(top: 10.0),
           child: RaisedButton(
             color: Colors.blue,
             child: Text("Login", 
             style: TextStyle(color: Colors.white, fontSize: 20.0)),
              onPressed: () { _onClickLogin(context); },
              ),
          );
   }

Aqui retornamos um Container cujo filho é um RaisedButton onde temos o callback onPressed definindo o método _onClickLogin(context).

Abaixo temos a implementação do método _onClickLogin():

_onClickLogin(BuildContext context) {
   
    if(login.isEmpty || senha.isEmpty) {
      showDialog(context: context,
        builder: (context){
          return AlertDialog(
            title:Text("Erro"),
            content: Text("Login e/ou Senha inválido(s)"),
            actions : <Widget>[
                FlatButton(
                   child: Text("OK"),
                      onPressed: () {
                        Navigator.pop(context);
                }
               )
            ]
           );
      },
      );
    }
  }

Nesta implementação estamos validando o login e a senha e exibindo uma mensagem ao usuário.

Usamos um AlertDialog e criamos uma Action contendo um FlatButton e um widget Text usando o callback onPressed() para poder fechar a caixa de diálogo de alerta que será exibida com a mensagem ao usuário.

Uma outra abordagem para fazer a validação é usar o recurso TextEditingController e a chave global do formulário - GlobalKey. Com isso além de poder fazer a validação poderemos obter e tratar as informações digitadas pelo usuário nos widgets TextFormField.

Para essa abordagem temos que alterar o código incluindo o código destacado nas linhas azuis:

import 'package:flutter/material.dart';
class LoginPage extends StatelessWidget {
  final _tLogin = TextEditingController();
  final _tSenha = TextEditingController();
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Fazer o Login"),
       ),
       body: Padding(
         padding: EdgeInsets.all(16.0),
         child: _body(context),
     ),
    );
  }
  String _validateLogin(String text){
      if(text.isEmpty){
        return "Informe o login";
      }
      return null;
  }
  String _validateSenha(String text){
      if(text.isEmpty){
        return "Informe a senha";
      }
      return null;
  }  
   _body(BuildContext context) {
    return Form(
      key: _formKey,
       child: ListView(
         children: <Widget>[
           textFormFieldLogin(),
           textFormFieldSenha(),
           containerButton(context)
         ],
        )
      );
  }
   TextFormField textFormFieldLogin() {
     return TextFormField(
           controller: _tLogin,
           validator: _validateLogin,
           keyboardType: TextInputType.text,
           style: TextStyle(color: Colors.black),
           decoration: InputDecoration( 
             labelText: "Login",
             labelStyle: TextStyle(fontSize:20.0, color: Colors.black),
             hintText: "Informe a senha"
            )
         );
   }
 TextFormField textFormFieldSenha() {
     return TextFormField( 
           controller: _tSenha,
            validator: _validateSenha,
           obscureText: true,
           keyboardType: TextInputType.text,
           style: TextStyle(color: Colors.black),
            decoration: InputDecoration( 
             labelText: "Senha",
             labelStyle: TextStyle(fontSize:20.0, color: Colors.black),
             hintText: "Informe a senha"
            )
         );
   }
   Container containerButton(BuildContext context) {
     return Container(
           height: 40.0,
           margin: EdgeInsets.only(top: 10.0),
           child: RaisedButton(
             color: Colors.blue,
             child: Text("Login", 
             style: TextStyle(color: Colors.white, fontSize: 20.0)),
              onPressed: () { _onClickLogin(context); },
              ),
          );
   }  
  _onClickLogin(BuildContext context) {
    final login = _tLogin.text;
    final senha = _tSenha.text;
    print("Login: $login , Senha: $senha " );  
     if(!_formKey.currentState.validate())  {
      return;
     }
    if(login.isEmpty || senha.isEmpty) {
      showDialog(context: context,
      builder: (context){
          return AlertDialog(
            title:Text("Erro"),
            content: Text("Login e/ou Senha inválido(s)"),
            actions : <Widget>[
              FlatButton(
                child: Text("OK"),
                onPressed: () {
                  Navigator.pop(context);
                }
               )
            ]
           );
      },
      );
    }
  }
}

Vamos entender o código:

1- Criamos as variáveis _tedLogin e _tedSenha para gerenciar as informações entradas no TextFormField, e, criamos uma chave global - _formkey -  para identificar o formulário:

final _tedLogin = TextEditingController();
final _tedSenha = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

2- Definimos dois métodos para validar login e senha:

...
 String _validaLogin(String text){
      if(text.isEmpty){
        return "Informe o login";
      }
      return null;
  }
  String _validaSenha(String text){
      if(text.isEmpty){
        return "Informe a senha";
      }
      return null;
 }  
...

3- Atribuimos a chave global ao Form:

...
_body(BuildContext context) {
    return Form(
      key: _formKey,

 ....

4- Atribuimos os valores aos argumentos controller e validator de cada TextFormField:

 ...
TextFormField textFormFieldLogin() {
     return TextFormField(
           controller: _tLogin,
           validator: _validateLogin,
           ...
...
 TextFormField textFormFieldSenha() {
     return TextFormField(
           controller: _tSenha,
            validator: _validateSenha,
            ...

5- No método _onClickLogin() obtemos o login e senha informados e exibimos no console e validamos o formulário usando a chave global:

...
_onClickLogin(BuildContext context) {
    final login = _tLogin.text;
    final senha = _tSenha.text;
    print("Login: $login , Senha: $senha " );  
     if(!_formKey.currentState.validate())  {
      return;
    }
  ....

Essa abordagem não exibe a caixa de diálogo de alerta pois já mostra ao usuário a mensagem para informar os valores quando isso não for feito.

Assim, na verdade não vamos precisar do código que faz a validação do login e senha conforme abaixo:

 if(login.isEmpty || senha.isEmpty) {
      showDialog(context: context,
      builder: (context){
          return AlertDialog(
            title:Text("Erro"),
            content: Text("Login e/ou Senha inválido(s)"),
            actions : <Widget>[
              FlatButton(
                child: Text("OK"),
                onPressed: () {
                  Navigator.pop(context);
                }
               )
            ]
           );
      },
      );
    }

Temos assim a nossa tela de login criada e pronta para ser usada.

Na próxima parte do artigo vamos implementar a WEB API e fazer a autenticação do usuário.

Pegue o código completo aqui:  main_login_page.dart

"Bom é louvar ao SENHOR, e cantar louvores ao teu nome, ó Altíssimo;
Para de manhã anunciar a tua benignidade, e todas as noites a tua fidelidade;"
Salmos 92:1,2

Referências:


José Carlos Macoratti