firebase - wurzelsand/flutter-memos GitHub Wiki

Firebase

flutter.dev/codelabs

Codelab: Set up firebase authentication

Codelab: App with authentication and firestore database

Host your app

Create a simple login app with Flutterfire without using flutterfire_ui or firebase_ui

  1. Install Flutterfire CLI and Firebase CLI. Login.

    dart pub global activate flutterfire_cli
    sudo npm install -g firebase-tools
    firebase login
    
  2. Open the Firebase Console to start a new project with any name (here: "godimu"):

  3. Add Email/Password as Sign-in method:

  4. Create new Flutter project and add dependencies for Firebase:

    > flutter create --empty my_app
    > cd my_app
    > flutter pub add firebase_core
    > flutter pub add firebase_auth
    
  5. So that our apps have access to Firebase and the configuration file firebase_options.dart is generated:

    > flutterfire configure
    "Select a Firebase project to configure your Flutter application with"
    > godimu (godimu)
    "Which platforms should your configuration support?"
    > android ios macos web
    "The files android/build.gradle & android/app/build.gradle will be updated to apply Firebase configuration and gradle build plugins. Do you want to continue?"
    > yes
    "Firebase configuration file lib/firebase_option.dart generated successfully with the following Firebase apps: ..."
    
  6. Reload your console web page to see your newly assigned apps. Open the Android settings to configure the Support email:

  7. Add a few users that you will allow to log in:

  8. Edit lib/main.dart:

    import 'package:flutter/material.dart';
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:firebase_core/firebase_core.dart';
    
    import 'firebase_options.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MainApp());
    }
    
    class MainApp extends StatelessWidget {
      const MainApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: StreamBuilder<User?>(
                stream: FirebaseAuth.instance.authStateChanges(),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return ElevatedButton(
                      onPressed: () {
                        FirebaseAuth.instance.signOut();
                      },
                      child: const Text('Logout'),
                    );
                  }
                  return const AuthGate();
                },
              ),
            ),
          ),
        );
      }
    }
    
    class AuthGate extends StatefulWidget {
      const AuthGate({super.key});
    
      @override
      State<AuthGate> createState() => _AuthGateState();
    }
    
    class _AuthGateState extends State<AuthGate> {
      final _emailController = TextEditingController();
      final _passwordController = TextEditingController();
    
      final _formKey = GlobalKey<FormState>();
    
      @override
      Widget build(BuildContext context) {
        return Form(
          key: _formKey,
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxWidth: 400),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextFormField(
                  controller: _emailController,
                  decoration: const InputDecoration(
                    hintText: 'Email',
                    border: OutlineInputBorder(),
                  ),
                  keyboardType: TextInputType.emailAddress,
                ),
                const SizedBox(height: 16),
                TextFormField(
                  controller: _passwordController,
                  obscureText: true,
                  decoration: const InputDecoration(
                    hintText: 'Password',
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 16),
                ElevatedButton(
                  onPressed: () async {
                    try {
                      await FirebaseAuth.instance.signInWithEmailAndPassword(
                          email: _emailController.text,
                          password: _passwordController.text);
                    } on FirebaseAuthException catch (e) {
                      if (!context.mounted) return;
                      ScaffoldMessenger.of(context)
                        ..hideCurrentSnackBar()
                        ..showSnackBar(
                          SnackBar(content: Text(e.message ?? 'Error')),
                        );
                    }
                  },
                  child: const Text('Login'),
                ),
              ],
            ),
          ),
        );
      }
    
      @override
      void dispose() {
        _emailController.dispose();
        _passwordController.dispose();
        super.dispose();
      }
    }
  9. Launch the program on an Android device

  10. To host the app on Firebase:

    > firebase init
    "Which Firebase features do you want to set up for this directory?"
    > Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
    "Please select an option:"
    > Use an existing project
    "Select a default Firebase project for this directory:"
    > godimu (godimu)
    "What do you want to use as your public directory?"
    > build/web
    "Configure as a single-page app (rewrite all urls to /index.html)?"
    > Yes
    "Set up automatic builds and deploys with GitHub?"
    > No
    "Wrote build/web/index.html"
    
    > flutter build web
    > firebase deploy
    "Hosting URL: https://godimu.web.app"
    

Firebase with flutterfire_ui and hosting

FlutterFire UI is deprecated

Starting from the first Firebase codelab from Google, we create a program that runs on iOS, Android, macOS and web. We also host it on Firebase.

  1. Open Firebase Console.
  2. Add a project with an arbitrary name, here: "Attentive Bloom"
    • Project name: "Attentive Bloom"
    • Disable Google Analytics
  3. Click "Authentication" image button. Click "Get started" image button.
    • Sign-in method
    • Enable Email/Password
  4. Open terminal:
    • flutter create --empty attentive_bloom
    • cd attentive_bloom
    • firebase login
    • firebase projects:list (to see attentivebloom)
    • flutterfire configure
      • Select attentivebloom. File firebase_options.dart is generated.
    • flutter pub add firebase_core
    • flutter pub add firebase_auth
    • flutter pub add flutterfire_ui
  5. Open Firebase Console.
    • Reload.
    • Edit Support email of Android apps settings.
  6. Write your app. Code: see below.
  7. Running on MacOS will fail:
    • To macos/Podfile add:
      target.build_configurations.each do |config|
        config.build_settings.delete 'MACOSX_DEPLOYMENT_TARGET'
      end
      
      after line:
      flutter_additional_macos_build_settings(target)
      
    • To Runner/DebugProfile.entitlements and Runner/Release.entitlements add
      <key>com.apple.security.network.client</key>
      <true/>
      
    • Maybe keychain problems.
  8. Open Firebase Console and choose Project Overview.
  9. To add an app to get started click web-icon.
    • Register app: "Attentive Bloom".
    • Enable Also set up Firebase Hosting.
    • next => next => Deploy to Firebase Hosting
  10. Change to terminal:
    • firebase login
    • firebase init
      • Choose Hosting: Configure files.
      • public directory: "build/web"
      • single page app: "yes"
      • Github: "no"
    • flutter build web
    • firebase deploy

