Conceitos básicos da AWS

Criar uma aplicação Flutter

Criar uma aplicação Flutter simples usando o AWS Amplify

Módulo 1: Criar e implantar um aplicativo Flutter

Neste módulo, você criará uma aplicação Flutter e a implantará na nuvem usando o serviço de hospedagem Web do AWS Amplify.

Introdução

O AWS Amplify é um conjunto de ferramentas que permitem aos desenvolvedores criar aplicativos mais rápido ao facilitar o uso de bibliotecas, possibilitando autenticar usuários, armazenar arquivos, capturar eventos de análise de dados e muito mais, com apenas algumas linhas de código.

Neste módulo, você criará a interface do usuário de um aplicativo de galeria de fotos. Isso inclui o fluxo de cadastro, uma página de galeria para exibir imagens e a capacidade de tirar uma foto com a câmera.

Este módulo definirá a base do nosso aplicativo para que os módulos a seguir possam focar as implementações reais do Amplify para a categoria específica.

O que você aprenderá

  • Implementar fluxo de cadastro e login
  • Navegação entre telas
  • Implementar uma grade de widgets
  • Tirar fotos com uma câmera de dispositivo

Principais conceitos

Navegador - Este tutorial usará o Flutter Navigator 2.0, que usa uma lista de páginas para determinar que exibição deve ser exibida usando uma implementação declarativa.

Retornos de chamada - Para enviar dados de um objeto para outro, usaremos retornos de chamada para comunicação. Um retorno de chamada é semelhante a uma função pois pode passar um argumento do local da chamada, mas executa o código em outro local.

 Tempo para a conclusão

30 minutos

 Serviços usados

