开始使用 AWS

构建 Flutter 应用程序

使用 AWS Amplify 创建简单的 Flutter 应用程序

模块 1:创建和部署 Flutter 应用程序

在本模块中,您将创建 Flutter 应用程序并使用 AWS Amplify 的 Web 托管服务将其部署到云中。

简介

AWS Amplify 是一套工具,它提供易于使用的库,可帮助开发人员更快地构建应用程序,开发人员只需编写几行代码,便可以验证用户身份、存储文件、捕获分析事件等等。

在本模块中,您将创建和构建 Photo Gallery 应用程序的用户界面。这包括注册流程、用于查看图片的库页面,以及使用摄像头拍照的功能。

本模块将为应用程序奠定基础,以便后续模块可以专注于具体类别的实际 Amplify 实施。

您将学到的内容

  • 实现注册和登录流程
  • 在屏幕之间导航
  • 实现小部件网格
  • 使用设备摄像头拍照

重要概念

导航器 – 本教程将使用 Flutter Navigator 2.0,它使用一个页面列表来确定应该使用声明式实现显示哪个视图。

回调 – 为了将数据从一个对象发送到另一个对象,我们将使用回调进行通信。回调类似于函数,它可以从调用站点传递参数,但在其他位置执行代码。

 完成时间

30 分钟

 使用的服务

