Erste Schritte mit AWS

Erstellen eine Flutter-Anwendung

Erstellen Sie eine einfache Flutter-Anwendung mit AWS Amplify

Modul 1: Erstellen und Bereitstellen einer Flutter-App

In diesem Modul erstellen Sie eine Flutter-Anwendung und stellen sie mit dem Webhosting-Service von AWS Amplify in der Cloud bereit.

Einführung

AWS Amplify ist eine Suite von Tools, mit denen Entwickler Anwendungen schneller bauen können, indem sie einfach zu verwendende Bibliotheken bereitstellen, die es ermöglichen, Benutzer zu authentifizieren, Dateien zu speichern, Analyse-Ereignisse zu erfassen und vieles mehr, mit nur wenigen Zeilen Code.

In diesem Modul erstellen Sie die Benutzeroberfläche einer Fotogalerie-Anwendung und bauen sie auf. Dazu gehören der Anmeldevorgang, eine Galerieseite zum Betrachten von Bildern und die Möglichkeit, ein Bild mit der Kamera aufzunehmen.

Dieses Modul bildet die Grundlage unserer Amplify-Anwendung, so dass sich die folgenden Module auf die tatsächlichen Amplify-Implementierungen für die jeweilige Kategorie konzentrieren können.

Lerninhalte

  • Implementierung eines Registrierungs- und Anmeldungsflows
  • Navigation zwischen Bildschirmen
  • Implementierung eines Rasters von Widgets
  • Bilder mit der Gerätekamera aufnehmen

Wichtige Konzepte

Navigator: In diesem Tutorial wird der Flutter Navigator 2.0 verwendet, der anhand einer Liste von Seiten bestimmt, welche Ansicht mit Hilfe einer deklarativen Implementierung angezeigt werden soll.

Callbacks: Um Daten von einem Objekt zu einem anderen zu senden, werden wir Rückrufe zur Kommunikation verwenden. Ein Callback ähnelt insofern einer Funktion, als dass ihm ein Argument von der Aufrufstelle übergeben werden kann, aber er führt an anderer Stelle Code aus.

 Veranschlagte Zeit

30 Minuten

 Verwendete Services

