Démarrer avec AWS

Créer une application Flutter

Créer une application Flutter simple avec AWS Amplify

Module 1 : Créer et déployer une application Flutter

Dans ce module, vous allez créer une application Flutter et la déployer dans le cloud en utilisant le service d'hébergement Web AWS Amplify.

Introduction

AWS Amplify est une suite d'outils qui permet aux développeurs de créer des applications plus rapidement en fournissant des bibliothèques faciles à utiliser qui permettent d'authentifier les utilisateurs, de stocker des fichiers, d'enregistrer des événements d'analyse et bien plus encore, avec seulement quelques lignes de code.

Au cours de ce module, vous allez créer et développer l'interface utilisateur d'une application de galerie de photos. Cela comprend le flux d'inscription, une page de galerie pour visualiser les images et la possibilité de prendre une photo avec l'appareil photo.

Ce module servira de base à notre application afin que les modules suivants puissent se concentrer sur les mises en œuvre effectives d'Amplify pour la catégorie spécifique.

Ce que vous apprendrez

  • Mise en place d'un flux d'inscription et de connexion
  • Navigation entre différents écrans
  • Mise en œuvre d'une trame de widgets
  • Prise de photos avec l'appareil photo

Concepts clés

Navigator - Ce tutoriel utilisera le Flutter Navigator 2.0, qui utilise une liste de pages pour déterminer quelle vue doit être affichée au moyen d'une implémentation déclarative.

Rappels - Afin d'envoyer des données d'un objet à un autre, nous utiliserons des rappels pour la communication. Un rappel est semblable à une fonction en ce sens qu'il peut faire passer un argument depuis le site d'appel, mais il exécute le code ailleurs.

 Durée

30 minutes

 Services utilisés

