Flutter: Cómo iniciar sesión con Firebase

Lanzamiento

25/12/18 - Se actualizó el último fragmento de código después de refactorizar y limpiar.

24/01/19 - Enlace duplicado a github en la parte superior del artículo.

23/07/19 - Método de recorte agregado al valor de correo electrónico y contraseña

Código fuente

En caso de que desee omitir todo el mumbo jumbo, puede obtener el código fuente aquí

https://github.com/tattwei46/flutter_login_demo

Actualizar

Aquí hay una secuela de esta publicación que es Cómo hacer CRUD con Firebase RTDB. ¡Echale un vistazo!

¿Qué es el aleteo?

Flutter es un SDK móvil de código abierto desarrollado por Google para crear aplicaciones de alta calidad para Android e iOS. Permite a los desarrolladores no solo crear aplicaciones con un diseño hermoso, una animación suave y un rendimiento rápido, sino que también pueden integrar nuevas características rápidamente. Flutter ofrece desarrollo de alta velocidad con su recarga en caliente con estado y reinicio en caliente. Con solo una base de código para administrar, puede ahorrar una gran cantidad de costos en comparación con la administración de proyectos de Android e iOS, ya que Flutter lo compila en código ARM nativo. Flutter usa el lenguaje de programación Dart que también es desarrollado por Google.

¿Por qué dardo?

  • Un lenguaje conciso, fuertemente tipado y orientado a objetos.
  • Admite la compilación justo a tiempo y anticipada.
  • JIT permite que Flutter recompile el código directamente en el dispositivo mientras la aplicación aún se está ejecutando.
  • Habilita el desarrollo rápido y permite una recarga en caliente estable por debajo de los segundos.
  • AOT permite que el código se compile directamente en el código ARM nativo que conduce a un inicio rápido y un rendimiento predecible.

¿Qué es Firebase?

Firebase es una plataforma de desarrollo web y móvil que proporciona al desarrollador una amplia gama de productos. Hoy veremos cómo construir nuestra primera aplicación de aleteo con la autenticación de Firebase y la base de datos en tiempo real. Esta aplicación permite al usuario registrarse o iniciar sesión y realizar tareas CRUD con Firebase. En esta publicación, nos centraremos únicamente en la parte de registro e inicio de sesión del usuario.

Cómo configurar el entorno

  • Siga las instrucciones en este enlace
  • Obtenga el SDK de Flutter
  • Ejecute Flutter doctor para instalar cualquier dependencia
aleteo médico
  • Usa el siguiente comando para abrir el simulador de iOS
abierto -a Simulador
  • Para abrir el emulador de Android, inicie Android Studio> herramientas> Administrador AVD y seleccione crear dispositivo virtual.

Aplicación Building Flutter

Puede obtener el código fuente completo en el enlace de GitHub en la parte inferior de la publicación. A continuación se muestra cómo derivamos del proyecto de ejemplo Flutter para completar el código fuente en GitHub.

Paso 1: Cree un nuevo proyecto de flutter para llamar a la demostración de inicio de sesión de flutter. Inicie el simulador y ejecute el proyecto usando flutter. Puede usar Android Studio o VSCode como su IDE preferido. Pasos para configurar su editor aquí.

aleteo

Si tiene tanto el emulador de Android como el simulador de iOS en ejecución, ejecute el siguiente comando para ejecutar en ambos.

flutter run -d all

Debería ver pantallas similares en Android Emulator y iOS Simulator.

Izquierda: Android, Derecha: iOS
Si está interesado en saber cómo obtener capturas de pantalla en sus simuladores;
Para Android: simplemente haga clic en el icono de la cámara en el lado izquierdo del panel de herramientas. La imagen se guardará en el escritorio.
Para iOS: [Opción 1] Mantenga presionada la tecla Comando + Mayús + 4. Presione la barra espaciadora para cambiar el puntero del mouse al ícono de la cámara. Señale el simulador de iOS, haga clic para tomar una captura de pantalla. La imagen se guardará en el escritorio.

[Opción 2] Seleccione Simulador y presione el comando + S. Gracias JerryZhou por compartir esta información.

Paso 2: en main.dart, borre todo el contenido y agregue la siguiente plantilla a su archivo. Vamos a crear un nuevo archivo llamado login_page.dart que tiene la clase LoginPage. En su terminal, presione la tecla R para realizar una recarga en caliente y debería ver "Hello World" en la pantalla.

