Flutter: come eseguire l'accesso utente con Firebase

pubblicazione

25/12/18 - Aggiornato lo snippet di codice aggiornato dopo il refactoring e la pulizia.

24/01/19 - Link duplicato a github nella parte superiore dell'articolo.

23/07/19 - Aggiunto il metodo trim al valore e-mail e password

Codice sorgente

Se vuoi saltare l'intero jumbo di mumbo, puoi prendere il codice sorgente qui

https://github.com/tattwei46/flutter_login_demo

Aggiornare

Ecco un sequel di questo post che è Come fare CRUD con Firebase RTDB. Controlla!

Che cos'è Flutter?

Flutter è un SDK mobile open source sviluppato da Google per creare applicazioni di alta qualità per Android e iOS. Consente agli sviluppatori non solo di creare applicazioni con un design accattivante, animazioni fluide e prestazioni veloci, ma anche di integrare rapidamente nuove funzionalità. Flutter offre uno sviluppo ad alta velocità con il suo stato di ricarica a caldo e il riavvio a caldo. Con una sola base di codice da gestire, puoi risparmiare molto rispetto alla gestione di progetti Android e iOS mentre Flutter lo compila in codice ARM nativo. Flutter utilizza il linguaggio di programmazione Dart, anch'esso sviluppato da Google.

Perché Dart

  • Un linguaggio conciso, fortemente tipizzato, orientato agli oggetti.
  • Supporta la compilazione just-in-time e anticipata.
  • JIT consente a flutter di ricompilare il codice direttamente sul dispositivo mentre l'app è ancora in esecuzione.
  • Consentire uno sviluppo rapido e abilitare il ricaricamento a caldo stabile in secondi.
  • AOT consente di compilare il codice direttamente nel codice ARM nativo che consente un avvio rapido e prestazioni prevedibili.

Cos'è Firebase

Firebase è una piattaforma di sviluppo Web e mobile che offre agli sviluppatori un'ampia gamma di prodotti. Oggi vedremo come costruire la nostra prima applicazione flutter con autenticazione Firebase e database in tempo reale. Questa applicazione consente all'utente di registrarsi o accedere ed eseguire todo elementi CRUD con Firebase. In questo post, ci concentreremo esclusivamente sulla parte di iscrizione e accesso dell'utente.

Come impostare l'ambiente

  • Segui le istruzioni in questo link
  • Ottieni l'SDK di Flutter
  • Esegui il medico Flutter per installare eventuali dipendenze
dottore svolazzante
  • Utilizzare il comando seguente per aprire il simulatore iOS
open -a Simulator
  • Per aprire l'emulatore Android, avvia Android Studio> strumenti> AVD Manager e seleziona Crea dispositivo virtuale.

Creazione dell'app Flutter

Puoi ottenere il codice sorgente completo nel link GitHub in fondo al post. Quanto segue mostra come deriviamo dal progetto di esempio Flutter per completare il codice sorgente in GitHub.

Passo 1: creare una nuova demo di accesso al flutter di chiamata al progetto flutter. Avviare il simulatore ed eseguire il progetto utilizzando Flutter. Puoi utilizzare Android Studio o VSCode come IDE preferito. I passaggi per configurare il tuo editor qui.

corsa svolazzante

Se hai sia l'emulatore Android che il simulatore iOS in esecuzione, esegui il comando seguente per eseguire su entrambi.

corsa svolazzante -d tutto

Dovresti vedere schermate simili sia sull'emulatore Android sia sul simulatore iOS.

Sinistra: Android, Destra: iOS
Se sei interessato a sapere come ottenere screenshot dai tuoi simulatori;
Per Android: è sufficiente fare clic sull'icona della fotocamera sul lato sinistro del riquadro degli strumenti. L'immagine verrà salvata sul desktop
Per iOS: [Opzione 1] Tieni premuto e premi comando + MAIUSC + 4. Premi la barra spaziatrice per cambiare il puntatore del mouse sull'icona della fotocamera. Puntare al simulatore iOS, fare clic per acquisire uno screenshot. L'immagine verrà salvata sul desktop.

[Opzione 2] Seleziona Simulatore e premi comando + S. Grazie JerryZhou per aver condiviso queste informazioni.