Implementierung

  • Erstellen eines Flutter-Projekts

    Starten Sie Visual Studio Code und erstellen Sie ein neues Flutter-Projekt mit einem Namen Ihrer Wahl.

    FlutterApp-Module1Photo1-small

    Sobald Ihr Projekt eingerichtet ist, ersetzen Sie den Boilerplate-Code in main.dart durch den folgenden Code:

    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. Wir haben das MyApp-Widget in ein StatefulWidget umgewandelt. Wir werden seinen Zustand später manipulieren.
    2. Das Home-Widget unserer MaterialApp ist ein Navigator, der es erlaubt, unsere Navigation auf deklarative Weise einzurichten.
  • Erstellen des Authentifizierungsflows
    Bevor wir Seiten zu unserem Navigator hinzufügen können, müssen wir die Widgets erstellen, die jede unserer Seiten repräsentieren werden. Beginnen wir mit der Anmeldeseite, die wir in eine neue Datei login_page.dart einfügen werden.
    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. Da LoginPage Benutzereingaben erfordert, müssen wir diesen Zustand verfolgen, indem wir einen TextEditingController für jedes Feld auf dem Bildschirm haben; in diesem Fall: Benutzername und Passwort.
    2. _LoginPageState.build wird ein Scaffold zurückgeben, mit dem unsere Widgets für ein mobiles Gerät richtig formatiert werden können.
    3. Es ist wichtig, den SafeArea zu beachten, da die Anwendung auf mehreren Geräten ausgeführt werden kann. In diesem Fall nutzen wir auch die minimalen Randeinschübe, um sowohl auf der linken als auch auf der rechten Seite des Bildschirms Polsterung hinzuzufügen, damit das Anmeldeformular nicht von Rand zu Rand verläuft.
    4. Unsere Benutzeroberfläche wird aus dem primären Anmeldeformular und einer Schaltfläche am unteren Bildschirmrand bestehen, mit der sich der Benutzer anmelden kann, anstatt sich einzuloggen. Wir verwenden hier einen Stack, um es einfacher zu machen, die Platzierung der einzelnen untergeordneten Widgets zu manipulieren.
    5. Das Erstellen einer _loginForm-Funktion ist völlig optional, aber es bringt die Build-Methode ein wenig durcheinander. Hier implementieren wir die Benutzeroberfläche für die Textfelder für Benutzername und Passwort sowie die Anmeldeschaltfläche.
    6. Die Schaltfläche "Anmelden" hat die Form eines interaktiven Satzes, der es dem Benutzer erlaubt, sich anzumelden, wenn er noch kein Konto hat. Es ist noch keine onPressed-Funktionalität implementiert.
    7. Es ist noch keine onPressed-Funktionalität implementiert. Die _login-Methode wird dafür verantwortlich sein, die Werte aus den Textfeld-Controllern zu extrahieren und ein AuthCredentials-Objekt zu erstellen. Im Moment werden lediglich die Werte der einzelnen Regler ausgedruckt.

    The UI of LoginPage is not finished, let's add it to the Navigator in main.dart.

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

    Der Parameter pages nimmt eine List<Page<dynamic>>, also übergeben wir eine einzelne MaterialPage, wobei unsere LoginPage das Kind ist.

    Lassen Sie die App laufen und Sie sollten Ihre LoginPage sehen.

    FlutterApp-Module1Photo2-small

    Der Benutzer muss sich erst registrieren können, bevor er sich anmelden kann. Implementieren wir die SignUpPage in einer neuen Datei 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');
      }
    }

    Unsere SignUpPage ist fast identisch mit der LoginPage mit der Ausnahme, dass sie ein zusätzliches Feld für E-Mail hat und der Text für die Schaltflächen geändert wurde.

    Fügen wir die SignUpPage auch als MaterialPage in den Navigator von main.dart ein.

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

    Führen Sie die App jetzt aus.

    FlutterApp-Module1Photo3-small

    Der Anmeldebildschirm sollte nun beim Start der App angezeigt werden, da dies die letzte Seite ist, die in der Seitenliste unseres Navigators implementiert wurde. Der Navigator behandelt das Seitenargument wie einen Stapel, bei dem der letzte Eintrag ganz oben steht. Das bedeutet, dass wir derzeit die SignUpPage über der LoginPage sehen.

    Wenn wir verschiedene Seiten anzeigen wollen, müssen wir eine Logik innerhalb unserer Liste implementieren, um zu bestimmen, wann bestimmte Seiten angezeigt werden sollen. Wir können diese Aktualisierungen durchführen, indem wir einen Stream erstellen und unseren Navigator in einem StreamBuilder verschachteln.

    Erstellen Sie eine neue Datei namens auth_service.dart und fügen Sie Folgendes hinzu:

    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 ist eine Aufzählung, die die vier verschiedenen Zustände abdeckt, in denen sich unser Authflow befinden kann: die Anmeldeseite, die Registrierungsseite, die Verifizierungsseite oder eine Sitzung. Die letzten beiden Seiten werden wir in Kürze hinzufügen.
    2. AuthState ist das eigentliche Objekt, das wir in unserem Stream beobachten werden, und es wird authFlowStatus als Eigenschaft enthalten.
    3. Unser AuthService wird zwei Zwecken dienen, er verwaltet den Stream Controller von AuthState und enthält alle unsere Authentifizierungsfunktionen, die im nächsten Modul hinzugefügt werden.
    4. authStateController ist dafür verantwortlich, neue AuthStates zur Beobachtung nach unten zu schicken.
    5. Dies ist eine einfache Funktion zur Aktualisierung des AuthState-Streams für die Anmeldung.
    6. Dies macht dasselbe wie showSignUp, aktualisiert aber den Stream, um Login zu senden.

    Öffnen Sie main.dart erneut und fügen Sie eine Instanz von AuthService in _MyAppState hinzu.

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

    Jetzt können wir den Navigator in einen StreamBuilder einpacken.

    ... // 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. Wir haben unseren Navigator mit einem StreamBuilder ausgestattet, der einen AuthState emittierenden Stream beobachten soll.
    2. Wir greifen auf den AuthState-Stream zu, indem wir vom authStateController aus der Instanz von AuthService darauf zugreifen.
    3. Der Stream kann Daten enthalten oder auch nicht. Um von unseren Daten, die vom Typ AuthState sind, sicher auf authFlowStatus zugreifen zu können, implementieren wir hier zunächst die Prüfung.
    4. Wenn der Stream AuthFlowStatus.login ausgibt, wird LoginPage angezeigt.
    5. Wenn der Stream AuthFlowStatus.signUp ausgibt, zeigen wir SignUpPage an.
    6. Wenn der Stream keine Daten enthält, wird ein CircularProgressIndicator angezeigt.

    Um sicherzustellen, dass der Stream von Anfang an Daten enthält, muss sofort ein Wert ausgegeben werden. Wir können dies erreichen, indem wir AuthFlowStatus.login senden, wenn _MyAppState initialisiert wird.

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

    Wenn wir die Anwendung jetzt ausführen, sollte sie LoginPage anzeigen, da dies der einzige Wert ist, der über den Stream ausgegeben wurde.

    Wir müssen noch die Möglichkeit implementieren, zwischen LoginPage und SignUpPage zu wechseln.

    Navigieren Sie zu login_page.dart und fügen Sie Folgendes hinzu:

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

    Unser Konstrukteur akzeptiert jetzt einen VoidCallback als Argument, der eine gewisse Funktionalität in main.dart auslösen kann und aus _LoginPageState aufgerufen wird.

    Übergeben Sie shouldShowSignUp als Argument für die Anmeldeschaltfläche in unserem _LoginPageState:

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

    Zurück in main.dart müssen wir eine Argumentation für den Parameter shouldShowSignUp der LoginPage übergeben:

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

    Führen Sie die Anwendung aus und drücken Sie die Anmeldeschaltfläche auf LoginPage. Sie sollte nun zur SignUpPage navigieren.

    Wir müssen in der Lage sein, dasselbe für die SignUpPage zu tun, damit der Benutzer zwischen Anmeldung und Login wechseln kann, indem er auf die Schaltfläche am unteren Bildschirmrand tippt.

    Fügen Sie Folgendes zu sign_up_page.dart hinzu:

    ... // 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.')),

    Genau wie wir es mit LoginPage implementiert haben, löst SignUpPage den VoidCallback aus, wenn der Benutzer auf die Schaltfläche am unteren Bildschirmrand drückt.

    Jetzt einfach main.dart aktualisieren, um eine Argumentation für shouldShowLogin zu akzeptieren.

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

    Wenn Sie die App dieses Mal ausführen, werden Sie feststellen, dass Sie zwischen der LoginPage und der SignUpPage hin- und herschalten können.

    Das letzte, was für jede dieser Seiten benötigt wird, ist eine Möglichkeit, die Benutzereingaben für jedes Feld als Anmeldedaten zu übergeben, die für die Anmeldung/Registrierung verarbeitet werden können.

    Erstellen Sie eine neue Datei namens auth_credentials.dart und fügen Sie Folgendes hinzu:

    // 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 ist eine abstrakte Klasse, die wir für eine Baseline mit den Mindestinformationen verwenden werden, die für die Anmeldung oder Registrierung erforderlich sind. Dies wird es uns ermöglichen, LoginCredentials und SignUpCredentials fast austauschbar zu verwenden.
    2. LoginCredentials eine einfache konkrete Implementierung von AuthCredentials, da für die Anmeldung nur der Benutzername und das Passwort benötigt werden.
    3. Fast identisch mit den LoginCredentials, aber mit E-Mail als zusätzlichem Feld, das für die Anmeldung erforderlich ist.

    Wir können jetzt Anmelde- und Registrierungsmethoden zum AuthService hinzufügen, die die entsprechenden Anmeldedaten akzeptieren und den Status des Navigators auf die richtige Seite ändern.

    Fügen Sie diese beiden Funktionen zu auth_service.dart hinzu:

    ... // 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. Wenn ein Benutzer irgendwelche AuthCredentials übergibt, führen wir eine gewisse Logik aus und versetzen den Benutzer schließlich in einen Sitzungszustand.
    2. Für die Anmeldung ist es erforderlich, dass die eingegebene E-Mail durch Eingabe eines Verifizierungscodes verifiziert wird. Daher sollte die Anmeldelogik den Zustand zur Verifizierung führen.

    Lassen Sie uns damit beginnen, LoginPage zu aktualisieren, um LoginCredentials über eine ValueChanged-Eigenschaft zu senden.

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

    Wir können jetzt unsere Anmeldedaten über die _login()-Methode in _LoginPageState übergeben:

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

    Lassen Sie uns etwas Ähnliches für SignUpPage implementieren:

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

    Und erstellen Sie die Anmeldedaten:

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

    Verbinden Sie nun alles in main.dart:

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

    Damit sind LoginPage und SignUpPage abgeschlossen, aber wie wir bei AuthFlowStatus gesehen haben, müssen wir noch eine Seite zur Überprüfung und Seiten zur Darstellung einer Sitzung implementieren.

    Fügen wir VerificationPage in einer neuen Datei verification_page.dart hinzu:

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

    Die VerificationPage ist in Wirklichkeit nur eine abgespeckte Version von LoginPage und gibt nur einen Verifizierungscode im Widgetbaum weiter.

    Zurück in auth_service.dart muss es eine Methode geben, um den Verifizierungscode zu behandeln und den Status auf Session zu aktualisieren.

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

    Fügen Sie nun die VerificationPage zum Navigator von main.dart hinzu.

    ... // shouldShowLogin: _authService.showLogin)),
    
    // Show Verification Code Page
    if (snapshot.data.authFlowStatus == AuthFlowStatus.verification)
      MaterialPage(child: VerificationPage(
        didProvideVerificationCode: _authService.verifyCode))
    
    ... // pages closing ],
  • Erstellen des Kamera-/Galerie-Flow

    Mit der Implementierung von VerificationPage können wir auf die UI wechseln, wenn sich ein Benutzer angemeldet hat. Wir werden eine Bildergalerie zeigen und die Möglichkeit haben, ein Bild mit der Gerätekamera aufzunehmen. Wir werden ein CameraFlow-Widget erstellen, das mit Zustandsänderungen umgehen kann, die bestimmen, wann jeder Bildschirm angezeigt werden soll.

    Erstellen Sie eine neue Datei namens camera_flow.dart und fügen Sie Folgendes hinzu:

    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 muss auslösen, wenn sich der Benutzer abmeldet und den Status wieder in main.dart aktualisieren. Wir werden diese Funktionalität kurz nach der Erstellung der GalleryPage implementieren.
    2. Diese Flagge fungiert als der Zustand, der dafür verantwortlich ist, wann die Kamera zeigen soll oder nicht.
    3. Um sicherzustellen, dass unser Navigator aktualisiert wird, wenn _shouldShowCamera aktualisiert wird, verwenden wir eine berechnete Eigenschaft, um den korrekten Navigationsstapel basierend auf dem aktuellen Status zurückzugeben. Derzeit verwenden wir vorerst Platzhalter-Seiten.
    4. Ähnlich wie _MyAppState verwenden wir ein Navigator-Widget, um festzulegen, welche Seite zu einem bestimmten Zeitpunkt während einer Sitzung angezeigt werden soll.
    5. Mit dieser Methode können wir umschalten, ob die Kamera angezeigt wird oder nicht, ohne setState() an der Aufrufstelle implementieren zu müssen.

    Erstellen Sie die GalleryPage in 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 wird einfach Bilder anzeigen, so dass es als StatelessWidget implementiert werden kann.
    2. Dieser VoidCallback verbindet sich mit der shouldLogOut-Methode in CameraFlow.
    3. Dieser VoidCallback aktualisiert das _shouldShowCamera-Flag in CameraFlow.
    4. Unser Logout-Button ist als Aktion in der AppBar implementiert und ruft beim Antippen shouldLogOut auf.
    5. Dieser FloatingActionButton löst beim Drücken die Anzeige unserer Kamera aus.
    6. Unsere Bilder werden in einem Raster mit zwei Spalten angezeigt. Wir sind derzeit dabei, 3 Elemente in diesem Raster hart zu kodieren.
    7. Wir werden ein Bildlade-Widget im Modul "Speicher hinzufügen" implementieren. Bis dahin werden wir Platzhalter zur Darstellung von Bildern verwenden.

    Wir können jetzt den Platzhalter in unseren CameraFlow._Seiten durch die neu erstellte GalleryPage ersetzen.

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

    Um das Erstellen einer CameraPage zu vereinfachen, werden wir der Datei pubspec.yaml einige Abhängigkeiten hinzufügen:

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

    Wir müssen auch einige Konfigurationsaktualisierungen für jede Plattform vornehmen.

    Auf Android, minSdkVersion auf 21 aktualisieren (Android > app > build.gradle)

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

    Aktualisieren Sie für iOS die Info.plist, um den Zugriff auf die Kamera zu ermöglichen. (ios > Runner > Info.plist):

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

    Fügen Sie dies nun zu einer neuen Datei namens camera_page.dart hinzu:

    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. Um ein Bild zu machen, benötigen wir eine Instance von CameraDescription, die uns vom CameraFlow zur Verfügung gestellt wird.
    2. Dieser ValueChanged liefert dem CameraFlow den lokalen Pfad zu dem Bild, das von der Kamera aufgenommen wird.
    3. Um sicherzustellen, dass wir eine CameraController-Instance haben, initialisieren wir sie in der Methode initState und initialisieren _initializeControllerFuture, sobald sie fertig ist.
    4. Der FutureBuilder wird dann beobachten, wann die Zukunft zurückgegeben wird und entweder eine Vorschau dessen zeigen, was die Kamera sieht, oder einen CircularProgressIndicator.
    5. Der FloatingActionButton löst _takePicture() aus, wenn er gedrückt wird.
    6. Mit dieser Methode wird ein temporärer Pfad zum Bildstandort erstellt und über didProvideImagePath an CameraFlow zurückgegeben.
    7. Schließlich müssen wir sicherstellen, dass wir den CameraController entsorgen, sobald die Seite entsorgt wurde.

    Zurück in CameraFlow müssen wir die Instance CameraDescription erstellen.

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

    Erstellen Sie eine Funktion zum Abrufen und Initialisieren der _camera.

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

    Wir werden diese Funktion aufrufen, sobald _CameraFlowState initialisiert ist.

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

    Zuletzt ersetzen Sie den Platzhalter in _pages durch eine Instance von CameraPage

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

    Nun wird die CameraPage mit einer Kamera initialisiert und gibt den imagePath zurück, sobald ein Bild aufgenommen wurde. Wir schließen die Kamera erst dann, wenn gerade jetzt ein Bild aufgenommen wird.

  • Abmeldung hinzufügen

    Um die Navigationsschleife unserer UI zu schließen, müssen wir eine Abmeldemethode zu AuthService hinzufügen.

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

    Implementieren Sie schließlich den Fall für CameraFlow in den Navigator.pages von main.dart.

    ... // _authService.verifyCode)),
    
    // Show Camera Flow
    if (snapshot.data.authFlowStatus == AuthFlowStatus.session)
      MaterialPage(
          child: CameraFlow(shouldLogOut: _authService.logOut))
    
    ... // pages closing ],
  • Die Anwendung testen

    Wenn Sie die App erneut ausführen, sollten Sie in der Lage sein, durch alle Bilder der App zu navigieren.

    EndofModule1-gif

Fazit

Sie haben die UI der Fotogalerie-App erfolgreich in Flutter-App implementiert. Sie sind bereit, Amplify in Ihr Projekt zu implementieren!

War das Modul hilfreich?

Vielen Dank
Bitte teilen Sie uns mit, was Ihnen gefallen hat.
Schließen
Es tut uns Leid Sie zu enttäuschen
Ist etwas veraltet, verwirrend oder ungenau? Bitte helfen Sie uns, dieses Tutorial zu verbessern, indem Sie Feedback geben.
Schließen

Amplify starten