Main.dart

import 'paquete: flutter / material.dart';
importar 'login_signup_page.dart';

void main () => runApp (nuevo MyApp ());

La clase MyApp extiende StatelessWidget {
  
  @anular
  Compilación de widgets (contexto BuildContext) {
    devolver nuevo MaterialApp (
      título: 'Demostración de inicio de sesión de Flutter',
      tema: nuevo ThemeData (
        primarySwatch: Colors.blue,
      ),
      inicio: nuevo LoginSignUpPage ()
    );
  }
}

login_signup_page.dart

import 'paquete: flutter / material.dart';

La clase LoginSignUpPage extiende StatelessWidget {

  @anular
  Compilación de widgets (contexto BuildContext) {
    volver nuevo andamio (
      appBar: nueva AppBar (
        título: nuevo texto ("demostración de inicio de sesión de Flutter"),
      ),
      cuerpo: nuevo contenedor (
        hijo: nuevo texto ("Hola mundo"),
      ),
    );
  }
}

Paso 3: Cambio de apátrida a apátrida.

login_signup_page.dart

import 'paquete: flutter / material.dart';

La clase LoginSignUpPage extiende StatefulWidget {

  @anular
  Estado  createState () => new _LoginSignUpPageState ();

}

La clase _LoginSignUpPageState extiende el estado  {

  @anular
  Compilación de widgets (contexto BuildContext) {
    volver nuevo andamio (
      appBar: nueva AppBar (
        título: nuevo texto ("demostración de inicio de sesión de Flutter"),
      ),
      cuerpo: nuevo contenedor (
        hijo: nuevo texto ("Hola mundo"),
      ),
    );
  }
}

Paso 4: dentro del cuerpo del andamio, reemplacemos el texto de Hello Word a un formulario y dentro de él colocaremos un ListView. Un ListView toma una serie de widgets. Refactorizaremos cada componente de la interfaz de usuario en un widget separado.

Siempre que estemos usando el ingreso de texto, es mejor envolverlo alrededor de un ListView para evitar el error de representación cuando el teclado virtual aparece debido a píxeles de desbordamiento.

login_signup_page.dart

@anular
Compilación de widgets (contexto BuildContext) {
  _isIos = Theme.of (context) .platform == TargetPlatform.iOS;
  volver nuevo andamio (
      appBar: nueva AppBar (
        título: nuevo texto ('demostración de inicio de sesión de Flutter'),
      ),
      cuerpo: Pila (
        hijos:  [
          _muestra cuerpo(),
          _showCircularProgress (),
        ],
      ));
}

Paso 5: construcción de cada componente de la interfaz de usuario

Observe que en el cuerpo del andamio, tenemos un widget de pila como cuerpo. Básicamente, lo que quiero hacer es mostrar al usuario un indicador de carga circular, cuando se está ejecutando cualquier actividad de inicio de sesión o registro. Para hacer esto, necesitamos superponer un Indicador de Progreso Circular (afortunadamente, Flutter ya tiene este widget, así que para usarlo, simplemente llámelo) con nuestro diseño de widget principal (el formulario de inicio de sesión / registro). Esa es la función del widget Pila, que permite que un widget se superponga sobre otro widget. Para controlar si se debe mostrar CircularProgressIndicator o no, verificamos en bool _isLoading si ahora la pantalla se está cargando o no.

Widget _showCircularProgress () {
  if (_isLoading) {
    Centro de retorno (hijo: CircularProgressIndicator ());
  } return Container (altura: 0.0, ancho: 0.0,);

}

Para el logotipo, utilizaremos un widget de héroe y también para importar la imagen agregando la siguiente línea en su pubspec.yaml. Luego ejecute get paquetes para importar su imagen.

bienes:
  - assets / flutter-icon.png

login_signup_page.dart

Widget _showLogo () {
  volver nuevo héroe (
    etiqueta: 'héroe',
    hijo: relleno (
      relleno: EdgeInsets.fromLTRB (0.0, 70.0, 0.0, 0.0),
      hijo: CircleAvatar (
        backgroundColor: Colors.transparent,
        radio: 48.0,
        hijo: Image.asset ('assets / flutter-icon.png'),
      ),
    ),
  );
}

[Actualización] Anteriormente utilizamos un widget de espaciado flexible usando SizedBox que toma la entrada de altura para tener un espaciado vertical entre los 2 widgets. Ahora solo colocamos un widget dentro de un widget de relleno y usamos padding: EdgeInsets.fromLTRB () que significa desde la izquierda, arriba, derecha e inferior e ingresamos el valor del relleno en la posición correcta en consecuencia.