Passo 2: in main.dart, cancella tutti i contenuti e aggiungi la seguente piastra di caldaia al tuo file. Creeremo un nuovo file chiamato login_page.dart che ha la classe LoginPage. Sul tuo terminale, premi il tasto R per eseguire la ricarica a caldo e dovresti vedere "Hello World" sullo schermo.

Main.dart

import 'pacchetto: flutter / material.dart';
import 'login_signup_page.dart';

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

la classe MyApp estende StatelessWidget {
  
  @oltrepassare
  Widget build (contesto BuildContext) {
    restituire nuovo MaterialApp (
      titolo: "Flutter Login Demo",
      tema: nuovo ThemeData (
        primarySwatch: Colors.blue,
      ),
      home: nuovo LoginSignUpPage ()
    );
  }
}

login_signup_page.dart

import 'pacchetto: flutter / material.dart';

la classe LoginSignUpPage estende StatelessWidget {

  @oltrepassare
  Widget build (contesto BuildContext) {
    restituire il nuovo Scaffold (
      AppBar: nuova AppBar (
        titolo: nuovo testo ("Flutter login demo"),
      ),
      body: new Container (
        figlio: nuovo testo ("Hello World"),
      ),
    );
  }
}

Fase 3: passaggio da apolide a stato.

login_signup_page.dart

import 'pacchetto: flutter / material.dart';

la classe LoginSignUpPage estende StatefulWidget {

  @oltrepassare
  State  createState () => new _LoginSignUpPageState ();

}

classe _LoginSignUpPageState estende lo stato  {

  @oltrepassare
  Widget build (contesto BuildContext) {
    restituire il nuovo Scaffold (
      AppBar: nuova AppBar (
        titolo: nuovo testo ("Flutter login demo"),
      ),
      body: new Container (
        figlio: nuovo testo ("Hello World"),
      ),
    );
  }
}

Passo 4: all'interno del corpo dell'impalcatura, sostituiamo il testo di Hello Word in un modulo e al suo interno inseriremo un ListView. Un ListView accetta una serie di widget. Rifattorizzeremo ogni componente dell'interfaccia utente in un widget separato.

Ogni volta che utilizziamo l'inserimento di testo, è meglio avvolgerlo in un ListView per evitare errori di rendering quando viene visualizzata la tastiera software a causa di pixel di overflow.

login_signup_page.dart

@oltrepassare
Widget build (contesto BuildContext) {
  _isIos = Theme.of (context) .platform == TargetPlatform.iOS;
  restituire il nuovo Scaffold (
      AppBar: nuova AppBar (
        titolo: nuovo testo ("Flutter login demo"),
      ),
      body: Stack (
        figli:  [
          _showBody (),
          _showCircularProgress (),
        ],
      ));
}

Fase 5: creazione di ciascun componente dell'interfaccia utente

Si noti nel corpo dell'impalcatura, abbiamo un widget Stack come corpo. Fondamentalmente quello che voglio fare è mostrare all'utente un indicatore di caricamento circolare, quando è in corso qualsiasi attività di accesso o registrazione. Per fare questo, abbiamo bisogno di sovrapporre un CircularProgressIndicator (per fortuna, Flutter ha già questo widget, quindi per usarlo, basta semplicemente chiamarlo) con il nostro layout principale del widget (il modulo di login / iscrizione). Questa è la funzione del widget Stack, che consente a un widget di sovrapporsi a un altro widget. Per controllare se mostrare o meno l'indicatore CircularProgress, controlliamo bool _isLoading se ora lo schermo si sta caricando o meno.

Widget _showCircularProgress () {
  if (_isLoading) {
    centro di restituzione (figlio: CircularProgressIndicator ());
  } Contenitore di ritorno (altezza: 0,0, larghezza: 0,0,);

}

Per il logo, utilizzeremo un widget eroe e anche per importare l'immagine aggiungendo la seguente riga nel tuo pubspec.yaml. Quindi esegui get pacchetti per importare la tua immagine.

risorse:
  - assets / flutter-icon.png

login_signup_page.dart

Widget _showLogo () {
  return new Hero (
    tag: 'eroe',
    bambino: imbottitura (
      imbottitura: EdgeInsets.fromLTRB (0.0, 70.0, 0.0, 0.0),
      figlio: CircleAvatar (
        backgroundColor: Colors.transparent,
        raggio: 48,0,
        figlio: Image.asset ('assets / flutter-icon.png'),
      ),
    ),
  );
}

