Introducción a AWS

Crear una aplicación Flutter

Crear una aplicación Flutter sencilla con AWS Amplify

Módulo 1: crear e implementar una aplicación Flutter

En este módulo, creará una aplicación Flutter y la implementará en la nube mediante el servicio de alojamiento web de AWS Amplify.

Introducción

AWS Amplify es un conjunto de herramientas que permite a los desarrolladores crear aplicaciones más rápidamente al proporcionar bibliotecas fáciles de usar que posibilitan autenticar usuarios, almacenar archivos, capturar eventos de análisis y mucho más, con solo algunas líneas de código.

En este módulo, creará y desarrollará la UI (interfaz de usuario) de una aplicación de galería de fotos. Esto incluye el flujo de inicio de sesión, una página con galería para ver imágenes y la capacidad de tomar una foto con la cámara.

Este módulo establecerá las bases de nuestra aplicación para que los siguientes módulos puedan centrarse en las implementaciones reales de Amplify para la categoría específica.

Lo que aprenderá

  • Implementar un flujo de registro e inicio de sesión
  • Navegación entre pantallas
  • Implementar una grilla de widgets
  • Tomar imágenes con una cámara del dispositivo

Conceptos clave

Navigator: este tutorial utilizará Flutter Navigator 2.0, que emplea una lista de páginas para determinar qué vista debe mostrarse mediante una implementación declarativa.

Devoluciones de llamada: a fin de enviar datos de un objeto a otro, utilizaremos devoluciones de llamada para la comunicación. Una devolución de llamada es similar a una función en el sentido de que se le puede pasar un argumento desde el sitio de la llamada, pero ejecuta código en otro lugar.

 Tiempo de realización

30 minutos

 Servicios utilizados