Luego viene nuestro campo de formulario de texto de correo electrónico y contraseña. Aviso para cada campo que tenemos validador y onSaved. Estas 2 devoluciones de llamada se activarán cuando se llame a form.validate () y form.save (). Entonces, por ejemplo, si se llama a form.save (), el valor en el campo de formulario de texto se copia en otra variable local.

También presentaremos un validador en nuestros campos para verificar si la entrada del campo está vacía y luego mostraremos una advertencia al usuario en rojo. También necesitamos crear las variables _email y _password para almacenar los valores. Para la contraseña, configuramos obsecureText: true para ocultar la contraseña del usuario.

Actualización: he agregado un método de recorte al valor del correo electrónico y la contraseña para eliminar los espacios en blanco iniciales o finales no intencionales.

Widget _showEmailInput () {
  acolchado de retorno (
    relleno: const EdgeInsets.fromLTRB (0.0, 100.0, 0.0, 0.0),
    hijo: nuevo TextFormField (
      maxLines: 1,
      keyboardType: TextInputType.emailAddress,
      enfoque automático: falso
      decoración: nueva InputDecoration (
          hintText: 'Correo electrónico',
          icono: nuevo icono (
            Icons.mail,
            color: Colors.grey,
          )),
      validador: (valor) => valor.es Vacío? 'El correo electrónico no puede estar vacío': nulo,
      onSaved: (value) => _email = value.trim (),
    ),
  );
}

Widget _showPasswordInput () {
  acolchado de retorno (
    relleno: const EdgeInsets.fromLTRB (0.0, 15.0, 0.0, 0.0),
    hijo: nuevo TextFormField (
      maxLines: 1,
      obscureText: verdadero,
      enfoque automático: falso
      decoración: nueva InputDecoration (
          hintText: 'Contraseña',
          icono: nuevo icono (
            Icons.lock,
            color: Colors.grey,
          )),
      validador: (valor) => valor.es Vacío? 'La contraseña no puede estar vacía': nulo,
      onSaved: (value) => _password = value.trim (),
    ),
  );
}

A continuación, debemos agregar el botón principal, pero debería poder mostrar el texto correcto dependiendo de si el usuario desea registrarse para una cuenta nueva o iniciar sesión con una cuenta existente. Para esto, necesitamos crear una enumeración para realizar un seguimiento si el formulario es para iniciar sesión o registrarse.

enum FormMode {INICIAR SESIÓN}

Asignaremos un método para la función de devolución de llamada de botón. Para esto, crearemos un método llamado _validateAndSubmit que pasará tanto el correo electrónico como la contraseña para la autenticación de Firebase. Más sobre eso más adelante en este post.

Widget _showPrimaryButton () {
  volver nuevo relleno (
      relleno: EdgeInsets.fromLTRB (0.0, 45.0, 0.0, 0.0),
      hijo: nuevo MaterialButton (
        elevación: 5.0,
        minWidth: 200.0,
        altura: 42.0,
        color: colores.azul,
        hijo: _formMode == FormMode.LOGIN
            ? Nuevo texto ('Iniciar sesión',
                estilo: nuevo TextStyle (fontSize: 20.0, color: Colors.white))
            : nuevo texto ('Crear cuenta',
                estilo: nuevo TextStyle (fontSize: 20.0, color: Colors.white)),
        onPressed: _validateAndSubmit,
      ));
}

Ahora necesitamos agregar un botón secundario para que el usuario pueda alternar entre el formulario de registro y el de inicio de sesión. En el método onPressed, nos gustaría alternar el estado del formulario entre LOGIN y SIGNUP. Aviso para el botón secundario, estamos usando FlatButton en lugar de RaisedButton como el botón de envío anterior. La razón es que si tiene 2 botones y desea hacer que uno sea más distintivo que el otro, RaisedButton es la opción correcta, ya que capta instantáneamente la atención de los usuarios en comparación con FlatButton.

login_page.dart