[Aggiorna] In precedenza utilizzavamo widget di spaziatura flessibile utilizzando SizedBox che accetta input di altezza per avere una spaziatura verticale tra i 2 widget. Ora inseriamo un solo widget all'interno di un widget di padding e utilizziamo padding: EdgeInsets.fromLTRB () che significa da sinistra, in alto, a destra e in basso e immettiamo il valore del padding nella posizione corretta di conseguenza.

Successivamente viene il nostro campo di testo e-mail e password. Avviso per ogni campo che abbiamo validatore e onSaved. Questi 2 callback verranno attivati ​​quando viene chiamato form.validate () e form.save (). Ad esempio, se viene chiamato form.save (), il valore nel campo del modulo di testo viene copiato in un'altra variabile locale.

Introdurremo anche un validatore nei nostri campi per verificare se l'input del campo è vuoto e quindi mostreremo un avviso all'utente in rosso. Dobbiamo anche creare le variabili _email e _password per memorizzare i valori. Per la password, impostiamo obsecureText: true per nascondere la password dell'utente.

Aggiornamento: ho aggiunto il metodo di taglio al valore sia della posta elettronica che della password per rimuovere gli spazi bianchi iniziali o finali non intenzionali.

Widget _showEmailInput () {
  ritorno imbottitura (
    imbottitura: const EdgeInsets.fromLTRB (0.0, 100.0, 0.0, 0.0),
    figlio: nuovo TextFormField (
      maxLines: 1,
      keyboardType: TextInputType.emailAddress,
      autofocus: falso,
      decorazione: nuovo InputDecoration (
          suggerimento Testo: "Email",
          icona: nuova icona (
            Icons.mail,
            colore: Colors.grey,
          )),
      validatore: (value) => value.isEmpty? "L'email non può essere vuota": null,
      onSaved: (value) => _email = value.trim (),
    ),
  );
}

Widget _showPasswordInput () {
  ritorno imbottitura (
    imbottitura: const EdgeInsets.fromLTRB (0.0, 15.0, 0.0, 0.0),
    figlio: nuovo TextFormField (
      maxLines: 1,
      obscureText: true,
      autofocus: falso,
      decorazione: nuovo InputDecoration (
          hintText: 'Password',
          icona: nuova icona (
            Icons.lock,
            colore: Colors.grey,
          )),
      validatore: (value) => value.isEmpty? 'La password non può essere vuota': null,
      onSaved: (value) => _password = value.trim (),
    ),
  );
}

Successivamente è necessario aggiungere il pulsante principale, ma dovrebbe essere in grado di visualizzare il testo corretto a seconda che l'utente desideri registrarsi per un nuovo account o accedere con un account esistente. Per questo, dobbiamo creare un enum per tenere traccia se il modulo è per il login o la registrazione.

enum FormMode {ACCEDI, REGISTRATI}

Assegneremo un metodo per la funzione di richiamata dei pulsanti. Per questo, creeremo un metodo chiamato _validateAndSubmit che passerà sia nell'email che nella password per l'autenticazione Firebase. Maggiori informazioni più avanti in questo post.

Widget _showPrimaryButton () {
  return new Padding (
      imbottitura: EdgeInsets.fromLTRB (0.0, 45.0, 0.0, 0.0),
      figlio: nuovo MaterialButton (
        elevazione: 5.0,
        minWidth: 200.0,
        altezza: 42,0,
        colore: Colors.blue,
        figlio: _formMode == FormMode.LOGIN
            ? nuovo testo ("Login",
                stile: nuovo TextStyle (fontSize: 20.0, colore: Colors.white))
            : nuovo testo ("Crea account",
                stile: new TextStyle (fontSize: 20.0, color: Colors.white)),
        onPressed: _validateAndSubmit,
      ));
}

Ora dobbiamo aggiungere un pulsante secondario per consentire all'utente di alternare tra il modulo di iscrizione e quello di accesso. Sul metodo onPressed, vorremmo alternare lo stato del modulo tra LOGIN e SIGNUP. Avviso per il pulsante secondario, stiamo usando FlatButton invece di RaisedButton come il pulsante di invio precedente. Il motivo è che se hai 2 pulsanti e desideri renderne uno più distintivo dell'altro, RaisedButton è la scelta giusta in quanto cattura immediatamente l'attenzione degli utenti rispetto a FlatButton.