Implementação

  • Criar um projeto Flutter

    Inicie o Visual Studio Code e crie um novo projeto Flutter com um nome da sua preferência.

    FlutterApp-Module1Photo1-small

    Quando o projeto for configurado, substitua o código boilerplate em main.dart pelo seguinte:

    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. Alteramos o widget MyApp para um StatefulWidget. Posteriormente, manipularemos o seu estado.
    2. O widget inicial do MaterialApp é um navegador que permitirá a configuração do nosso navegador de forma declarativa.
  • Criar o fluxo de autenticação
    Antes de adicionarmos páginas ao navegador, precisamos criar os widgets que representarão cada página. Vamos começar com a página de login que colocaremos em um novo arquivo 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. Como o LoginPage requer a entrada do usuário, precisamos monitorar esse estado com um TextEditingController para cada campo na tela; neste caso: nome do usuário e senha.
    2. _LoginPageState.build retornará um Scaffold que permitirá a formatação adequada de nossos widgets para um dispositivo móvel.
    3. É importante observar a SafeArea pois o aplicativo poderá ser executado em vários dispositivos. Nesse caso, estamos aproveitando o mínimo de inserções de borda para adicionar preenchimento nos lados esquerdo e direito da tela. Portanto, o formulário de login não será de ponta a ponta.
    4. Nossa interface do usuário consistirá no formulário de login principal e em um botão no final da tela que permitirá que o usuário se cadastre em vez de fazer login. Estamos usando uma pilha aqui para facilitar a manipulação do placement de cada widget filho.
    5. Criar uma função _loginForm é totalmente opcional, mas isso organiza um pouco o método de criação. Aqui estamos implementando a interface do usuário para os campos de texto de nome do usuário e senha, bem como o botão de login.
    6. O botão de cadastro terá a forma de uma frase interativa que permitirá ao usuário se cadastrar se ele ainda não tiver uma conta. Ainda não há funcionalidade onPressed implementada.
    7. O método _login será responsável por extrair os valores dos controladores de campo de texto e criar um objeto AuthCredentials. No momento, ele está simplesmente imprimindo os valores de cada controlador.

    A interface do usuário de LoginPage não foi concluída. Vamos adicioná-la ao navegador em main.dart.

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

    O parâmetro pages tem um List<Page<dynamic>>; então, aprovamos um único MaterialPage em que LoginPage é o filho.

    Execute o aplicativo e deverá aparecer o LoginPage.

    FlutterApp-Module1Photo2-small

    O usuário precisará se cadastrar antes de fazer login. Vamos implementar o SignUpPage em um novo arquivo 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');
      }
    }

    O SignUpPage é quase idêntico ao LoginPage, exceto por ter um campo adicional ou e-mail e pela alteração do texto dos botões.

    Também vamos adicionar o SignUpPage como um MaterialPage no navegador do main.dart.

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

    Execute o aplicativo.

    FlutterApp-Module1Photo3-small

    A tela de cadastro deve aparecer quando o aplicativo é executado pois ela é a última página implementada na lista de páginas do navegador. O navegador trata o argumento pages como uma pilha, em que o último aparece primeiro. Isso significa que estamos vendo o SignUpPage empilhado sobre LoginPage.

    Se desejar exibir páginas diferentes, precisaremos implementar lógica dentro da lista para determinar quando exibir páginas específicas. Podemos concluir essas atualizações criando um Stream e aninhando o navegador em um StreamBuilder.

    Crie um novo arquivo chamado auth_service.dart e adicione o seguinte:

    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 é uma enumeração que cobrirá os quatro estados diferentes possíveis do fluxo de autenticação: página de login, página de cadastro, página de verificação ou uma sessão. Em breve, adicionaremos as duas últimas páginas.
    2. AuthState é o objeto real que observaremos em nosso stream e ele conterá authFlowStatus como uma propriedade.
    3. O AuthService terá dois objetivos: gerenciar o controlador de stream de AuthState e conter toda a funcionalidade de autenticação que será adicionada no próximo módulo.
    4. authStateController é responsável por enviar novo downstream AuthState a ser observado.
    5. Esta é uma função simples para atualizar o stream AuthState para signUp.
    6. Isso não equivale a showSignUp, mas atualiza o stream para enviar login.

    Abra novamente main.dart e adicione uma instância de AuthService em _MyAppState.

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

    Agora podemos quebrar o navegador em um 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. Quebramos o navegador com um StreamBuilder que espera observar um stream que emite AuthState.
    2. Acessamos o stream AuthState no authStateController a partir da instância de AuthService.
    3. O stream pode ou não ter dados. Para acessar com segurança authFlowStatus a partir de dados, que são do tipo AuthState, implementamos confira aqui primeiro.
    4. Se o stream emitir AuthFlowStatus.login, mostraremos LoginPage.
    5. Se o stream emitir AuthFlowStatus.signUp, mostraremos SignUpPage.
    6. Se o stream não tiver dados, um CircularProgressIndicator será exibido.

    Para garantir que o stream tenha dados desde o início, emita um valor imediatamente. Podemos conseguir isso enviando AuthFlowStatus.login quando _MyAppState é inicializado.

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

    Se executarmos o aplicativo agora, ele deverá exibir LoginPage pois esse é o único valor emitido pelo stream.

    Ainda precisaremos implementar a capacidade de alternar entre LoginPage e SignUpPage.

    Navegue até login_page.dart e adicione:

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

    Nosso construtor agora está aceitando um VoidCallback como um argumento que pode acionar algumas funcionalidades em main.dart e ser chamado de _LoginPageState.

    Passe shouldShowSignUp como o argumento para o botão de cadastro em nosso _LoginPageState:

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

    De volta em main.dart, precisamos passar um argumento para o parâmetro shouldShowSignUp do LoginPage:

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

    Execute o aplicativo e pressione o botão de cadastro em LoginPage. Agora ele deve navegar para SignUpPage

    Precisamos conseguir fazer o mesmo para SignUpPage para que o usuário possa alternar entre cadastro e login, tocando no botão no final da tela.

    Adicione o seguinte 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.')),

    Assim como implementado com LoginPage, o SignUpPage acionará o VoidCallback quando o usuário pressionar o botão no final da tela.

    Basta atualizar main.dart para aceitar um argumento para shouldShowLogin.

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

    Se você executar o aplicativo desta vez, note que será capaz de alternar entre o LoginPage e o SignUpPage.

    A última coisa necessária para cada uma dessas páginas é uma forma de passar a entrada do usuário para cada campo como credenciais que podem ser processadas para login/cadastro.

    Crie um novo arquivo chamado auth_credentials.dart e adicione:

    // 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 é uma classe abstrata que usaremos para uma linha de base das informações mínimas necessárias para executar login ou cadastro. Isso permitirá o uso de LoginCredentials e SignUpCredentials de forma quase intercambiável.
    2. LoginCredentials é uma implementação concreta simples de AuthCredentials pois o registro em log só exige nome do usuário e senha.
    3. Quase idêntico ao LoginCredentials, mas com e-mail como campo adicionado exigido para cadastro.

    Agora podemos adicionar métodos de login e cadastro ao AuthService que aceitará as respectivas credenciais e alterará o estado do navegador para a página correta.

    Adicione estas duas funções ao 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 um usuário passar AuthCredentials, executaremos uma certa lógica e, por fim, colocaremos o usuário em um estado da sessão.
    2. O cadastro exigirá que o e-mail inserido seja verificado inserindo um código de verificação. Então, a lógica de cadastro deve alterar o estado para verificação.

    Vamos começar atualizando LoginPage para enviar LoginCredentials por meio da propriedade ValueChanged.

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

    Agora podemos passar credenciais do método _login() em _LoginPageState:

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

    Vamos implementar algo semelhante para SignUpPage:

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

    E criar as credenciais:

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

    Agora conecte tudo em main.dart:

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

    Isso finaliza LoginPage e SignUpPage. Mas, como vimos com o AuthFlowStatus, ainda precisamos implementar uma página para verificação e páginas para representar uma sessão.

    Vamos adicionar VerificationPage em um novo arquivo 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);
      }
    }

    O VerificationPage é de fato apenas uma versão reduzida de LoginPage e só passa um código de verificação na árvore de widget.

    De volta no auth_service.dart, é preciso um método para tratar o código de verificação e atualizar o estado para sessão.

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

    Agora adicione VerificationPage ao 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 ],
  • Criar o fluxo de câmera/galeria

    Com VerificationPage implementado, podemos mover para a interface do usuário quando um usuário faz login. Exibiremos uma galeria de imagens e teremos a capacidade de tirar uma foto com a câmera de dispositivo. Criaremos um widget CameraFlow que tratará alterações de estado determinando quando mostrar cada tela.

    Crie um novo arquivo chamado camera_flow.dart e adicione:

    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 precisará acionar quando o usuário estiver saindo e atualizar o estado de volta para main.dart. Implementaremos essa funcionalidade em breve, após criar o GalleryPage.
    2. Este sinalizador funcionará como o estado responsável por indicar quando a câmera deve ser mostrada ou não.
    3. Para garantir que o navegador seja atualizado quando _shouldShowCamera for atualizado, estamos usando uma propriedade calculada para retornar a pilha de navegação correta com base no estado atual. Por enquanto, estamos usando páginas de espaço reservado.
    4. Semelhante ao _MyAppState, estamos usando o widget Navigator para determinar que página deve ser mostrada em determinado momento para uma sessão.
    5. Este método permitirá alternar se a câmera é mostrada ou não sem precisar implementar setState() no site da chamada.

    Crie GalleryPage em 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 simplesmente exibirá imagens; então, ele pode ser implementado como StatelessWidget.
    2. Este VoidCallback se conectará ao método shouldLogOut em CameraFlow.
    3. Este VoidCallback atualizará o sinalizador _shouldShowCamera em CameraFlow.
    4. O botão de logout é implementado como uma ação no AppBar e chama shouldLogOut quando tocam nele.
    5. Este FloatingActionButton acionará a câmera para ser mostrada quando pressionado.
    6. Nossas imagens serão exibidas em uma grade com duas colunas. No momento, estamos codificando 3 itens nesta grade.
    7. Implementaremos um widget de carregamento de imagem no módulo Adicionar Armazenamento. Até lá, usaremos um espaço reservado para representar imagens.

    Agora podemos substituir o espaço reservado em CameraFlow._pages pelo GalleryPage recém-criado.

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

    Para simplificar a criação de um CameraPage, adicionaremos algumas dependências ao arquivo pubspec.yaml:

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

    Também precisamos fazer uma atualização de configuração para cada plataforma.

    No Android, atualize minSdkVersion para 21 (android > app > build.gradle)

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

    No iOS, atualize o Info.plist para permitir acessar a câmera. (ios > Runner > Info.plist):

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

    Adicione isso a um novo arquivo chamado 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 tirar uma foto, precisaremos obter uma instância de CameraDescription que será fornecido pelo CameraFlow.
    2. Este ValueChanged fornecerá o CameraFlow com o caminho local para a imagem que é capturada pela câmera.
    3. Para garantir que tenhamos uma instância do CameraController, precisamos inicializá-lo no método initState e inicializar _initializeControllerFuture quando for concluído.
    4. O FutureBuilder observará quando o Futuro reserva e mostrará uma demonstração do que a câmera vê ou um CircularProgressIndicator.
    5. O FloatingActionButton acionará _takePicture() quando pressionado.
    6. Este método construirá um caminho temporário para o local da imagem e o transmitirá ao CameraFlow por meio do didProvideImagePath.
    7. Finalmente, precisamos garantir o descarte do CameraController após o descarte da página.

    No CameraFlow, precisamos criar a instância do CameraDescription.

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

    Crie uma função para obter e inicializar _camera.

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

    Chamaremos esta função assim que _CameraFlowState for inicializado.

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

    Por último, substitua o espaço reservado em _pages por uma instância de CameraPage

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

    Agora o CameraPage será inicializado com uma câmera e retornará imagePath quando uma imagem for obtida. Só estamos fechando a câmera pois foi tirada uma foto agora.

  • Adicionar saída

    Para fechar o loop de navegação da interface do usuário, precisamos adicionar um método de saída ao AuthService.

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

    Finalmente, implemente o caso para CameraFlow no Navigator.pages de main.dart.

    ... // _authService.verifyCode)),
    
    // Show Camera Flow
    if (snapshot.data.authFlowStatus == AuthFlowStatus.session)
      MaterialPage(
          child: CameraFlow(shouldLogOut: _authService.logOut))
    
    ... // pages closing ],
  • Testar a aplicação

    Se você reexecutou o aplicativo, precisa ser capaz de navegar em todas as telas do aplicativo.

    EndofModule1-gif

Conclusão

Você implementou com êxito a interface do usuário do aplicativo Galeria de Fotos no Flutter. Você está pronto para implementar o Amplify no seu projeto!

Este módulo foi útil?

Agradecemos a sua atenção
Gostaríamos de saber do que você gostou.
Fechar
Desculpe por ter desapontado
Encontrou algo desatualizado, confuso ou incorreto? Envie seus comentários para nos ajudar a melhorar este tutorial.
Fechar

Inicializar o Amplify