Widget _showSecondaryButton () {
  volver nuevo FlatButton (
    hijo: _formMode == FormMode.LOGIN
        ? Texto nuevo ('Crear una cuenta',
            estilo: nuevo TextStyle (fontSize: 18.0, fontWeight: FontWeight.w300))
        : nuevo texto ('¿Tienes una cuenta? Iniciar sesión',
            estilo:
                nuevo TextStyle (fontSize: 18.0, fontWeight: FontWeight.w300)),
    onPressed: _formMode == FormMode.LOGIN
        ? _changeFormToSignUp
        : _changeFormToLogin,
  );
}

En el método para alternar el modo de formulario, es crucial envolverlo alrededor de setState ya que necesitamos decirle a Flutter que vuelva a renderizar la pantalla con el valor actualizado del FormMode.

void _changeFormToSignUp () {
  _formKey.currentState.reset ();
  _errorMessage = "";
  setState (() {
    _formMode = FormMode.SIGNUP;
  });
}

void _changeFormToLogin () {
  _formKey.currentState.reset ();
  _errorMessage = "";
  setState (() {
    _formMode = FormMode.LOGIN;
  });
}

A continuación, vamos a tener un _showErrorMessage () que pasa el mensaje de error al usuario desde el lado de Firebase cuando intentan iniciar sesión o registrarse. Este mensaje de error podría ser algo así como "Ya existe una cuenta de usuario existente". Entonces vamos a tener un String _errorMessage para almacenar el mensaje de error de Firebase.

Widget _showErrorMessage () {
  if (_errorMessage.length> 0 && _errorMessage! = null) {
    devolver texto nuevo (
      _mensaje de error,
      estilo: TextStyle (
          fontSize: 13.0,
          color: Colors.red,
          altura: 1.0,
          fontWeight: FontWeight.w300),
    );
  } más {
    devolver nuevo contenedor (
      altura: 0.0,
    );
  }
}

Finalmente, organicemos esos componentes individuales de la interfaz de usuario y regresemos a nuestro ListView.

Widget _showBody () {
  devolver nuevo contenedor (
      relleno: EdgeInsets.all (16.0),
      hijo: nuevo formulario (
        clave: _formKey,
        hijo: nuevo ListView (
          shrinkWrap: verdadero,
          hijos:  [
            _showLogo (),
            _showEmailInput (),
            _showPasswordInput (),
            _showPrimaryButton (),
            _showSecondaryButton (),
            _showErrorMessage (),
          ],
        ),
      ));
}
Validador TextFormField en acción

Paso 6: registre un nuevo proyecto con Firebase

Vaya a https://console.firebase.google.com y registre un nuevo proyecto.

Para Android, haga clic en el ícono de Android. Ingrese el nombre de su paquete que se puede encontrar en android / app / src / main / AndroidManifest.xml

Descargue el archivo de configuración que es google-services.json (Android).

Arrastre google-services.json a la carpeta de la aplicación en la vista de proyecto

Necesitamos agregar el complemento Google Services Gradle para leer google-services.json. En /android/app/build.gradle agregue lo siguiente a la última línea del archivo.

aplique el complemento: 'com.google.gms.google-services'

En android / build.gradle, dentro de la etiqueta buildscript, agregue una nueva dependencia.

