Nozioni di base su AWS

Creazione di un'applicazione Flutter

Creazione di una semplice applicazione Flutter utilizzando AWS Amplify

Modulo 1: creazione e distribuzione di un'applicazione Flutter

In questo modulo, creerai un'applicazione Flutter e la distribuirai nel cloud tramite il servizio di hosting Web di AWS Amplify.

Introduzione

AWS Amplify è una suite di strumenti che consentono agli sviluppatori di creare applicazioni più rapidamente perché fornisce librerie facili da usare e con poche righe di codice consente di autenticare gli utenti, archiviare file, acquisire eventi di analisi e molto altro.

In questo modulo, creerai e compilerai l'interfaccia utente di un'applicazione di galleria fotografica. Questo processo comprende il flusso di registrazione, una pagina di galleria per la visualizzazione di immagini e la possibilità di fare una foto con la fotocamera.

Questo modulo getterà le basi della nostra applicazione, in modo che i moduli seguenti potranno incentrarsi sulle effettive implementazioni di Amplify per la categoria specifica.

Avrai modo di approfondire i seguenti aspetti

  • Implementazione di un flusso di registrazione e di accesso
  • Navigazione tra schermate
  • Implementazione di una griglia di widget
  • Scatto di foto con la fotocamera del dispositivo

Concetti chiave

Navigator: questo tutorial utilizzerà il Navigator 2.0 di Flutter, che usa un elenco di pagine per stabilire la visualizzazione da mostrare tramite un'implementazione dichiarativa.

Callback: per inviare dati da un oggetto all'altro, utilizzeremo i callback per la comunicazione. Un callback è simile a una funzione per il fatto che può essere trasmesso come argomento dal sito di chiamata, ma esegue il codice da un'altra parte.

 Tempo richiesto per il completamento

30 minuti

 Servizi utilizzati