Implementación

  • Crear un proyecto con Flutter

    Inicie Visual Studio Code y cree un nuevo proyecto con Flutter al cual le colocará el nombre que desee.

    FlutterApp-Module1Photo1-small

    Una vez que su proyecto esté configurado, reemplace el código repetitivo en main.dart con lo siguiente:

    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. Hemos cambiado el widget MyApp por StatefulWidget. Más adelante nos encargaremos de su estado.
    2. El widget de inicio de nuestra MaterialApp es un navegador que permitirá configurar nuestra navegación de forma declarativa.
  • Crear el flujo de autenticación
    Para poder agregar páginas a nuestro navegador, primero tenemos que crear los widgets que representarán cada una de las páginas. Comencemos con la página de inicio de sesión, que colocaremos en un nuevo archivo llamado 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. Dado que LoginPage requiere la entrada del usuario, tenemos que controlar ese estado con un TextEditingController para cada campo en la pantalla, en este caso, nombre de usuario y contraseña.
    2. _LoginPageState.build devolverá un widget Scaffold que permitirá que nuestros widgets puedan formatearse correctamente para un dispositivo móvil.
    3. Es importante tener en cuenta el widget SafeArea, ya que la aplicación podrá ejecutarse en varios dispositivos. En este caso, también aprovechamos las inserciones de borde mínimo destinadas a agregar relleno en los lados izquierdo y derecho de la pantalla para que el formulario de inicio de sesión no sea de borde a borde.
    4. Nuestra UI consistirá en el formulario de inicio de sesión principal y un botón en la parte inferior de la pantalla que permitirá al usuario registrarse en lugar de iniciar sesión. Aquí usamos una pila para facilitar la manipulación de la ubicación de cada widget secundario.
    5. Crear una función _loginForm es completamente opcional, pero ordena un poco el método de construcción. Aquí implementamos la UI para los campos de texto de nombre de usuario y contraseña, así como el botón de inicio de sesión.
    6. El botón de registro tendrá la forma de una oración interactiva que permitirá al usuario registrarse si aún no tiene una cuenta. Aún no se ha implementado ninguna funcionalidad onPressed.
    7. El método _login extraerá los valores de los controladores de campo de texto y creará un objeto AuthCredentials. En este momento solo imprime los valores de cada controlador.

    La UI de LoginPage no está terminada, agreguémosla al navegador en main.dart.

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

    El parámetro de páginas toma List<Page<dynamic>>, por lo que pasamos una sola MaterialPage donde nuestra LoginPage es la secundaria.

    Ejecute la aplicación y debería ver su LoginPage.

    FlutterApp-Module1Photo2-small

    Para poder iniciar sesión, el usuario primero deberá registrarse. Implementemos SignUpPage en un nuevo archivo 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');
      }
    }

    Nuestra SignUpPage es casi idéntica a LoginPage con la excepción de que tiene un campo extra para el correo electrónico y el texto de los botones es diferente.

    Agreguemos SignUpPage como MaterialPage también en el navegador de main.dart.

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

    Ahora, ejecute la aplicación.

    FlutterApp-Module1Photo3-small

    La pantalla de registro ahora debería aparecer cuando se inicia la aplicación, ya que es la última página implementada en la lista de páginas de nuestro navegador. El navegador trata el argumento de las páginas como una pila, donde el último está en la cima. Esto significa que actualmente estamos viendo SignUpPage apilada sobre LoginPage.

    Si queremos mostrar diferentes páginas, tendremos que implementar lógica dentro de nuestra lista para determinar cuándo mostrar páginas específicas. Podemos lograr estas actualizaciones por medio de la creación de un Stream y la anidación de nuestro navegador en un StreamBuilder.

    Cree un nuevo archivo llamado auth_service.dart y agréguele lo siguiente:

    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 es una enumeración que cubrirá los cuatro estados que puede tener nuestro flujo de autenticación: la página de inicio de sesión, la página de registro, la página de verificación o una sesión. Pronto agregaremos las últimas dos páginas.
    2. AuthState es el objeto que observaremos en nuestro stream y contendrá authFlowStatus como propiedad.
    3. Nuestro AuthService tendrá dos propósitos, administrar el controlador de streams de AuthState y contener toda la funcionalidad de autenticación que se agregará en el siguiente módulo.
    4. authStateController se encarga de enviar nuevos AuthState para su control.
    5. Esta es una función simple para actualizar el stream de AuthState para signUp.
    6. Esto hace lo mismo que showSignUp, pero actualiza el stream para enviar el inicio de sesión.

    Abra main.dart otra vez y agregue una instancia de AuthService en _MyAppState.

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

    Ahora podemos envolver al navegador en 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. Envolvimos el navegador con un StreamBuilder que espera observar un stream que emita AuthState.
    2. Accedemos al stream AuthState desde authStateController de la instancia de AuthService.
    3. El stream puede o no tener datos. Para acceder de forma segura a authFlowStatus desde los datos, que son de tipo AuthState, primero implementamos la verificación aquí.
    4. Si el stream emite AuthFlowStatus.login, mostraremos LoginPage.
    5. Si el stream emite AuthFlowStatus.singUp, mostraremos SignUpPage.
    6. Si el stream no tiene datos, se muestra un CircularProgressIndicator.

    Para garantizar que el stream tenga datos desde el principio, es necesario emitir un valor inmediatamente. Para esto, se envía AuthFlowStatus.login al inicializarse _MyAppState.

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

    Si ejecutamos la aplicación ahora, debería mostrar LoginPage, ya que ese es el único valor que se emitió por medio del stream.

    Aún tenemos que implementar la capacidad para cambiar entre LoginPage y SignUpPage.

    Vaya a login_page.dart y agregue lo siguiente:

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

    Nuestro constructor ahora acepta un VoidCallback como argumento que puede activar alguna funcionalidad en main.dart y puede llamarse desde _LoginPageState.

    Pase shouldShowSignUp como argumento para el botón de registro en nuestro _LoginPageState:

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

    De regreso en main.dart, tenemos que pasar un argumento para el parámetro shouldShowSignUp de LoginPage:

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

    Ejecute la aplicación y presione el botón de registro en LoginPage. Ahora debería ir a SignUpPage.

    Tenemos que poder hacer lo mismo con SignUpPage para que el usuario pueda cambiar entre registrarse e iniciar sesión al tocar el botón en la parte inferior de la pantalla.

    Agregue lo siguiente a 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.')),

    Tal como lo implementamos con LoginPage, SignUpPage activará VoidCallback cuando el usuario presione el botón en la parte inferior de la pantalla.

    Ahora, tan solo actualice main.dart para aceptar un argumento para shouldShowLogin.

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

    Si ejecuta la aplicación esta vez, verá que puede cambiar entre LoginPage y SignUpPage.

    Lo último que hace falta para cada una de estas páginas es una forma de pasar la entrada del usuario para cada campo como credenciales que se puedan procesar para iniciar sesión o registrarse.

    Cree un nuevo archivo llamado auth_credentials.dart y agregue lo siguiente:

    // 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 es una clase abstracta que usaremos para una línea de base de la información mínima necesaria destinada a iniciar sesión o registrarse. Esto nos permitirá usar LoginCredentials y SignUpCredentials casi indistintamente.
    2. LoginCredentials es una implementación sencilla y concreta de AuthCredentials, ya que para iniciar sesión solo se necesita el nombre de usuario y la contraseña.
    3. Casi exactamente lo mismo que LoginCredentials pero con el correo electrónico como un campo adicional necesario para registrarse.

    Ahora podemos agregar métodos de inicio de sesión y registro a AuthService que acepten las credenciales correspondientes y cambien el estado del navegador a la página correcta.

    Agregue estas dos funciones a 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. Cuando un usuario pasa una AuthCredential, realizaremos algo de lógica y finalmente pondremos al usuario en estado de sesión.
    2. El registro requerirá la verificación del correo electrónico ingresado y para eso deberá ingresarse un código de verificación. Por lo tanto, la lógica de registro debería cambiar el estado a verificación.

    Comencemos por actualizar LoginPage para enviar LoginCredentials mediante una propiedad ValueChanged.

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

    Ahora podemos pasar nuestras credenciales desde el método _login() en _LoginPageState:

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

    Implementaremos algo similar para SignUpPage:

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

    Y crearemos las credenciales:

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

    Ahora, conectemos todo en main.dart:

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

    Esto envuelve LoginPage y SignUpPage, pero como vimos con AuthFlowStatus, aún tenemos que implementar una página de verificación y páginas para representar una sesión.

    Agregaremos VerificationPage en un nuevo archivo 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);
      }
    }

    VerificationPage es solo una versión reducida de LoginPage y solo pasa un código de verificación al árbol de widgets.

    De regreso en auth_service.dart, debe haber un método para manejar el código de verificación y actualizar el estado a sesión.

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

    Ahora agregaremos VerificationPage al navegador de main.dart.

    ... // shouldShowLogin: _authService.showLogin)),
    
    // Show Verification Code Page
    if (snapshot.data.authFlowStatus == AuthFlowStatus.verification)
      MaterialPage(child: VerificationPage(
        didProvideVerificationCode: _authService.verifyCode))
    
    ... // pages closing ],
  • Crear el flujo de cámara/galería

    Con VerificationPage implementado, podemos pasar a la UI para cuando un usuario haya iniciado sesión. Mostraremos una galería de imágenes y tendremos la capacidad de tomar una foto con la cámara del dispositivo. Crearemos un widget CameraFlow que gestionará los cambios de estado que indican cuándo mostrar cada pantalla.

    Cree un nuevo archivo llamado camera_flow.dart y agréguele lo siguiente:

    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 deberá activarse cuando el usuario cierre la sesión y se actualice el estado en main.dart. Implementaremos esta funcionalidad poco después de crear la GalleryPage.
    2. Este indicador actuará como el estado responsable de cuándo la cámara debería mostrarse o no mostrarse.
    3. Para asegurarnos de que nuestro navegador esté actualizado cuando _shouldShowCamera se actualice, usamos una propiedad calculada para devolver la pila de navegación correcta según el estado actual. Por ahora estamos usando las páginas de marcador de posición.
    4. De forma similar a _MyAppState, estamos usando un widget de navegador para determinar qué página debe mostrarse en un momento dado para una sesión.
    5. Este método nos permitirá alternar si la cámara se muestra o no sin tener que implementar setState() en el sitio de la llamada.

    Cree GalleryPage en 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 solo mostrará imágenes, por lo que se puede implementar como un StatelessWidget.
    2. Esta VoidCallback se conectará al método shouldLogOut en CameraFlow.
    3. Esta VoidCallback actualizará el indicador _shouldShowCamera en CameraFlow.
    4. Nuestro botón de cierre de sesión se implementa como una acción en la AppBar y cuando se la toca, llama a shouldLogOut.
    5. Al presionar este FloatingActionButton, se activará nuestra cámara para mostrarla.
    6. Las imágenes se mostrarán en una grilla con dos columnas. Actualmente estamos codificando tres elementos en esta grilla.
    7. En el módulo Agregar almacenamiento, implementaremos un widget para cargar imágenes. Hasta entonces, usaremos el marcador de posición para representar las imágenes.

    Ahora podemos reemplazar el marcador de posición en nuestras CameraFlow._pages con la GalleryPage recientemente creada.

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

    Para simplificar la creación de una CameraPage, agregaremos un par de dependencias al archivo pubspec.yaml:

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

    También es necesario actualizar la configuración de cada plataforma.

    En Android, actualice minSdkVersion a 21 (android > app > build.gradle)

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

    En iOS, actualice Info.plist para poder acceder a la cámara. (ios > Runner > Info.plist):

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

    Ahora agregue esto a un nuevo archivo llamado 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. Para tomar una foto, necesitaremos obtener una instancia de CameraDescription proporcionada por CameraFlow.
    2. Este ValueChanged proporcionará a CameraFlow la ruta local a la imagen capturada por la cámara.
    3. Para asegurarnos de que tenemos una instancia de CameraController, la inicializamos en el método initState e iniciamos _initializeControllerFuture una vez que haya terminado.
    4. FutureBuilder observará cuando se muestre Future y mostrará una vista previa de lo que ve la cámara o un CircularProgressIndicator.
    5. Al presionarlo, el FloatingActionButton activará _takePicture().
    6. Este método construirá una ruta temporal a la ubicación de la imagen y la devolverá a CameraFlow a través de didProvideImagePath.
    7. Por último, debemos asegurarnos de eliminar el CameraController una vez que se haya eliminado la página.

    En CameraFlow, tenemos que crear la instancia de CameraDescription.

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

    Cree una función para obtener e inicializar _camera.

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

    Llamaremos a esta función tan pronto como se inicialice _CameraFlowState.

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

    Por último, reemplace el marcador de posición en _pages con una instancia de CameraPage.

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

    Ahora, se inicializará CameraPage con una cámara y mostrará la imagePath una vez que se tome una imagen. Solo cerraremos la cámara una vez que se tome una foto.

  • Agregar el cierre de sesión

    Para cerrar el ciclo de navegación de nuestra UI, debemos agregar un método de cierre de sesión a AuthService.

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

    Por último, implemente el caso para CameraFlow en las Navigator.pages de main.dart.

    ... // _authService.verifyCode)),
    
    // Show Camera Flow
    if (snapshot.data.authFlowStatus == AuthFlowStatus.session)
      MaterialPage(
          child: CameraFlow(shouldLogOut: _authService.logOut))
    
    ... // pages closing ],
  • Probar la aplicación

    Si vuelve a ejecutar la aplicación, debería poder navegar por todas las pantallas.

    EndofModule1-gif

Conclusión

Ha implementado con éxito la UI de la aplicación Photo Gallery en Flutter. ¡Ya puede implementar Amplify en su proyecto!

¿Este módulo le resultó útil?

Gracias
Indíquenos lo que le gustó.
Cerrar
Lamentamos haberlo decepcionado
¿Hay información desactualizada, confusa o inexacta? Ayúdenos a mejorar este tutorial con sus comentarios.
Cerrar

Inicializar Amplify