buildscript {
   repositorios {
      // ...
}
dependencias {
   // ...
   classpath 'com.google.gms: google-services: 3.2.1'
}

Para iOS, abra ios / Runner.xcworkspace para iniciar Xcode. El nombre del paquete se puede encontrar en el identificador de paquete en la vista Runner.

Descargue el archivo de configuración que es GoogleService-info.plist (iOS).

Arrastre GoogleService-info.plist a la subcarpeta Runner dentro de Runner como se muestra a continuación.

Paso 7: Agregar dependencias en pubspec.yaml
A continuación, debemos agregar la dependencia firebase_auth en pubspec.yaml. Para obtener el último número de versión, vaya a https://pub.dartlang.org/ y busque firebase auth.

firebase_auth: ^ 0.6.6

Paso 8: Importar autenticación de Firebase

import 'paquete: firebase_auth / firebase_auth.dart';

Paso 9: habilite el registro mediante correo electrónico y contraseña en Firebase

Paso 10: Inicie sesión en Firebase

Firebase signInWithEmailAndPassword es un método que devuelve un valor futuro. Por lo tanto, el método debe esperar y la función de envoltura externa debe tener asíncrono. Así que adjuntamos los métodos de inicio de sesión y registro con try catch block. Si hubo un error, nuestro bloque catch debería poder capturar el mensaje de error y mostrarlo al usuario.

Hay una diferencia en cómo se almacena el mensaje real en el error arrojado por Firebase. En IOS, el mensaje está en e.details, mientras que en Android está en e.message. Puede verificar fácilmente la plataforma utilizando _isIos = Theme.of (context) .platform == TargetPlatform.iOS y debería estar dentro de cualquiera de los métodos de compilación del widget porque necesita un contexto.

_validateAndSubmit () async {
  setState (() {
    _errorMessage = "";
    _isLoading = true;
  });
  if (_validateAndSave ()) {
    String userId = "";
    tratar {
      if (_formMode == FormMode.LOGIN) {
        userId = await widget.auth.signIn (_email, _password);
        print ('Ingresado: $ userId');
      } más {
        userId = await widget.auth.signUp (_email, _password);
        print ('Usuario registrado: $ userId');
      }
      if (userId.length> 0 && userId! = null) {
        widget.onSignedIn ();
      }
    } captura (e) {
      print ('Error: $ e');
      setState (() {
        _isLoading = false;
        if (_isIos) {
          _errorMessage = e.details;
        } más
          _errorMessage = e.message;
      });
    }
  }
}

Paso 11: Borrar el campo de formulario al alternar

Necesitamos agregar la siguiente línea tanto en _changeFormToSignUp como en _changeFormToLogin para restablecer el campo del formulario cada vez que el usuario cambia entre el formulario de inicio de sesión y el de registro.

formKey.currentState.reset ();

Paso 12: intente registrar un usuario

Intentemos registrar un usuario ingresando un correo electrónico y una contraseña.

Si encuentra algo como a continuación, esto se debe a que hay un espacio adicional al final de su correo electrónico
I / flutter (14294): Error PlatformException (excepción, la dirección de correo electrónico está mal formateada., Nulo)
Si encuentra algo como a continuación, cambie su contraseña para que tenga al menos 6 caracteres de longitud.
I / flutter (14294): Error PlatformException (excepción, la contraseña dada no es válida. [La contraseña debe tener al menos 6 caracteres], nulo)

Finalmente, una vez que tenga éxito, debería poder ver en su terminal la siguiente línea. La cadena aleatoria es la ID de usuario.

I / flutter (14294): se inscribió JSwpKsCFxPZHEqeuIO4axCsmWuP2

Del mismo modo, si tratamos de iniciar sesión en el mismo usuario que registramos, deberíamos obtener algo como esto:

I / flutter (14294): firmado en JSwpKsCFxPZHEqeuIO4axCsmWuP2

Paso 13: Implemente la clase Auth

Crear un nuevo archivo de autenticación .dart. También vamos a implementar la clase abstracta BaseAuth. El propósito de esta clase abstracta es que actúa como una capa intermedia entre nuestros componentes de la interfaz de usuario y la clase de implementación real que depende del marco que elijamos. En cualquier caso, decidimos cambiar Firebase a algo como PostgreSQL, entonces no tendría impacto en los componentes de la interfaz de usuario.

importar 'dart: async';
import 'paquete: firebase_auth / firebase_auth.dart';

clase abstracta BaseAuth {
  Inicio de sesión futuro  (correo electrónico de cadena, contraseña de cadena);
  Inscripción futura en  (correo electrónico de cadena, contraseña de cadena);
  Futuro  getCurrentUser ();
  Futuro  signOut ();
}

La clase Auth implementa BaseAuth {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;

  Inicio de sesión futuro  (correo electrónico de cadena, contraseña de cadena) asíncrono {
    Usuario de FirebaseUser = await _firebaseAuth.signInWithEmailAndPassword (correo electrónico: correo electrónico, contraseña: contraseña);
    return user.uid;
  }

  Registro futuro en  (correo electrónico de cadena, contraseña de cadena) asíncrono {
    Usuario de FirebaseUser = await _firebaseAuth.createUserWithEmailAndPassword (correo electrónico: correo electrónico, contraseña: contraseña);
    return user.uid;
  }

  Futuro  getCurrentUser () async {
    Usuario de FirebaseUser = await _firebaseAuth.currentUser ();
    return user.uid;
  }

  Futuro  signOut () async {
    return _firebaseAuth.signOut ();
  }
}

En login_page.dart

La clase LoginSignUpPage extiende StatefulWidget {
LoginSignUpPage ({this.auth});
autenticación final de BaseAuth;
@anular
Estado  createState () => new _LoginPageState ();
}

En main.dart

inicio: nuevo LoginSignUpPage (auth: nuevo Auth ())

De vuelta en login_page.dart, intercambiemos nuestro inicio de sesión con correo electrónico y contraseña

String userId = await widget.auth.signIn (_email, _password);
String userId = await widget.auth.signUp (_email, _password);

Paso 14: raíz y hogar con VoidCallback

Creemos un nuevo archivo llamado home_page.dart. Esto aparecerá vacío para hacer la lista después de que el usuario haya iniciado sesión o registrado correctamente. Como de costumbre, implementamos Scaffold con AppBar pero esta vez vamos a tener un FlatButton dentro de AppBar para la función de cierre de sesión. Este cierre de sesión llama al método de cierre de sesión de Firebase dentro de la clase BaseAuth.

También necesitamos crear un archivo llamado root_page.dart. Esto reemplazará a home: LoginSignUpPage (auth: new Auth ()) en nuestro main.dart.

inicio: nueva página raíz (autenticación: nueva autenticación ())

Cuando se inicia la aplicación, debe navegar a esta página. Esta página actúa como administrador para verificar la identificación de usuario válida de Firebase y los dirige a la página correspondiente de acuerdo. Por ejemplo, si la identificación de usuario está presente, lo que significa que el usuario ya ha iniciado sesión y debería mostrarse la página de inicio en lugar de la página de inicio de sesión. Esto se hará dentro de initState, que es la función que se ejecutará primero en el archivo.

En la página raíz, habrá 2 métodos, que son _onLoggedIn y _onSignedOut. En _onLoggedIn, tratamos de obtener la identificación del usuario y establecer el estado de autenticación del usuario para que el usuario ya haya iniciado sesión. En _onSignedOut, borramos la identificación de usuario almacenada y establecer el estado de autenticación del usuario para que el usuario no haya iniciado sesión.

En root_page, pasamos 2 parámetros a login_page, uno es la clase Auth que implementamos más fácilmente (lo instanciamos en main.dart) y el método _onLoggedIn). En la página login_signup_, creamos 2 variables que son auth del tipo BaseAuth y onSignedIn del tipo VoidCallback. Podemos recuperar fácilmente los 2 parámetros pasados ​​en login_signup_page en nuestras variables locales usando la siguiente línea.