Implémentation

  • Création d'un projet Flutter

    Lancez Visual Studio Code et créez un nouveau projet Flutter au nom de votre choix.

    FlutterApp-Module1Photo1-small

    Une fois le projet configuré, remplacez le code réutilisable dans main.dart comme suit :

    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. Nous avons modifié le widget MyApp en StatefulWidget. Nous manipulerons son état plus tard.
    2. Le widget d'accueil de notre MaterialApp est un Navigator qui permettra de configurer notre navigation de façon déclarative.
  • Création du flux d'authentification
    Avant de pouvoir ajouter des pages à notre Navigator, nous devons créer les widgets qui représenteront chacune de nos pages. Commençons avec la page de connexion, que nous insérerons dans un nouveau fichier 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. LoginPage nécessite une entrée utilisateur, nous devons donc mémoriser cet état en intégrant un TextEditingController pour chaque champ à l'écran ; dans le cas qui nous intéresse : nom d'utilisateur et mot de passe.
    2. _LoginPageState.build renverra un Scaffold pour permettre à nos widgets d'être correctement formatés pour un appareil mobile.
    3. Il est important d'observer le SafeArea, car l'application aura la capacité de fonctionner sur de multiples appareils. Dans notre cas, nous profitons également des incrustations de périphérie minimales pour ajouter un remplissage à gauche et à droite de l'écran afin que le formulaire de connexion ne se présente pas d'un bord à l'autre.
    4. Notre interface utilisateur consistera du formulaire de connexion primaire et d'un bouton en bas de l'écran pour permettre à l'utilisateur de s'inscrire au lieu de se connecter. Nous utilisons ici une pile pour rendre plus facile la manipulation du placement de chaque sous-widget.
    5. La création d'une fonction _loginForm est totalement optionnelle, mais elle permet de désencombrer un peu la méthode de création. Ici, nous implémentons l'interface utilisateur pour les champs de texte du nom d'utilisateur et du mot de passe, ainsi que le bouton de connexion.
    6. Notre bouton d'inscription prendra la forme d'une phrase interactive qui permet à l'utilisateur de créer un compte s'il n'en possède pas déjà un. Aucune fonctionnalité onPressed n'est encore implémentée.
    7. La méthode _login aura en charge d'extraire les valeurs des contrôleurs des champs de texte et de créer un objet AuthCredentials. Pour l'heure, elle imprime simplement les valeurs de chaque contrôleur.

    L'interface utilisateur de LoginPage n'est pas terminée, ajoutez-la au Navigator dans main.dart.

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

    Le paramètre pages prend un List<Page<dynamic>>, nous passons alors à une seule MaterialPage dans lequel notre LoginPage est la sous-page.

    Lancez l'application et vous devriez voir votre LoginPage.

    FlutterApp-Module1Photo2-small

    L'utilisateur devra pouvoir s'inscrire avant de pouvoir se connecter. Implémentons la SignUpPage dans un nouveau fichier 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');
      }
    }

    Notre SignUpPage est quasiment identique à la LoginPage, à l'exception d'un champ supplémentaire pour l'adresse e-mail et du texte des boutons qui a été changé.

    Ajoutons également SignUpPage en tant que MaterialPage dans le Navigator de main.dart.

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

    Et maintenant, exécutez l'application.

    FlutterApp-Module1Photo3-small

    L'écran d'inscription devrait désormais s'afficher lorsque l'application est lancée, car il s'agit de la dernière page mise en œuvre dans la liste de pages de notre Navigator. Le Navigator considère l'argument pages comme une pile, dans laquelle le dernier arrivé se trouve au-dessus du reste. Cela signifie que SignUpPage est actuellement inséré tout en haut de LoginPage.

    Pour afficher différentes pages, il faudra implémenter une logique à l'intérieur de la liste afin de déterminer quand afficher des pages spécifiques. Nous pouvons réaliser ces mises à jour en créant un Stream et en imbriquant notre Navigator dans un StreamBuilder.

    Créez un nouveau fichier appelé auth_service.dart et ajoutez ce qui suit :

    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 est une énumération qui traite l'ensemble des quatre états possibles pour notre flux d'authentification, à savoir : la page de connexion, la page d'inscription, la page de vérification ou une session. Nous ajouterons les deux dernières pages très prochainement.
    2. AuthState est l'objet que nous allons observer concrètement dans notre flux, et qui contiendra authFlowStatus en tant que propriété.
    3. Notre AuthService remplira deux fonctions : gérer le contrôleur de flux d'AuthState et contenir toutes nos fonctionnalités d'authentification qui seront ajoutées dans le prochain module.
    4. authStateController est chargé d'envoyer le nouveau flux descendant d'AuthState à observer.
    5. Cette fonction simple permet de mettre à jour le flux AuthState en signUp.
    6. Elle effectue la même tâche que showSignUp en même temps qu'elle met à jour le flux pour envoyer la connexion.

    Ouvrez main.dart à nouveau et ajoutez création d'une instance AuthService dans _MyAppState.

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

    Nous pouvons maintenant habiller le Navigator d'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. Nous avons habillé notre Navigator d'un StreamBuilder destiné à observer un flux émettant AuthState.
    2. L'accès au flux AuthState se fait depuis l'authStateController de l'instance d'AuthService.
    3. Le flux peut contenir ou non des données. Afin d'accéder en toute sécurité à authFlowStatus à partir de nos données, qui sont de type AuthState, nous implémentons d'abord la vérification ici.
    4. Si le flux émet AuthFlowStatus.login, nous afficherons LoginPage.
    5. Si le flux émet AuthFlowStatus.signUp, nous afficherons SignUpPage.
    6. Si le flux ne contient aucune donnée, un CircularProgressIndicator s'affichera.

    Pour s'assurer que le flux contient des données dès le départ, une valeur doit être émise immédiatement. Cela peut être effectué en lançant AuthFlowStatus.login dès l'initialisation de _MyAppState.

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

    Si nous exécutons l'application maintenant, elle devrait afficher LoginPage, car c'est la seule valeur qui a été émise par le flux.

    Il nous reste encore à implémenter la possibilité de passer de la page de connexion à la page d'inscription.

    Naviguez jusqu'à login_page.dart et ajoutez ce qui suit :

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

    Notre concepteur accepte désormais un VoidCallback comme argument qui peut déclencher certaines fonctionnalités dans main.dart et appelées depuis _LoginPageState.

    Passez shouldShowSignUp en argument pour le bouton d'inscription dans notre _LoginPageState :

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

    De retour dans main.dart, nous devons passer un argument pour le paramètre shouldShowSignUp de LoginPage :

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

    Lancez l'application et cliquez sur le bouton d'inscription depuis la page de connexion. SignUpPage devrait maintenant s'ouvrir

    Nous devons pouvoir faire la même chose sur SignUpPage pour que l'utilisateur puisse passer de l'inscription à la connexion en cliquant sur le bouton en bas de l'écran.

    Ajoutez ce qui suit à 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.')),

    De la même manière que nous l'avons implémenté avec LoginPage, SignUpPage déclenche le VoidCallback lorsque l'utilisateur appuie sur le bouton en bas de l'écran.

    Il ne reste plus qu'à mettre à jour main.dart afin d'accepter un argument pour shouldShowLogin.

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

    Si vous exécutez l'application cette fois-ci, vous remarquerez que vous pouvez basculer entre la page de connexion et la page d'inscription.

    La dernière chose nécessaire pour chacune de ces pages est un moyen de transmettre les données saisies par l'utilisateur pour chaque champ sous forme d'informations d'identification qui peuvent être traitées pour la connexion/l'inscription.

    Créez un nouveau fichier appelé auth_credentials.dart et ajoutez ce qui suit :

    // 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 est une classe abstraite que nous utiliserons comme référence pour les informations minimales nécessaires à la connexion ou à l'inscription. Cela nous permettra d'utiliser LoginCredentials et SignUpCredentials de manière presque interchangeable.
    2. LoginCredentials étant une implémentation concrète et simple d'AuthCredentials puisque la connexion ne nécessite que le nom d'utilisateur et le mot de passe.
    3. Quasiment identique à LoginCredentials, mais avec l'adresse e-mail comme champ supplémentaire requis pour l'inscription.

    Nous pouvons maintenant ajouter des méthodes de connexion et d'inscription à AuthService qui acceptera les informations d'identification respectives et modifiera l'état du Navigator à la bonne page.

    Ajoutez les deux fonctions suivantes à 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. Lorsqu'un utilisateur transmet un AuthCredentials, nous exécuterons une certaine logique pour finalement placer l'utilisateur en état de session.
    2. Pour finaliser l'inscription, il faut que l'e-mail saisi soit validé en entrant un code de vérification. Ainsi, la logique d'inscription devrait changer l'état en vérification.

    Commençons par mettre à jour LoginPage pour envoyer LoginCredentials par une propriété ValueChanged.

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

    Nous pouvons maintenant transmettre nos informations d'identification depuis la méthode _login() dans _LoginPageState :

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

    Mettons en place quelque chose de similaire pour SignUpPage :

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

    Et ajoutons les informations d'identification :

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

    Maintenant, relions tout dans main.dart :

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

    Cela permet de clore LoginPage et SignUpPage, mais comme nous l'avons vu avec AuthFlowStatus, nous devons encore mettre en place une page de vérification et des pages pour présenter une nouvelle session.

    Ajoutons VerificationPage dans un nouveau fichier 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 page de vérification n'est qu'une version allégée de LoginPage et ne transmet qu'un code de vérification en haut de l'arbre des widgets.

    De retour dans auth_service.dart, il faut une méthode pour gérer le code de vérification et mettre à jour l'état en session.

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

    Maintenant, ajoutez VerificationPage au Navigator de main.dart.

    ... // shouldShowLogin: _authService.showLogin)),
    
    // Show Verification Code Page
    if (snapshot.data.authFlowStatus == AuthFlowStatus.verification)
      MaterialPage(child: VerificationPage(
        didProvideVerificationCode: _authService.verifyCode))
    
    ... // pages closing ],
  • Création du flux Appareil photo/Galerie

    Maintenant que VerificationPage a été implémenté, nous pouvons passer à l'interface utilisateur visible une fois que l'utilisateur est connecté. Nous afficherons une galerie d'images et nous aurons la possibilité de prendre une photo avec l'appareil photo. Nous allons créer un widget CameraFlow qui gérera les changements d'état pour décider quand afficher chaque écran.

    Créez un nouveau fichier appelé camera_flow.dart et ajoutez ce qui suit :

    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 devra se déclencher lorsque l'utilisateur se déconnecte et mettre à jour l'état dans main.dart. Nous implémenterons cette fonctionnalité peu après la création de GalleryPage.
    2. Cet indicateur agira comme l'état qui décidera du moment où la caméra devra ou non s'afficher.
    3. Pour s'assurer que notre Navigator est mis à jour lorsque _shouldShowCamera est mis à jour, nous utilisons une propriété de calcul qui renvoie la pile de navigation correspondante en fonction de l'état actuel. Nous utilisons des pages Placeholder pour l'instant.
    4. Comme pour _MyAppState, nous utilisons un widget Navigator pour déterminer quelle page doit être affichée à un moment donné pour une session.
    5. Cette méthode nous permettra de basculer si la caméra est affichée ou non sans avoir à déployer setState() sur le site de l'appel.

    Créez GalleryPage dans 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 affichera simplement des images, et peut être implémenté comme un StatelessWidget.
    2. Ce VoidCallback se connectera à la méthode shouldLogOut dans CameraFlow.
    3. Ce VoidCallback mettra à jour l'indicateur _shouldShowCamera dans CameraFlow.
    4. Notre bouton de déconnexion est implémenté comme une action dans l'AppBar et appelle shouldLogOut lorsqu'il est tapé.
    5. Ce FloatingActionButton déclenchera l'affichage de l'appareil photo lorsqu'il sera activé.
    6. Nos images seront affichées dans une mosaïque à deux colonnes. Nous sommes actuellement en train de coder 3 éléments dans cette grille.
    7. Nous allons mettre en place un widget de chargement d'images dans le module Add Stroage. Entre-temps, nous utiliserons Placeholder pour représenter les images.

    Nous pouvons maintenant remplacer le Placeholder dans notre CameraFlow._pages par la GalleryPage nouvellement créée.

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

    Pour simplifier la création d'une CameraPage, nous allons ajouter quelques dépendances au fichier pubspec.yaml :

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

    Nous devons également procéder à quelques mises à jour de la configuration pour chaque plate-forme.

    Sous Android, mettez à jour minSdkVersion à 21 (android > app > build.gradle)

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

    Pour iOS, mettez à jour le fichier Info.plist pour permettre l'accès à la caméra. (ios > Runner > Info.plist) :

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

    Maintenant, ajoutez ce qui suit à un nouveau fichier 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. Pour prendre une photo, nous devrons obtenir une instance de CameraDescription qui sera fournie par le CameraFlow.
    2. Ce ValueChanged fournira au CameraFlow le chemin local vers l'image prise par l'appareil photo.
    3. Pour nous assurer que nous avons une instance de CameraController, nous l'initialisons dans la méthode initState et nous initialisons _initializeControllerFuture une fois terminée.
    4. Le FutureBuilder surveille alors le moment où le Future est renvoyé et montre soit un aperçu de ce que voit l'appareil photo, soit un CircularProgressIndicator.
    5. Le FloatingActionButton déclenchera _takePicture() lorsqu'il est actionné.
    6. Cette méthode permet de construire un chemin temporaire vers l'emplacement de l'image et de le renvoyer à CameraFlow par le biais de didProvideImagePath.
    7. Pour finir, nous devons nous assurer que le CameraController est supprimé lorsque la page a été supprimée.

    De retour dans CameraFlow, nous devons créer l'instance CameraDescription.

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

    Créez une fonction pour obtenir et initialiser _camera.

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

    Nous activerons cette fonction dès que _CameraFlowState aura été initialisé.

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

    Enfin, remplacez le Placeholder dans _pages par une instance de CameraPage

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

    Maintenant, CameraPage sera initialisé avec un appareil photo et renverra l'imagePath dès qu'une image est prise. Pour l'instant, nous ne fermons l'appareil photo que lorsqu'une photo est prise.

  • Ajoutez Log Out

    Pour fermer la boucle de navigation de notre interface utilisateur, nous devons ajouter une méthode de déconnexion à AuthService.

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

    Enfin, implémentez le cas de CameraFlow dans les pages Navigator.pages de main.dart.

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

    Si vous exécutez de nouveau l'application, vous devriez pouvoir naviguer sur tous les écrans de l'application.

    EndofModule1-gif

Conclusion

Vous avez mis en œuvre avec succès l'interface utilisateur de l'application Photo Gallery dans Flutter. Vous êtes prêt à implémenter Amplify dans votre projet !

Ce module vous a-t-il été utile ?

Merci
Merci de nous indiquer ce que vous avez aimé.
Fermer
Nous sommes désolés de vous décevoir.
Quelque chose est-il obsolète, déroutant ou inexact ? Aidez-nous à améliorer ce didacticiel en fournissant des commentaires.
Fermer

Initialiser Amplify