Implementazione

  • Creazione di un progetto Flutter

    Avvia Visual Studio Code e crea un nuovo progetto Flutter con un nome scelto da te.

    FlutterApp-Module1Photo1-small

    Una volta configurato il progetto, sostituisci il codice boilerplate in main.dart con il seguente:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    // 1
    class MyApp extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Photo Gallery App',
          theme: ThemeData(visualDensity: VisualDensity.adaptivePlatformDensity),
          // 2
          home: Navigator(
            pages: [],
            onPopPage: (route, result) => route.didPop(result),
          ),
        );
      }
    }
    1. Abbiamo modificato il widget MyApp in un StatefulWidget. In seguito modificheremo il suo stato.
    2. L'home widget di MaterialApp è un Navigator che consentirà la configurazione della navigazione in modo dichiarativo.
  • Creazione del flusso di autenticazione
    Prima di poter aggiungere pagine al Navigator, dobbiamo creare i widget che rappresenteranno ciascuna delle pagine. Iniziamo con la pagina di accesso, che inseriremo in un nuovo file login_page.dart.
    import 'package:flutter/material.dart';
    
    class LoginPage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _LoginPageState();
    }
    
    class _LoginPageState extends State<LoginPage> {
      // 1
      final _usernameController = TextEditingController();
      final _passwordController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        // 2
        return Scaffold(
          // 3
          body: SafeArea(
              minimum: EdgeInsets.symmetric(horizontal: 40),
              // 4
              child: Stack(children: [
                // Login Form
                _loginForm(),
    
                // 6
                // Sign Up Button
                Container(
                  alignment: Alignment.bottomCenter,
                  child: FlatButton(
                      onPressed: () {},
                      child: Text('Don\'t have an account? Sign up.')),
                )
              ])),
        );
      }
    
      // 5
      Widget _loginForm() {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Username TextField
            TextField(
              controller: _usernameController,
              decoration:
                  InputDecoration(icon: Icon(Icons.mail), labelText: 'Username'),
            ),
    
            // Password TextField
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(
                  icon: Icon(Icons.lock_open), labelText: 'Password'),
              obscureText: true,
              keyboardType: TextInputType.visiblePassword,
            ),
    
            // Login Button
            FlatButton(
                onPressed: _login,
                child: Text('Login'),
                color: Theme.of(context).accentColor)
          ],
        );
      }
    
      // 7
      void _login() {
        final username = _usernameController.text.trim();
        final password = _passwordController.text.trim();
    
        print('username: $username');
        print('password: $password');
      }
    }
    1. Poiché la pagina di accesso richiede l'input dell'utente, dobbiamo tenere traccia di quello stato tramite un TextEditingController per ogni campo della schermata; in questo caso, nome utente e password.
    2. _LoginPageState.build restituirà uno scaffold che consentirà la corretta formattazione dei widget per un dispositivo mobile.
    3. È importante osservare il widget SafeArea, poiché l'applicazione potrà essere eseguita su più dispositivi mobili. In questo caso, stiamo sfruttando anche gli inset dell'edge minimo per aggiungere riempimento sia sul lato destro che sul lato sinistro della schermata, in modo che il modulo di accesso non vada da un edge all'altro.
    4. L'interfaccia utente sarà composta dal modulo di accesso principale e da un pulsante nella parte inferiore della schermata che consentirà all'utente di effettuare la registrazione anziché l'accesso. Qui utilizziamo uno stack per semplificare la modifica della collocazione di ciascun widget figlio.
    5. La creazione di una funzione _loginForm è interamente facoltativa, ma sfoltisce un po' il metodo di compilazione. Qui stiamo implementando l'interfaccia utente per i campi di testo nome utente e password, oltre al pulsante di accesso.
    6. Il pulsante di registrazione avrà la forma di una frase interattiva che consente all'utente di registrarsi se ancora non dispone di un account. Non è stata ancora implementata alcuna funzionalità onPressed.
    7. Il metodo _login avrà il compito di estrarre i valori dai controller dei campi di testo e creare un oggetto AuthCredentials. Ora sta semplicemente stampando i valori di ciascun controller.

    L'interfaccia utente della pagina di accesso non è completa; aggiungiamola al Navigator in main.dart.

    ... // home: Navigator(
    
    pages: [MaterialPage(child: LoginPage())],
    
    ... // onPopPage: (route, result) => route.didPop(result),

    Il parametro pages richiede una List<Page<dynamic>>, quindi trasmettiamo un singolo MaterialPage in cui LoginPage è il figlio.

    Esegui l'applicazione; dovresti vedere la LoginPage.

    FlutterApp-Module1Photo2-small

    L'utente dovrà poter effettuare la registrazione prima dell'accesso. Implementiamo la SignUpPage in un nuovo file sign_up_page.dart

    import 'package:flutter/material.dart';
    
    class SignUpPage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _SignUpPageState();
    }
    
    class _SignUpPageState extends State<SignUpPage> {
      final _usernameController = TextEditingController();
      final _emailController = TextEditingController();
      final _passwordController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
              minimum: EdgeInsets.symmetric(horizontal: 40),
              child: Stack(children: [
                // Sign Up Form
                _signUpForm(),
    
                // Login Button
                Container(
                  alignment: Alignment.bottomCenter,
                  child: FlatButton(
                      onPressed: () {},
                      child: Text('Already have an account? Login.')),
                )
              ])),
        );
      }
    
      Widget _signUpForm() {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Username TextField
            TextField(
              controller: _usernameController,
              decoration:
                  InputDecoration(icon: Icon(Icons.person), labelText: 'Username'),
            ),
    
            // Email TextField
            TextField(
              controller: _emailController,
              decoration:
                  InputDecoration(icon: Icon(Icons.mail), labelText: 'Email'),
            ),
    
            // Password TextField
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(
                  icon: Icon(Icons.lock_open), labelText: 'Password'),
              obscureText: true,
              keyboardType: TextInputType.visiblePassword,
            ),
    
            // Sign Up Button
            FlatButton(
                onPressed: _signUp,
                child: Text('Sign Up'),
                color: Theme.of(context).accentColor)
          ],
        );
      }
    
      void _signUp() {
        final username = _usernameController.text.trim();
        final email = _emailController.text.trim();
        final password = _passwordController.text.trim();
    
        print('username: $username');
        print('email: $email');
        print('password: $password');
      }
    }

    La SignUpPage è quasi identica alla LoginPage, tranne che per il campo aggiuntivo per l'indirizzo e-mail e per il fatto che il testo dei pulsanti è cambiato.

    Aggiungiamo la SignUpPage come MaterialPage anche al Navigator del file main.dart.

    ... // home: Navigator(
    
    pages: [
      MaterialPage(child: LoginPage()),
      MaterialPage(child: SignUpPage())
    ],
    
    ... // onPopPage: (route, result) => route.didPop(result),

    Ora esegui l'applicazione.

    FlutterApp-Module1Photo3-small

    La schermata di registrazione ora dovrebbe essere visualizzata all'avvio dell'applicazione, poiché è l'ultima pagina implementata nell'elenco delle pagine del Navigator. Il Navigator considera l'argomento pages come uno stack, dove l'ultimo si trova in cima. Questo significa che stiamo vedendo la SignUpPage impilata sulla LoginPage.

    Per visualizzare pagine diverse, dovremo implementare la logica all'interno dell'elenco, per stabilire quando visualizzare determinate pagine. Possiamo ottenere questi aggiornamenti creando uno stream e annidando il Navigator in un StreamBuilder.

    Crea un nuovo file chiamato auth_service.dart e aggiungi i seguenti elementi:

    import 'dart:async';
    
    // 1
    enum AuthFlowStatus { login, signUp, verification, session }
    
    // 2
    class AuthState {
      final AuthFlowStatus authFlowStatus;
    
      AuthState({this.authFlowStatus});
    }
    
    // 3
    class AuthService {
      // 4
      final authStateController = StreamController<AuthState>();
    
      // 5
      void showSignUp() {
        final state = AuthState(authFlowStatus: AuthFlowStatus.signUp);
        authStateController.add(state);
      }
    
      // 6
      void showLogin() {
        final state = AuthState(authFlowStatus: AuthFlowStatus.login);
        authStateController.add(state);
      }
    }
    1. AuthFlowStatus è un'enumerazione che copre i quattro diversi stati in cui si può trovare il flusso di autenticazione: pagina di accesso, pagina di registrazione, pagina di verifica o sessione. Le ultime due pagine verranno aggiunte a breve.
    2. AuthState è l'oggetto effettivo che osserveremo nello stream e conterrà la proprietà authFlowStatus.
    3. L'AuthService avrà due scopi: gestire il controller dello stream di AuthState e contenere tutte le funzionalità di autenticazione che verranno aggiunte nel modulo seguente.
    4. authStateController è responsabile dell'invio di nuovi downstream di AuthState per l'osservazione.
    5. Si tratta di una semplice funzione per aggiornare lo stream AuthState in signUp.
    6. Il risultato è uguale a showSignUp, ma aggiorna lo stream per inviare l'accesso.

    Apri nuovamente main.dart e crea un'istanza di AuthService in _MyAppState.

    ... // class _MyAppState extends State<MyApp> {
    
    final _authService = AuthService();
    
    ... // @override

    Ora è possibile eseguire il wrapping del Navigator in un StreamBuilder.

    ... // theme: ThemeData(visualDensity: VisualDensity.adaptivePlatformDensity),
    
    // 1
    home: StreamBuilder<AuthState>(
        // 2
        stream: _authService.authStateController.stream,
        builder: (context, snapshot) {
          // 3
          if (snapshot.hasData) {
            return Navigator(
              pages: [
                // 4
                // Show Login Page
                if (snapshot.data.authFlowStatus == AuthFlowStatus.login)
                  MaterialPage(child: LoginPage()),
    
                // 5
                // Show Sign Up Page
                if (snapshot.data.authFlowStatus == AuthFlowStatus.signUp)
                  MaterialPage(child: SignUpPage())
              ],
              onPopPage: (route, result) => route.didPop(result),
            );
          } else {
            // 6
            return Container(
              alignment: Alignment.center,
              child: CircularProgressIndicator(),
            );
          }
        }),
        
    ... // MaterialApp closing ); 
    1. Abbiamo eseguito il wrapping del Navigator con un StreamBuilder che è in attesa di osservare uno stream che emette AuthState.
    2. Accediamo allo stream AuthState dall'authStateController dall'istanza di AuthService.
    3. Lo stream potrebbe non contenere dati. Per accedere in sicurezza ad authFlowStatus dai nostri dati, che sono di tipo AuthState, innanzitutto implementiamo il controllo qui.
    4. Se lo stream emette AuthFlowStatus.login vedremo la LoginPage.
    5. Se lo stream emette AuthFlowStatus.signUp vedremo la SignUpPage.
    6. Se lo stream non contiene dati, verrà visualizzato un indicatore di avanzamento circolare.

    Per assicurarci che lo stream contenga dati dall'inizio, è necessario emettere immediatamente un valore. Questo risultato può essere ottenuto inviando AuthFlowStatus.login quando _MyAppState viene inizializzato.

    ... // final _authService = AuthService();
    
    @override
    void initState() {
     super.initState();
     _authService.showLogin();
    }
    
    ... // @override

    Se eseguiamo l'applicazione in questo momento, vedremo la LoginPage poiché è l'unico valore emesso tramite stream.

    Dobbiamo comunque implementare la possibilità di passare da LoginPage a SignUpPage e viceversa.

    Passa al file login_page.dart e aggiungi quanto segue:

    ... // class LoginPage extends StatefulWidget {
    
    final VoidCallback shouldShowSignUp;
    
    LoginPage({Key key, this.shouldShowSignUp}) : super(key: key);
    
    ... // @override

    Il costruttore ora accetta un argomento VoidCallback che può attivare alcune funzionalità in main.dart e che vengono chiamate da _LoginPageState.

    Trasmetti l'argomento shouldShowSignUp per il pulsante di registrazione in _LoginPageState:

    ... // child: FlatButton(
    
    onPressed: widget.shouldShowSignUp,
    
    ... // child: Text('Don\'t have an account? Sign up.')),

    Torna al file main.dart; dobbiamo trasmettere un argomento per il parametro shouldShowSignUp della LoginPage:

    ... // if (snapshot.data.authFlowStatus == AuthFlowStatus.login)
    
    MaterialPage(
       child: LoginPage(
           shouldShowSignUp: _authService.showSignUp))
    
    ... // Show Sign Up Page

    Esegui l'applicazione e premi il pulsante di registrazione nella LoginPage. Il sistema dovrebbe navigare alla SignUpPage

    Dobbiamo poter fare lo stesso per la SignUpPage, in modo che l'utente possa passare dalla registrazione all'accesso toccando il pulsante nella parte inferiore della schermata.

    Aggiungi quanto segue al file sign_up_page.dart:

    ... // class SignUpPage extends StatefulWidget {
    
    final VoidCallback shouldShowLogin;
    
    SignUpPage({Key key, this.shouldShowLogin}) : super(key: key);
    
    ... // @override
    ... // child: FlatButton(
    
    onPressed: widget.shouldShowLogin,
    
    ... // child: Text('Already have an account? Login.')),

    Proprio come quanto implementato con LoginPage, SignUpPage avvierà VoidCallback quando l'utente preme il pulsante nella parte inferiore della schermata.

    Ora aggiorna semplicemente main.dart in modo che accetti un argomento per shouldShowLogin.

    ... // if (snapshot.data.authFlowStatus == AuthFlowStatus.signUp)
    
    MaterialPage(
       child: SignUpPage(
           shouldShowLogin: _authService.showLogin))
    
    ... // pages closing ],

    Se ora esegui l'applicazione, noterai che potrai passare dalla LoginPage alla SignUpPage e viceversa.

    L'ultima cosa necessaria per ciascuna di queste pagine è un modo per trasmettere l'input dell'utente in ogni campo come credenziali che possono essere elaborate per l'accesso o la registrazione.

    Crea un nuovo file chiamato auth_credentials.dart e aggiungi i seguenti elementi:

    // 1
    abstract class AuthCredentials {
      final String username;
      final String password;
    
      AuthCredentials({this.username, this.password});
    }
    
    // 2
    class LoginCredentials extends AuthCredentials {
      LoginCredentials({String username, String password})
          : super(username: username, password: password);
    }
    
    // 3
    class SignUpCredentials extends AuthCredentials {
      final String email;
    
      SignUpCredentials({String username, String password, this.email})
          : super(username: username, password: password);
    }
    1. AuthCredentials è una classe astratta che utilizzeremo come base delle minime informazioni necessarie per eseguire l'accesso o la registrazione. Questo ci consentirà di utilizzare LoginCredentials e SignUpCredentials in modo quasi intercambiabile.
    2. LoginCredentials sono una semplice implementazione concreta delle AuthCredentials, poiché l'accesso richiede solo nome utente e password.
    3. Quasi identiche alle LoginCredentials, ma con l'aggiunta del campo dell'indirizzo e-mail che è obbligatorio per la registrazione.

    Ora possiamo aggiungere i metodi di accesso e di registrazione a AuthService, che accetterà le rispettive credenziali e modificherà lo stato del Navigator nella pagina corretta.

    Aggiungi queste due funzioni al file auth_service.dart:

    ... // showLogin closing }
    
    // 1
    void loginWithCredentials(AuthCredentials credentials) {
     final state = AuthState(authFlowStatus: AuthFlowStatus.session);
     authStateController.add(state);
    }
    
    // 2
    void signUpWithCredentials(SignUpCredentials credentials) {
     final state = AuthState(authFlowStatus: AuthFlowStatus.verification);
     authStateController.add(state);
    }
    
    ... // AuthService closing }
    1. Quando un utente trasmette credenziali di autenticazione, eseguiremo una logica e metteremo infine l'utente in stato session (Sessione).
    2. La registrazione richiederà che l'indirizzo e-mail inserito venga verificato tramite un codice di verifica. Pertanto, la logica di registrazione deve modificare lo stato in verification (Verifica).

    Iniziamo aggiorniamo la LoginPage in modo che invii le LoginCredentials tramite una proprietà ValueChanged.

    ... // class LoginPage extends StatefulWidget {
    
    final ValueChanged<LoginCredentials> didProvideCredentials;
    
    ... // final VoidCallback shouldShowSignUp;
    
    LoginPage({Key key, this.didProvideCredentials, this.shouldShowSignUp})
       : super(key: key);
    
    ... // @override

    Ora possiamo trasmettere le credenziali dal metodo _login() a _LoginPageState:

    ... // print('password: $password');
    
    final credentials =
      LoginCredentials(username: username, password: password);
    widget.didProvideCredentials(credentials);
    
    ... // _login closing }

    Implementiamo qualcosa di simile per SignUpPage:

    ... // class SignUpPage extends StatefulWidget {
    
    final ValueChanged<SignUpCredentials> didProvideCredentials;
    
    ... // final VoidCallback shouldShowLogin;
    
    SignUpPage({Key key, this.didProvideCredentials, this.shouldShowLogin})
       : super(key: key);
    
    ... // @override

    E crea le credenziali:

    ... // print('password: $password');
    
    final credentials = SignUpCredentials(
       username: username, 
       email: email, 
       password: password
    );
    widget.didProvideCredentials(credentials);
    
    ... // _signUp closing }

    Ora collega tutto in main.dart:

    ... // child: LoginPage(
    
    didProvideCredentials: _authService.loginWithCredentials,
    
    ... // shouldShowSignUp: _authService.showSignUp)),
    ... // child: SignUpPage(
    
    didProvideCredentials: _authService.signUpWithCredentials,
    
    ... // shouldShowLogin: _authService.showLogin))

    In questo modo si esegue il wrapping di LoginPage e SignUpPage, ma come abbiamo visto per AuthFlowStatus abbiamo comunque bisogno di implementare una pagina per la verifica e pagine per rappresentare una sessione.

    Aggiungiamo VerificationPage in un nuovo file verification_page.dart:

    import 'package:flutter/material.dart';
    
    class VerificationPage extends StatefulWidget {
      final ValueChanged<String> didProvideVerificationCode;
    
      VerificationPage({Key key, this.didProvideVerificationCode})
          : super(key: key);
    
      @override
      State<StatefulWidget> createState() => _VerificationPageState();
    }
    
    class _VerificationPageState extends State<VerificationPage> {
      final _verificationCodeController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
            minimum: EdgeInsets.symmetric(horizontal: 40),
            child: _verificationForm(),
          ),
        );
      }
    
      Widget _verificationForm() {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Verification Code TextField
            TextField(
              controller: _verificationCodeController,
              decoration: InputDecoration(
                  icon: Icon(Icons.confirmation_number),
                  labelText: 'Verification code'),
            ),
    
            // Verify Button
            FlatButton(
                onPressed: _verify,
                child: Text('Verify'),
                color: Theme.of(context).accentColor)
          ],
        );
      }
    
      void _verify() {
        final verificationCode = _verificationCodeController.text.trim();
        widget.didProvideVerificationCode(verificationCode);
      }
    }

    La VerificationPage in realtà è solo la versione ridotta della LoginPage e trasmette soltanto un codice di verifica lungo l'albero di widget.

    Torna al file auth_service.dart; deve esserci un metodo per gestire il codice di verifica e aggiornare lo stato in session (Sessione).

    ... // signUpWithCredentials closing }
    
    void verifyCode(String verificationCode) {
     final state = AuthState(authFlowStatus: AuthFlowStatus.session);
     authStateController.add(state);
    }
    
    ... // AuthService closing }

    Ora aggiungi la VerificationPage al Navigator del file main.dart.

    ... // shouldShowLogin: _authService.showLogin)),
    
    // Show Verification Code Page
    if (snapshot.data.authFlowStatus == AuthFlowStatus.verification)
      MaterialPage(child: VerificationPage(
        didProvideVerificationCode: _authService.verifyCode))
    
    ... // pages closing ],
  • Creazione del flusso Fotocamera/Galleria

    Dopo l'implementazione della VerificationPage, possiamo passare all'interfaccia utente, dove un utente ha effettuato l'accesso. Visualizzeremo una galleria di immagini e potremo fare una foto con la fotocamera del dispositivo. Creeremo un widget CameraFlow che gestirà le modifiche di stato e stabilirà quando mostrare ogni schermata.

    Crea un nuovo file chiamato camera_flow.dart e aggiungi i seguenti elementi:

    import 'package:flutter/material.dart';
    
    class CameraFlow extends StatefulWidget {
      // 1
      final VoidCallback shouldLogOut;
    
      CameraFlow({Key key, this.shouldLogOut}) : super(key: key);
    
      @override
      State<StatefulWidget> createState() => _CameraFlowState();
    }
    
    class _CameraFlowState extends State<CameraFlow> {
      // 2
      bool _shouldShowCamera = false;
    
      // 3
      List<MaterialPage> get _pages {
        return [
          // Show Gallery Page
          MaterialPage(child: Placeholder()),
    
          // Show Camera Page
          if (_shouldShowCamera) 
          MaterialPage(child: Placeholder())
        ];
      }
    
      @override
      Widget build(BuildContext context) {
        // 4
        return Navigator(
          pages: _pages,
          onPopPage: (route, result) => route.didPop(result),
        );
      }
    
      // 5
      void _toggleCameraOpen(bool isOpen) {
        setState(() {
          this._shouldShowCamera = isOpen;
        });
      }
    }
    1. CameraFlow dovrà essere avviato quando l'utente sta eseguendo la disconnessione e dovrà aggiornare nuovamente lo stato in main.dart. Implementeremo questa funzionalità subito dopo aver creato la GalleryPage.
    2. Questo contrassegno fungerà da stato che dovrà stabilire quando visualizzare o meno la fotocamera.
    3. Per garantire che il Navigator sia aggiornato al momento dell'aggiornamento di _shouldShowCamera, utilizziamo una proprietà computed per riportare il corretto stack di navigazione allo stato attuale. Per ora stiamo utilizzando pagine segnaposto.
    4. Analogamente a _MyAppState utilizziamo un widget Navigator per stabilire quali pagine mostrare in un qualsiasi momento di una sessione.
    5. Questo metodo consentirà di stabilire se mostrare o meno la fotocamera senza dover implementare setState() nel sito della chiamata.

    Crea GalleryPage in gallery_page.dart:

    import 'package:flutter/material.dart';
    
    // 1
    class GalleryPage extends StatelessWidget {
      // 2
      final VoidCallback shouldLogOut;
      // 3
      final VoidCallback shouldShowCamera;
    
      GalleryPage({Key key, this.shouldLogOut, this.shouldShowCamera})
        : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Gallery'),
            actions: [
              // 4
              // Log Out Button
              Padding(
                padding: const EdgeInsets.all(8),
                child:
                    GestureDetector(child: Icon(Icons.logout), onTap: shouldLogOut),
              )
            ],
          ),
          // 5
          floatingActionButton: FloatingActionButton(
              child: Icon(Icons.camera_alt), onPressed: shouldShowCamera),
          body: Container(child: _galleryGrid()),
        );
      }
    
      Widget _galleryGrid() {
        // 6
        return GridView.builder(
            gridDelegate:
                SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
            itemCount: 3,
            itemBuilder: (context, index) {
              // 7
              return Placeholder();
            });
      }
    }
    1. GalleryPage mostrerà semplicemente le immagini e pertanto può essere implementato come StatelessWidget.
    2. Questo VoidCallback si collegherà al metodo shouldLogOut nel CameraFlow.
    3. Questo VoidCallback aggiornerà il contrassegno _shouldShowCamera nel CameraFlow.
    4. Il pulsante di disconnessione viene implementato come azione nella barra delle applicazioni e quando viene toccato chiama shouldLogOut.
    5. Questo FloatingActionButton, quando premuto, avvierà la comparsa della fotocamera.
    6. Le immagini verranno visualizzate in una griglia con due colonne. Al momento, in questa griglia stiamo codificando 3 elementi.
    7. Nel modulo Aggiunta di storage implementeremo un widget di caricamento di immagini. Fino ad allora, utilizzeremo il segnaposto per rappresentare le immagini.

    Ora possiamo sostituire il segnaposto nelle CameraFlow._pages con la GalleryPage appena creata.

    ... // Show Gallery Page
    
    MaterialPage(
        child: GalleryPage(
            shouldLogOut: widget.shouldLogOut,
            shouldShowCamera: () => _toggleCameraOpen(true))),
    
    ... // Show Camera Page

    Per semplificare la creazione di una CameraPage, aggiungeremo un paio di dipendenze al file pubspec.yaml file:

    ... # cupertino_icons: ^1.0.0
    
    camera:
    path_provider:
    path:
    
    ... # dev_dependencies:

    Dovremo anche apportare alcuni aggiornamenti alla configurazione di ciascuna piattaforma.

    In Android, aggiornare minSdkVersion alla versione 21 (android > app > build.gradle)

    ... // defaultConfig {
    ...
    
    minSdkVersion 21
    
    ... // targetSdkVersion 29

    Per iOS, aggiornare Info.plist per consentire l'accesso alla fotocamera. (ios > Runner > Info.plist):

    ... <!-- <false/> -->
    
    <key>NSCameraUsageDescription</key>
    <string>Need the camera to take pictures.</string>
    
    ... <!-- </dict> -->

    Ora aggiungerlo a un nuovo file chiamato camera_page.dart:

    import 'package:camera/camera.dart';
    import 'package:flutter/material.dart';
    import 'package:path/path.dart';
    import 'package:path_provider/path_provider.dart';
    
    class CameraPage extends StatefulWidget {
      // 1
      final CameraDescription camera;
      // 2
      final ValueChanged didProvideImagePath;
    
      CameraPage({Key key, this.camera, this.didProvideImagePath})
          : super(key: key);
    
      @override
      State<StatefulWidget> createState() => _CameraPageState();
    }
    
    class _CameraPageState extends State<CameraPage> {
      CameraController _controller;
      Future<void> _initializeControllerFuture;
    
      @override
      void initState() {
        super.initState();
        // 3
        _controller = CameraController(widget.camera, ResolutionPreset.medium);
        _initializeControllerFuture = _controller.initialize();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: FutureBuilder<void>(
            future: _initializeControllerFuture,
            builder: (context, snapshot) {
              // 4
              if (snapshot.connectionState == ConnectionState.done) {
                return CameraPreview(this._controller);
              } else {
                return Center(child: CircularProgressIndicator());
              }
            },
          ),
          // 5
          floatingActionButton: FloatingActionButton(
              child: Icon(Icons.camera), onPressed: _takePicture),
        );
      }
    
      // 6
      void _takePicture() async {
        try {
          await _initializeControllerFuture;
    
          final tmpDirectory = await getTemporaryDirectory();
          final filePath = '${DateTime.now().millisecondsSinceEpoch}.png';
          final path = join(tmpDirectory.path, filePath);
    
          await _controller.takePicture(path);
    
          widget.didProvideImagePath(path);
        } catch (e) {
          print(e);
        }
      }
    
      // 7
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    }
    1. Per fare una foto, dobbiamo ottenere un'istanza di CameraDescription, che verrà fornita dal CameraFlow.
    2. Questo ValueChanged fornirà al CameraFlow il percorso locale all'immagine acquisita dalla fotocamera.
    3. Per assicurarci di avere un'istanza CameraController, la inizializziamo nel metodo initState, quindi al termine inizializziamo _initializeControllerFuture.
    4. Il FutureBuilder osserverà il momento della restituzione del Future e mostrerà un'anteprima di ciò che vede la fotocamera o un indicatore di avanzamento circolare.
    5. Il FloatingActionButton, quando premuto, attiverà _takePicture().
    6. Questo metodo costruirà un percorso temporaneo alla posizione dell'immagine e lo trasmetterà al CameraFlow tramite didProvideImagePath.
    7. Infine, una volta eliminata la pagina, dobbiamo assicurarci di eliminare il CameraController.

    Torna al CameraFlow; dobbiamo creare l'istanza CameraDescription.

    ... // class _CameraFlowState extends State<CameraFlow> {
    
    CameraDescription _camera;
    
    ... // bool _shouldShowCamera = false;

    Crea una funzione per ottenere e inizializzare _camera.

    ... // _toggleCameraOpen closing }
    
    void _getCamera() async {
      final camerasList = await availableCameras();
      setState(() {
        final firstCamera = camerasList.first;
        this._camera = firstCamera;
      });
    }
    
    ... // _CameraFlowState closing }

    Chiameremo questa funzione non appena verrà inizializzato _CameraFlowState.

    ... // _pages closing }
    
    @override
    void initState() {
     super.initState();
     _getCamera();
    }
    
    ... // @override of build

    Infine, sostituisci il segnaposto in _pages con un'istanza di CameraPage

    ... // if (_shouldShowCamera)
    
    MaterialPage(
       child: CameraPage(
           camera: _camera,
           didProvideImagePath: (imagePath) {
             this._toggleCameraOpen(false);
           }))
    
    ... // _pages closing ];

    Ora la pagina della fotocamera verrà inizializzata con una fotocamera e restituirà l'imagePath quando viene scattata una foto. Chiudiamo la fotocamera solo dopo che è stata scattata una foto.

  • Aggiunta della disconnessione

    Per chiudere il loop di navigazione dell'interfaccia utente, dobbiamo aggiungere a AuthService un metodo di disconnessione.

    ... // verifyCode closing }
    
    void logOut() {
     final state = AuthState(authFlowStatus: AuthFlowStatus.login);
     authStateController.add(state);
    }
    
    ... // AuthService closing }

    Infine, implementa il caso per il CameraFlow in Navigator.pages del file main.dart.

    ... // _authService.verifyCode)),
    
    // Show Camera Flow
    if (snapshot.data.authFlowStatus == AuthFlowStatus.session)
      MaterialPage(
          child: CameraFlow(shouldLogOut: _authService.logOut))
    
    ... // pages closing ],
  • Test dell'applicazione

    Se esegui nuovamente l'applicazione, dovresti essere in grado di navigare tra tutte le sue schermate.

    EndofModule1-gif

Conclusione

Hai implementato correttamente l'interfaccia utente dell'applicazione di galleria fotografica in Flutter. Sei pronto a implementare Amplify nel progetto!

Questo modulo è stato utile?

Grazie
Facci sapere cosa ti è piaciuto.
Chiudi
Spiacenti di non esserti stati d'aiuto
C'è qualcosa di obsoleto, ambiguo o approssimativo? Aiutaci a migliorare questo tutorial con il tuo feedback.
Chiudi

Inizializzazione di Amplify