login_page.dart

Widget _showSecondaryButton () {
  ritorna nuovo FlatButton (
    figlio: _formMode == FormMode.LOGIN
        ? nuovo testo ("Crea un account",
            stile: nuovo TextStyle (fontSize: 18.0, fontWeight: FontWeight.w300))
        : nuovo testo ("Hai un account? Accedi",
            stile:
                nuovo TextStyle (fontSize: 18.0, fontWeight: FontWeight.w300)),
    onPressed: _formMode == FormMode.LOGIN
        ? _changeFormToSignUp
        : _changeFormToLogin,
  );
}

Sul metodo per attivare / disattivare la modalità modulo, è fondamentale avvolgerlo attorno a setState poiché è necessario dire a Flutter di eseguire nuovamente il rendering dello schermo con il valore aggiornato di FormMode.

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

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

Successivamente, avremo un _showErrorMessage () che passerà il messaggio di errore all'utente dal lato Firebase quando stanno tentando di eseguire il login o l'iscrizione. Questo messaggio di errore potrebbe essere simile a "Esiste già un account utente esistente". Quindi avremo un String _errorMessage per memorizzare il messaggio di errore da Firebase.

Widget _showErrorMessage () {
  if (_errorMessage.length> 0 && _errorMessage! = null) {
    ritorna nuovo testo (
      _messaggio di errore,
      stile: TextStyle (
          fontSize: 13.0,
          color: Colors.red,
          altezza: 1.0,
          fontWeight: FontWeight.w300),
    );
  } altro {
    return new Container (
      altezza: 0,0,
    );
  }
}

Infine, sistemiamo i singoli componenti dell'interfaccia utente e rimettiamoli sul nostro ListView.

Widget _showBody () {
  return new Container (
      imbottitura: EdgeInsets.all (16.0),
      figlio: nuovo modulo (
        chiave: _formKey,
        figlio: nuovo ListView (
          shrinkWrap: true,
          figli:  [
            _showLogo (),
            _showEmailInput (),
            _showPasswordInput (),
            _showPrimaryButton (),
            _showSecondaryButton (),
            _showErrorMessage (),
          ],
        ),
      ));
}
Validatore TextFormField in azione

Fase 6: registra il nuovo progetto con Firebase

Vai su https://console.firebase.google.com e registra un nuovo progetto.

Per Android, fai clic sull'icona Android. Inserisci il nome del pacchetto che puoi trovare in android / app / src / main / AndroidManifest.xml

Scarica il file di configurazione che è google-services.json (Android).

Trascina google-services.json nella cartella dell'app nella vista progetto

Dobbiamo aggiungere il plug-in Gradle dei servizi di Google per leggere google-services.json. In /android/app/build.gradle aggiungi quanto segue all'ultima riga del file.

applica plug-in: "com.google.gms.google-services"

In android / build.gradle, all'interno del tag buildscript, aggiungi una nuova dipendenza.