实施

  • 创建 Flutter 项目

    启动 Visual Studio Code,并用所选的名称创建一个新的 Flutter 项目。

    FlutterApp-Module1Photo1-small

    项目设置完成后,请将 main.dart 中的样板文件代码替换为以下内容:

    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. 我们已将 MyApp 小部件更改为 StatefulWidget。我们将在稍后操作其状态。
    2. MaterialApp 的主小部件是 Navigator,它允许以声明的方式设置导航。
  • 创建身份验证流
    将页面添加到 Navigator 前,我们需要创建代表每个页面的小部件。让我们从登录页面开始,我们需要添加一个新文件 login_page.dart。
    import 'package:flutter/material.dart';
    
    class LoginPage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _LoginPageState();
    }
    
    class _LoginPageState extends State<LoginPage> {
      // 1
      final _usernameController = TextEditingController();
      final _passwordController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        // 2
        return Scaffold(
          // 3
          body: SafeArea(
              minimum: EdgeInsets.symmetric(horizontal: 40),
              // 4
              child: Stack(children: [
                // Login Form
                _loginForm(),
    
                // 6
                // Sign Up Button
                Container(
                  alignment: Alignment.bottomCenter,
                  child: FlatButton(
                      onPressed: () {},
                      child: Text('Don\'t have an account? Sign up.')),
                )
              ])),
        );
      }
    
      // 5
      Widget _loginForm() {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Username TextField
            TextField(
              controller: _usernameController,
              decoration:
                  InputDecoration(icon: Icon(Icons.mail), labelText: 'Username'),
            ),
    
            // Password TextField
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(
                  icon: Icon(Icons.lock_open), labelText: 'Password'),
              obscureText: true,
              keyboardType: TextInputType.visiblePassword,
            ),
    
            // Login Button
            FlatButton(
                onPressed: _login,
                child: Text('Login'),
                color: Theme.of(context).accentColor)
          ],
        );
      }
    
      // 7
      void _login() {
        final username = _usernameController.text.trim();
        final password = _passwordController.text.trim();
    
        print('username: $username');
        print('password: $password');
      }
    }
    1. 由于 LoginPage 需要用户输入,因此我们需要跟踪该状态,方法是为屏幕上的每个字段设置 TextEditingController;在此例中是 username 和 password。
    2. _LoginPageState.build 将返回 Scaffold,它允许针对移动设备对小部件进行正确的格式化。
    3. 观察 SafeArea 很重要,因为应用程序将能够在多个设备上运行。在本例中,我们还利用最小边缘插页在屏幕左右两侧添加填充,这样登录窗体便不会显示在边缘位置。
    4. 用户界面包括主登录窗体和屏幕底部的按钮,该按钮允许用户注册而不是登录。我们在这里使用了一个堆栈,以便更容易地操作每个子小部件的位置。
    5. 虽然创建 _loginForm 函数是完全可选的操作,但它的确可以在一定程度上简化构建方法。我们在这里实现包含用户名和密码文本字段以及登录按钮的用户界面。
    6. 注册按钮将采取交互式语句的形式,如果用户还没有账户,则可以使用此按钮注册一个账户。尚未实现 onPressed 功能。
    7. _login 方法将负责从文本字段控制器中提取值,并创建 AuthCredentials 对象。现在它只是打印每个控制器的值。

    LoginPage 的用户界面还没有完成,我们来将其添加到 main.dart 中的 Navigator 中。

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

    由于页面参数使用 List<Page<dynamic>>,因此我们传入一个单独的 MaterialPage,其中 LoginPage 是子页面。

    运行应用程序后,应该会看到 LoginPage。

    FlutterApp-Module1Photo2-small

    用户需要先注册才能登录。我们在新文件 sign_up_page.dart 中实现 SignUpPage

    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');
      }
    }

    SignUpPage 几乎与 LoginPage 相同,只是它有一个附加的电子邮件字段,且按钮的文本已被更改。

    同样,我们在 main.dart 的 Navigator 中,将 SignUpPage 添加为 MaterialPage。

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

    现在运行应用程序。

    FlutterApp-Module1Photo3-small

    现在,应用程序启动时应该会显示注册屏幕,因为它是 Navigator 页面列表中实现的最后一个页面。Navigator 将页面参数视为一个堆栈,其中最后实现的页面参数显示在最顶部。这意味着,我们现在会看到 SignUpPage 堆叠在 LoginPage 顶部。

    如果想显示不同的页面,必须在列表中实现逻辑,以决定特定页面的显示时机。要完成这些更新,可以创建一个 Stream,并将 Navigator 嵌套在 StreamBuilder 中。

    创建一个名为 auth_service.dart 的新文件,并添加以下内容:

    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 是一个枚举,它涵盖身份验证流可以具有的四种不同状态:登录页、注册页、验证页或会话。我们稍后添加最后两种页面。
    2. AuthState 是我们要在流中观察的实际对象,它包含属性 authFlowStatus。
    3. AuthService 有两个用途:管理 AuthState 的流控制器,以及包含要在下一个模块中添加的所有身份验证功能。
    4. authStateController 负责发送要观察的新的 AuthState 的下游。
    5. 这是一个简单的函数,用于将 AuthState 流更新为 signUp。
    6. 它与 showSignUp 实现的功能相同,但将更新流以发送登录。

    再次打开 main.dart,并在 _MyAppState 中创建一个 AuthService 的实例。

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

    现在我们可以将 Navigator 封装在 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. 将 Navigator 封装在 StreamBuilder 中是为了观察发出 AuthState 的流。
    2. 我们通过访问 AuthService 实例中的 authStateController 来实现对 AuthState 流的访问。
    3. 流不一定包含数据。为了从 AuthState 类型的数据安全地访问 authFlowStatus,我们先在这里进行检查。
    4. 如果流发出 AuthFlowStatus.login,则显示 LoginPage。
    5. 如果流发出 AuthFlowStatus.signUp,则显示 SignUpPage。
    6. 如果流不包含数据,则显示 CircularProgressIndicator。

    为了确保流从一开始就包含数据,需要立即发出一个值。为此,我们可以在 _MyAppState 进行初始化时发送 AuthFlowStatus.login。

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

    如果现在运行应用程序,它应显示 LoginPage,因为这是通过流发出的唯一值。

    我们仍需实现在 LoginPage 和 SignUpPage 之间进行切换的功能。

    导航到 login_page.dart 并添加以下内容:

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

    构造函数现在接受 VoidCallback 作为实参,它可以触发 main.dart 中的一些功能以及从 _LoginPageState 调用的功能。

    在 _LoginPageState 中,将 shouldShowSignUp 作为注册按钮的实参来传递:

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

    回到 main.dart 中,我们需要为 LoginPage 的 shouldShowSignUp 形参传递一个实参:

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

    运行应用程序并按下 LoginPage 上的注册按钮。它现在应导航到 SignUpPage

    我们需要能够为 SignUpPage 实现一样的功能,这样一来,用户便可点击屏幕底部的按钮,在注册和登录之间进行切换。

    添加以下 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.')),

    正如我们使用 LoginPage 中实现的功能,当用户按下屏幕底部的按钮时,SignUpPage 将触发 VoidCallback。

    现在只需更新 main.dart 以接受 shouldShowLogin 的实参。

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

    如果现在运行应用程序,您会发现可以在 LoginPage 和 SignUpPage 之间切换了。

    这些页面需要实现的最后一项功能是将用户为每个字段输入的内容作为凭据进行传递,并可对其进行处理,以用于登录/注册。

    创建一个名为 auth_credentials.dart 的新文件,并添加以下内容:

    // 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 是一个抽象类,我们使用它作为执行登录或注册所需的最少信息的基线。这使我们几乎可以互换地使用 LoginCredentials 和 SignUpCredentials。
    2. LoginCredentials 是简单具体的 AuthCredentials 实现,因为登录只需要用户名和密码。
    3. 它几乎与 LoginCredentials 完全相同,唯一的区别是在注册时需要填写电子邮件字段。

    我们现在可以将登录和注册方法添加到 AuthService,后者将接受各自的凭据,并将 Navigator 的状态更改为正确的页面。

    将以下两个函数添加到 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. 当用户传递任何 AuthCredentials 时,我们将执行一些逻辑,并最终将用户置于会话状态。
    2. 注册时需要输入验证码来验证输入的电子邮件。因此,注册逻辑应将状态更改为验证。

    首先更新 LoginPage,以通过 ValueChanged 属性发送 LoginCredentials。

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

    现在可以传递 _LoginPageState 中 _login() 方法中的凭据:

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

    我们来为 SignUpPage 实现类似的功能:

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

    并创建凭据:

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

    现在连接 main.dart 中的所有项:

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

    该操作将完成 LoginPage 和 SignUpPage 的所有功能,但正如我们在 AuthFlowStatus 中看到的那样,我们仍然需要实现一个用于验证的页面和用于呈现会话的页面。

    让我们在新文件 verification_page.dart 中添加 VerificationPage:

    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 实际上只是 LoginPage 的精简版本,且只在小部件树上传递验证码。

    回到 auth_service.dart,需要有一个方法来处理验证码并将状态更新为会话。

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

    现在,将 VerificationPage 添加到 main.dart 的 Navigator 中。

    ... // shouldShowLogin: _authService.showLogin)),
    
    // Show Verification Code Page
    if (snapshot.data.authFlowStatus == AuthFlowStatus.verification)
      MaterialPage(child: VerificationPage(
        didProvideVerificationCode: _authService.verifyCode))
    
    ... // pages closing ],
  • 创建摄像头/库流

    实现 VerificationPage 后,我们可以移至用户登录时显示的用户界面。我们将显示一个图像库,并能够使用设备摄像头拍照。我们将创建一个 CameraFlow 小部件用于处理状态更改,指示何时显示每个屏幕。

    创建一个名为 camera_flow.dart 的新文件,并添加以下内容:

    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 需要触发并更新 main.dart 中的状态。我们将在创建 GalleryPage 之后不久实现该功能。
    2. 该标记充当状态,负责指示何时应显示摄像头。
    3. 为了确保 Navigator 在 _shouldShowCamera 更新时得到更新,我们使用一个计算属性,以根据当前状态返回正确的导航堆栈。我们目前使用的是 Placeholder 页面。
    4. 与 _MyAppState 类似,我们使用 Navigator 小部件来确定在给定时间内应为会话显示哪个页面。
    5. 此方法允许我们切换是否显示摄像头,而无需在调用站点实现 setState()。

    在 gallery_page.dart 中创建 GalleryPage:

    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 直接显示图像,因此可将其作为 StatelessWidget 来实现。
    2. 此 VoidCallback 将连接到 CameraFlow 中的 shouldLogOut 方法。
    3. 此 VoidCallback 将更新 CameraFlow 中的 _shouldShowCamera 标记。
    4. 注销按钮在 AppBar 中作为一项操作来实现,并在用户点击该按钮时调用 shouldLogOut。
    5. 此 FloatingActionButton 将在用户按下按钮时触发显示摄像头。
    6. 图像将显示在一个包含两个列的网格中。我们目前正对该网格中的 3 个项进行硬编码。
    7. 我们将在“添加存储”模块中实现一个图像加载小部件。在此之前,我们将使用 Placeholder 来表示图像。

    现在,我们可以将 CameraFlow._pages 中的 Placeholder 替换为新创建的 GalleryPage。

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

    为了简化创建 CameraPage 的过程,我们将向 pubspec.yaml 文件添加几个依赖项:

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

    我们还需要更新每个平台的一些配置。

    在 Android 上,将 minSdkVersion 更新为 21 (android > app > build.gradle)

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

    在 iOS 上,更新 Info.plist 以允许访问摄像头 (ios > Runner > Info.plist):

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

    现在,将其添加到名为 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. 为了拍照,我们需要获取由 CameraFlow 提供的 CameraDescription 实例。
    2. 此 ValueChanged 将向 CameraFlow 提供通过摄像头拍摄的图像的本地路径。
    3. 为了确保拥有 CameraController 实例,我们在 initState 方法中对其初始化,并在其完成后初始化 _initializeControllerFuture。
    4. FutureBuilder 随后将观察 Future 何时返回,并显示摄像头所拍摄图像的预览或 CircularProgressIndicator。
    5. FloatingActionButton 将在用户按下按钮后触发 _takePicture()。
    6. 此方法将构造指向图像位置的临时路径,并通过 didProvideImagePath 将其传递回 CameraFlow。
    7. 最后,我们需要确保在释放页面后释放 CameraController。

    回到 CameraFlow,我们需要创建 CameraDescription 实例。

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

    创建一个获取和初始化 _camera 的函数。

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

    我们将在 _CameraFlowState 初始化立即后调用此函数。

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

    最后,将 _pages 中的 Placeholder 替换为 CameraPage 的实例

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

    现在,CameraPage 将随摄像头初始化,并在图像拍摄完成后返回 imagePath。现在,我们仅在图片拍摄完成后关闭摄像头。

  • 添加“注销”

    为了关闭用户界面的导航循环,我们需要向 AuthService 添加一个注销方法。

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

    最后,在 main.dart 的 Navigator.pages 中实现 CameraFlow 的用例。

    ... // _authService.verifyCode)),
    
    // Show Camera Flow
    if (snapshot.data.authFlowStatus == AuthFlowStatus.session)
      MaterialPage(
          child: CameraFlow(shouldLogOut: _authService.logOut))
    
    ... // pages closing ],
  • 测试应用程序

    如果再次运行应用程序,您应该能够浏览应用程序的所有屏幕。

    EndofModule1-gif

结论

您已在 Flutter 中成功实现 Photo Gallery 应用程序的用户界面。 现在可以在您的项目中实施 Amplify 了!

此模块有帮助吗?

谢谢
请告知我们您喜欢什么。
关闭
很抱歉让您失望了
是否存在过时、令人困惑或不准确的内容? 请向我们提供反馈,帮助我们改进本教程。
关闭

初始化 Amplify