LoginSignUpPage ({this.auth, this.onSignedIn});

autenticación final de BaseAuth;
VoidCallback final onSignedIn;

VoidCallback permite que login_signup_page llame al método dentro de root_page, que es _onSignedIn cuando el usuario inicia sesión. Cuando se llama a _onSignedIn, establecerá authStatus en LOGGED_IN y setState para volver a dibujar la aplicación. Cuando se redibuja la aplicación, initState verifica el estado de auth y, dado que es LOGGED_IN, mostrará home_page, pasando auth y voidcallback de _signOut.

root_page.dart

@anular
Compilación de widgets (contexto BuildContext) {
  conmutador (authStatus) {
    case AuthStatus.NOT_DETERMINED:
      return _buildWaitingScreen ();
      rotura;
    case AuthStatus.NOT_LOGGED_IN:
      volver nuevo LoginSignUpPage (
        auth: widget.auth,
        onSignedIn: _onLoggedIn,
      );
      rotura;
    case AuthStatus.LOGGED_IN:
      if (_userId.length> 0 && _userId! = null) {
        volver nueva página de inicio (
          userId: _userId,
          auth: widget.auth,
          onSignedOut: _onSignedOut,
        );
      } else return _buildWaitingScreen ();
      rotura;
    defecto:
      return _buildWaitingScreen ();
  }
}

Observe la cinta de depuración en la esquina superior derecha de la aplicación, puede eliminarla fácilmente agregando la siguiente línea dentro del widget MaterialApp en main.dart

Pantalla de inicio de sesión de demostración
debugShowCheckedModeBanner: falso,
Banner de depuración eliminado

Puede obtener el código fuente completo en el enlace de github a continuación

Si encuentra que este artículo es útil, dele algo de

Referencia:

El Flutter Pub es una publicación mediana que le ofrece los recursos más recientes y sorprendentes, como artículos, videos, códigos, podcasts, etc. sobre esta gran tecnología para enseñarle cómo crear hermosas aplicaciones con ella. Puede encontrarnos en Facebook, Twitter y Medium u obtener más información sobre nosotros aquí. ¡Nos encantaría conectarnos! Y si usted es un escritor interesado en escribir para nosotros, puede hacerlo a través de estas pautas.