buildscript {
   repository {
      // ...
}
dipendenze {
   // ...
   percorso di classe "com.google.gms: google-services: 3.2.1"
}

Per iOS, apri ios / Runner.xcworkspace per avviare Xcode. Il nome del pacchetto si trova nell'identificatore del bundle nella vista Runner.

Scarica il file di configurazione che è GoogleService-info.plist (iOS).

Trascina GoogleService-info.plist nella sottocartella Runner all'interno di Runner come mostrato di seguito.

7 Passo 7: aggiungere dipendenze in pubspec.yaml
Quindi dobbiamo aggiungere la dipendenza firebase_auth in pubspec.yaml. Per ottenere il numero di versione più recente, visitare https://pub.dartlang.org/ e cercare l'autenticazione firebase.

firebase_auth: ^ 0.6.6

Fase 8: Importa autenticazione Firebase

import 'pacchetto: firebase_auth / firebase_auth.dart';

Passo 9: abilitare la registrazione tramite e-mail e password su Firebase

TeFase 10: Accedi a Firebase

Firebase signInWithEmailAndPassword è un metodo che restituisce un valore futuro. Quindi il metodo deve essere in attesa e la funzione wrapper esterna deve essere asincrona. Quindi alleghiamo i metodi di accesso e registrazione con try catch block. Se si è verificato un errore, il nostro blocco catch dovrebbe essere in grado di acquisire il messaggio di errore e mostrarlo all'utente.

Esiste una differenza nella modalità di archiviazione del messaggio effettivo nell'errore generato da Firebase. In IOS, il messaggio è in e.details mentre per Android è in e.message. Puoi facilmente controllare la piattaforma usando _isIos = Theme.of (context) .platform == TargetPlatform.iOS e dovrebbe trovarsi all'interno di qualsiasi metodo del widget di build perché ha bisogno di un contesto.

_validateAndSubmit () async {
  setState (() {
    _errorMessage = "";
    _isLoading = true;
  });
  if (_validateAndSave ()) {
    String userId = "";
    provare {
      if (_formMode == FormMode.LOGIN) {
        userId = await widget.auth.signIn (_email, _password);
        print ("Accesso: $ userId");
      } altro {
        userId = await widget.auth.signUp (_email, _password);
        print ('Utente registrato: $ userId');
      }
      if (userId.length> 0 && userId! = null) {
        widget.onSignedIn ();
      }
    } cattura (e) {
      print ('Errore: $ e');
      setState (() {
        _isLoading = false;
        if (_isIos) {
          _errorMessage = e.details;
        } altro
          _errorMessage = e.message;
      });
    }
  }
}

TeFase 11: Cancella il campo modulo quando si attiva / disattiva

È necessario aggiungere la seguente riga in _changeFormToSignUp e _changeFormToLogin per reimpostare il campo modulo ogni volta che l'utente passa dal login al modulo di iscrizione.

formKey.currentState.reset ();

Fase 12: prova ad iscrivere un utente

Proviamo a iscrivere un utente inserendo un indirizzo email e una password.

Se incontri qualcosa di simile al di sotto, questo è perché c'è una spaziatura aggiuntiva alla fine della tua e-mail
I / flutter (14294): Errore PlatformException (eccezione, l'indirizzo e-mail è formattato in modo errato., Null)
Se incontri qualcosa di simile al di sotto, modifica la password in modo che sia lunga almeno 6 caratteri.
I / flutter (14294): Errore PlatformException (eccezione, la password fornita non è valida. [La password deve contenere almeno 6 caratteri], null)

Alla fine, una volta completato, dovresti essere in grado di vedere nel tuo terminale la seguente riga. La stringa casuale è l'ID utente.

I / flutter (14294): registrato JSwpKsCFxPZHEqeuIO4axCsmWuP2

Allo stesso modo se proviamo ad accedere allo stesso utente a cui ci siamo registrati, dovremmo ottenere qualcosa del genere:

I / flutter (14294): accesso JSwpKsCFxPZHEqeuIO4axCsmWuP2

Fase 13: Implementare la classe Auth

Crea una nuova chiamata di file authentication.dart. Implementeremo anche la classe astratta BaseAuth. Scopo di questa classe astratta è che funge da strato intermedio tra i nostri componenti dell'interfaccia utente e la classe di implementazione effettiva che dipende dal framework che scegliamo. In ogni caso, abbiamo deciso di scambiare Firebase con qualcosa come PostgreSQL, quindi non avrebbe avuto alcun impatto sui componenti dell'interfaccia utente.

import 'dart: async';
import 'pacchetto: firebase_auth / firebase_auth.dart';

classe astratta BaseAuth {
  Future  signIn (String email, String password);
  Future  signUp (String email, String password);
  Future  getCurrentUser ();
  Future  signOut ();
}

class Auth implementa BaseAuth {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;

  Future  signIn (String email, String password) async {
    Utente FirebaseUser = attende _firebaseAuth.signInWithEmailAndPassword (email: email, password: password);
    return user.uid;
  }

  Future  signUp (String email, String password) async {
    Utente FirebaseUser = attende _firebaseAuth.createUserWithEmailAndPassword (email: email, password: password);
    return user.uid;
  }

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

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

In login_page.dart

la classe LoginSignUpPage estende StatefulWidget {
LoginSignUpPage ({this.auth});
autenticazione finale BaseAuth;
@oltrepassare
State  createState () => new _LoginPageState ();
}

In main.dart

home: nuovo LoginSignUpPage (auth: new Auth ())

Di nuovo in login_page.dart, scambiamo il nostro signInWithEmailAndPassword

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

Fase 14: Root e home con VoidCallback

Creiamo un nuovo file chiamato home_page.dart. Verrà visualizzato un elenco di attività vuoto dopo che l'utente ha effettuato correttamente l'accesso o registrato. Come al solito, implementiamo Scaffold con AppBar ma questa volta avremo un FlatButton all'interno di AppBar per la funzione di logout. Questo logout chiama il metodo signout di Firebase all'interno della classe BaseAuth.

Dobbiamo anche creare una chiamata al file root_page.dart. Questo sostituirà home: LoginSignUpPage (auth: new Auth ()) nel nostro main.dart.

home: new RootPage (auth: new Auth ())

All'avvio dell'app, dovrebbe accedere a questa pagina. Questa pagina funge da gestore per verificare l'id utente Firebase valido e indirizzarli alla pagina appropriata in base. Ad esempio, se l'ID utente è presente, il che significa che l'utente è già connesso e l'utente dovrebbe mostrare la home_page anziché login_signup_page. Questo sarà fatto all'interno di initState che è la funzione che verrà eseguita per prima nel file.

Nella root_page, ci saranno 2 metodi che sono _onLoggedIn e _onSignedOut. In _onLoggedIn, proviamo a ottenere l'id utente e setstate authStatus per l'utente è già connesso. In _onSignedOut, cancelliamo l'id utente memorizzato e setstate authStatus per l'utente non è connesso.

Nella root_page, passiamo 2 parametri in login_page, uno è la classe Auth che implementiamo più facilmente (lo istanziamo in main.dart) e il metodo _onLoggedIn). Nella pagina login_signup, creiamo 2 variabili che è auth di tipo BaseAuth e onSignedIn di tipo VoidCallback. Possiamo facilmente recuperare i 2 parametri passati in login_signup_page nelle nostre variabili locali usando la seguente riga.

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

autenticazione finale BaseAuth;
final VoidCallback onSignedIn;

VoidCallback consente a login_signup_page di chiamare il metodo all'interno di root_page che è _onSignedIn quando l'utente accede. Quando viene chiamato _onSignedIn, imposterà authStatus su LOGGED_IN e setState per ridisegnare l'app. Quando l'app viene ridisegnata, initState controlla authStatus e poiché è LOGGED_IN, mostrerà home_page, passando in auth e voidcallback di _signOut.

root_page.dart

@oltrepassare
Widget build (contesto BuildContext) {
  switch (authStatus) {
    case AuthStatus.NOT_DETERMINED:
      return _buildWaitingScreen ();
      rompere;
    case AuthStatus.NOT_LOGGED_IN:
      ritorna nuovo LoginSignUpPage (
        auth: widget.auth,
        onSignedIn: _onLoggedIn,
      );
      rompere;
    case AuthStatus.LOGGED_IN:
      if (_userId.length> 0 && _userId! = null) {
        ritorna nuova HomePage (
          userId: _userId,
          auth: widget.auth,
          onSignedOut: _onSignedOut,
        );
      } else return _buildWaitingScreen ();
      rompere;
    predefinito:
      return _buildWaitingScreen ();
  }
}

Nota la barra di debug nell'angolo in alto a destra dell'app, puoi rimuoverla facilmente aggiungendo la seguente riga all'interno del widget MaterialApp in main.dart

Schermata di accesso alla demo
debugShowCheckedModeBanner: false,
Banner di debug rimosso

Puoi ottenere il codice sorgente completo nel link github di seguito

Se trovi utile questo articolo, dai un po '

Riferimento:

Il Flutter Pub è una pubblicazione media che ti offre le ultime e sorprendenti risorse come articoli, video, codici, podcast ecc. Su questa fantastica tecnologia per insegnarti come costruire bellissime app con esso. Puoi trovarci su Facebook, Twitter e Medium o saperne di più su di noi qui. Ci piacerebbe connetterci! E se sei uno scrittore interessato a scrivere per noi, puoi farlo attraverso queste linee guida.