main.dart:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutterfire_ui/auth.dart';

import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: AuthGate(),
    );
  }
}

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const SignInScreen(
            providerConfigs: [
              EmailProviderConfiguration(),
            ],
          );
        }
        return const HomeScreen();
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => ProfileScreen(
                    appBar: AppBar(
                      title: const Text('User Profile'),
                    ),
                    actions: [
                      SignedOutAction((context) {
                        Navigator.of(context).pop();
                      })
                    ],
                    children: const [
                      Text('You can customize the profile screen here'),
                    ],
                  ),
                ),
              );
            },
          )
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            const Icon(Icons.celebration),
            Text(
              'Welcome!',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

Waiting for email verification

  1. Add async:

    import 'dart:async';
  2. Add actions-parameter to SignInScreen:

    actions: [
      AuthStateChangeAction((context, state) {
        if (state is UserCreated) {
          state.credential.user?.sendEmailVerification();
        }
      }),
    ],
  3. Create stream:

    Stream<User?> getEmailVerifiedUser() async* {
      bool finished = false;
      while (!finished) {
        final user = FirebaseAuth.instance.currentUser;
        await user?.reload();
        if (user == null) {
          finished = true;
          yield null;
        } else if (user.emailVerified) {
          finished = true;
          yield user;
        } else {
          await Future.delayed(const Duration(seconds: 5));
        }
      }
    }
  4. Use Stream to decide wether to return HomeScreen or WaitingForEmailVerificationScreen:

    return StreamBuilder<User?>(
      stream: getEmailVerifiedUser(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return const HomeScreen();
        }
        return const WaitingForEmailVerificationScreen();
      },
    );
    class WaitingForEmailVerificationScreen extends StatelessWidget {
      const WaitingForEmailVerificationScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Column(
          children: [
            Text('Waiting for email verification'),
            SignOutButton(),
          ],
        );
      }
    }

Use Firestore to grant access only to selected users

  1. Open Firebase Console.

  2. Build/Firestore Database: Click "Create database" image button.

    1. Start in test mode.
    2. Choose cloud location.
  3. Terminal: flutter pub add cloud_firestore

  4. Add cloud_firestore.dart:

    import 'package:cloud_firestore/cloud_firestore.dart';
  5. Change actions for SignInScreen by creating a Firestore collection profile and adding documents with fields email, uid and permission:

    actions: [
      AuthStateChangeAction((context, state) {
        if (state is UserCreated) {
          final user = state.credential.user;
          if (user == null) return;
          FirebaseFirestore.instance.collection('profile').add({
            'uid': user.uid,
            'email': user.email,
            'permission': false,
          });
          user.sendEmailVerification();
        }
      }),
    ],
  6. Instead of returning HomeScreen after email verification, listen to profile collection:

    final user = snapshot.data!;
    return StreamBuilder(
      stream: FirebaseFirestore.instance
          .collection('profile')
          .snapshots(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          final allProfiles = snapshot.data!.docs;
          final profileOfCurrentUser = allProfiles.firstWhere(
              (profile) => profile.data()['uid'] == user.uid);
          if (profileOfCurrentUser.data()['permission'] == true) {
            return const HomeScreen();
          }
        }
        return const WaitingForPermissionScreen();
      },
    );
    class WaitingForPermissionScreen extends StatelessWidget {
      const WaitingForPermissionScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Column(
          children: [
            Text('Waiting for permission'),
            SignOutButton(),
          ],
        );
      }
    }
  7. Go to Rules tab of Cloud Firestore where you find something like this:

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /{document=**} {
          allow read, write: if
              request.time < timestamp.date(2023, 6, 23);
        }
      }
    }
    

    Change it to:

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /profile/{entry} {
          allow read: if request.auth.uid != null;
          allow write: if request.auth.uid != null
              && request.resource.data.uid == request.auth.uid
              && request.resource.data.permission == true
        }
      }
    }
    

    Security Rules

    If you want to see all users email verification status:

    firebase auth:export /dev/stdout --format json | grep "email"
    

Firebase Storage

firebase.google.com/storage

flutter pub add firebase_storage
import 'package:firebase_storage/firebase_storage.dart';

When you want to download a file, such as an image or video, from your storage bucket using the web platform, the process is initially terminated by an exception . This is due to CORS. To solve the problem, install gsutils.

After initialization (./google-cloud-sdk/bin/gcloud init) create the file cors.json:

[
    {
        "origin": [
            "*"
        ],
        "method": [
            "GET"
        ],
        "maxAgeSeconds": 3600
    }
]
gsutil cors set cors.json gs://PROJECT_NAME.appspot.com
import 'dart:convert';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import 'package:flutter/foundation.dart';


final storageRef = FirebaseStorage.instance.ref();
final pictureRef = storageRef.child('images/image.jpg');
Uint8List? data = await pictureRef.getData();

if (data != null && kIsWeb) {
  final String base64data = base64Encode(data);
  // 'data:image/jpeg;base64,$base64data'
  final anchor = AnchorElement(href: 'data:;base64,$base64data');
  anchor.download = 'downloaded_image.jpg';
  anchor.click();
  anchor.remove();
}
⚠️ **GitHub.com Fallback** ⚠️