Conceitos básicos da AWS
Criar uma aplicação Flutter
Criar uma aplicação Flutter simples usando o AWS Amplify

criar um aplicativo flutter
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.
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), ), ); } }
- Alteramos o widget MyApp para um StatefulWidget. Posteriormente, manipularemos o seu estado.
- O widget inicial do MaterialApp é um navegador que permitirá a configuração do nosso navegador de forma declarativa.
-
Criar o fluxo de autenticaçãoAntes 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'); } }
- 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.
- _LoginPageState.build retornará um Scaffold que permitirá a formatação adequada de nossos widgets para um dispositivo móvel.
- É 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.
- 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.
- 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.
- 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.
- 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.
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.
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); } }
- 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.
- AuthState é o objeto real que observaremos em nosso stream e ele conterá authFlowStatus como uma propriedade.
- 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.
- authStateController é responsável por enviar novo downstream AuthState a ser observado.
- Esta é uma função simples para atualizar o stream AuthState para signUp.
- 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 );
- Quebramos o navegador com um StreamBuilder que espera observar um stream que emite AuthState.
- Acessamos o stream AuthState no authStateController a partir da instância de AuthService.
- 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.
- Se o stream emitir AuthFlowStatus.login, mostraremos LoginPage.
- Se o stream emitir AuthFlowStatus.signUp, mostraremos SignUpPage.
- 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); }
- 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.
- LoginCredentials é uma implementação concreta simples de AuthCredentials pois o registro em log só exige nome do usuário e senha.
- 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 }
- Quando um usuário passar AuthCredentials, executaremos uma certa lógica e, por fim, colocaremos o usuário em um estado da sessão.
- 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; }); } }
- 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.
- Este sinalizador funcionará como o estado responsável por indicar quando a câmera deve ser mostrada ou não.
- 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.
- Semelhante ao _MyAppState, estamos usando o widget Navigator para determinar que página deve ser mostrada em determinado momento para uma sessão.
- 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(); }); } }
- GalleryPage simplesmente exibirá imagens; então, ele pode ser implementado como StatelessWidget.
- Este VoidCallback se conectará ao método shouldLogOut em CameraFlow.
- Este VoidCallback atualizará o sinalizador _shouldShowCamera em CameraFlow.
- O botão de logout é implementado como uma ação no AppBar e chama shouldLogOut quando tocam nele.
- Este FloatingActionButton acionará a câmera para ser mostrada quando pressionado.
- Nossas imagens serão exibidas em uma grade com duas colunas. No momento, estamos codificando 3 itens nesta grade.
- 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(); } }
- Para tirar uma foto, precisaremos obter uma instância de CameraDescription que será fornecido pelo CameraFlow.
- Este ValueChanged fornecerá o CameraFlow com o caminho local para a imagem que é capturada pela câmera.
- Para garantir que tenhamos uma instância do CameraController, precisamos inicializá-lo no método initState e inicializar _initializeControllerFuture quando for concluído.
- O FutureBuilder observará quando o Futuro reserva e mostrará uma demonstração do que a câmera vê ou um CircularProgressIndicator.
- O FloatingActionButton acionará _takePicture() quando pressionado.
- Este método construirá um caminho temporário para o local da imagem e o transmitirá ao CameraFlow por meio do didProvideImagePath.
- 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.