Skip to content

Commit

Permalink
feat: add firebase auth, and anonymous signin
Browse files Browse the repository at this point in the history
  • Loading branch information
thecodexhub committed Aug 4, 2024
1 parent ebedd0f commit 84920a8
Show file tree
Hide file tree
Showing 18 changed files with 509 additions and 11 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ _\*Sudoku works on iOS, Android, and Web._

---

## Setup 🧩

The project is designed to be integrated with 3 different Firebase projects (dependeing upon flavor).

Check warning on line 40 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Misspelled word (dependeing) Suggestions: (depending*)

> The default app instance can be initialized here simply by passing no "name" as an argument in both Dart & manual initialization flows. If you have a google-services.json file in your android project or a GoogleService-Info.plist file in your iOS+ project, it will automatically create a default (named "[DEFAULT]") app instance on the native platform. However, you will still need to call this method before using any FlutterFire plugins.
Hence, every time you try to run the app in android or ios, you have to configure the `google-services.json` and/or `GoogleService-Info.plist`. To do that, choose the correct firebase project while running the below command(s):

```sh
# Development flavor
flutterfire config --out=lib/firebase_options_development.dart --ios-bundle-id=dev.thecodexhub.sudoku.dev --android-app-id=dev.thecodexhub.sudoku.dev

Check warning on line 48 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (flutterfire)

Check warning on line 48 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (thecodexhub)

Check warning on line 48 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (thecodexhub)

# Staging flavor
flutterfire config --out=lib/firebase_options_staging.dart --ios-bundle-id=dev.thecodexhub.sudoku.stg --android-app-id=dev.thecodexhub.sudoku.stg

Check warning on line 51 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (flutterfire)

Check warning on line 51 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (thecodexhub)

Check warning on line 51 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (thecodexhub)

# Production flavor
flutterfire config --out=lib/firebase_options_production.dart --ios-bundle-id=dev.thecodexhub.sudoku --android-app-id=dev.thecodexhub.sudoku

Check warning on line 54 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (flutterfire)

Check warning on line 54 in README.md

View workflow job for this annotation

GitHub Actions / spell-check / build

Unknown word (thecodexhub)
```

## Running Tests 🧪

To run all unit and widget tests use the following command:
Expand Down
42 changes: 42 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,34 +1,65 @@
PODS:
- Firebase/Auth (10.29.0):
- Firebase/CoreOnly
- FirebaseAuth (~> 10.29.0)
- Firebase/CoreOnly (10.29.0):
- FirebaseCore (= 10.29.0)
- firebase_auth (5.1.3):
- Firebase/Auth (= 10.29.0)
- firebase_core
- Flutter
- firebase_core (3.3.0):
- Firebase/CoreOnly (= 10.29.0)
- Flutter
- FirebaseAppCheckInterop (10.29.0)
- FirebaseAuth (10.29.0):
- FirebaseAppCheckInterop (~> 10.17)
- FirebaseCore (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
- GoogleUtilities/Environment (~> 7.8)
- GTMSessionFetcher/Core (< 4.0, >= 2.1)
- RecaptchaInterop (~> 100.0)
- FirebaseCore (10.29.0):
- FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.12)
- GoogleUtilities/Logger (~> 7.12)
- FirebaseCoreInternal (10.29.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- Flutter (1.0.0)
- GoogleUtilities/AppDelegateSwizzler (7.13.3):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (7.13.3):
- GoogleUtilities/Privacy
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Network (7.13.3):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.13.3)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (7.13.3)
- GoogleUtilities/Reachability (7.13.3):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GTMSessionFetcher/Core (3.5.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.0)
- RecaptchaInterop (100.0.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS

DEPENDENCIES:
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
Expand All @@ -37,12 +68,18 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- Firebase
- FirebaseAppCheckInterop
- FirebaseAuth
- FirebaseCore
- FirebaseCoreInternal
- GoogleUtilities
- GTMSessionFetcher
- PromisesObjC
- RecaptchaInterop

EXTERNAL SOURCES:
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
Flutter:
Expand All @@ -54,13 +91,18 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
Firebase: cec914dab6fd7b1bd8ab56ea07ce4e03dd251c2d
firebase_auth: 6a09851aca5fcbb1f3745bed7ec88f221c52c3e9
firebase_core: 57aeb91680e5d5e6df6b888064be7c785f146efb
FirebaseAppCheckInterop: 6a1757cfd4067d8e00fccd14fcc1b8fd78cfac07
FirebaseAuth: e2ebfaf9fb4638a1c9a3b0efd17d1b90943987cd
FirebaseCore: 30e9c1cbe3d38f5f5e75f48bfcea87d7c358ec16
FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78

PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5
Expand Down
8 changes: 7 additions & 1 deletion lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ class App extends StatelessWidget {
const App({
required SudokuAPI apiClient,
required PuzzleRepository puzzleRepository,
required AuthenticationRepository authenticationRepository,
super.key,
}) : _apiClient = apiClient,
_puzzleRepository = puzzleRepository;
_puzzleRepository = puzzleRepository,
_authenticationRepository = authenticationRepository;

final SudokuAPI _apiClient;
final PuzzleRepository _puzzleRepository;
final AuthenticationRepository _authenticationRepository;

@override
Widget build(BuildContext context) {
Expand All @@ -28,6 +31,9 @@ class App extends StatelessWidget {
RepositoryProvider<PuzzleRepository>.value(
value: _puzzleRepository,
),
RepositoryProvider<AuthenticationRepository>.value(
value: _authenticationRepository,
),
],
child: const AppView(),
);
Expand Down
10 changes: 8 additions & 2 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import 'dart:async';
import 'dart:developer';

import 'package:bloc/bloc.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:sudoku/app_bloc_observer.dart';

/// The type definition for the builder widget.
typedef BootstrapBuilder = FutureOr<Widget> Function(
FirebaseAuth firebaseAuth,
);

/// Bootstrap is responsible for any common setup and calls
/// [runApp] with the widget returned by [builder] in an error zone.
Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
Future<void> bootstrap(BootstrapBuilder builder) async {
// Add Open Font License (OFL) for Inter google font
LicenseRegistry.addLicense(() async* {
final license = await rootBundle.loadString('licenses/OFL.txt');
Expand All @@ -31,7 +37,7 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
await runZonedGuarded(
() async {
Bloc.observer = const AppBlocObserver();
runApp(await builder());
runApp(await builder(FirebaseAuth.instance));
},
(error, stackTrace) => log(error.toString(), stackTrace: stackTrace),
);
Expand Down
18 changes: 16 additions & 2 deletions lib/main_development.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,32 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();

await Firebase.initializeApp(
name: 'sudoku-gemini-dev',
options: DefaultFirebaseOptions.currentPlatform,
);

unawaited(
bootstrap(() async {
bootstrap((firebaseAuth) async {
final apiClient = SudokuDioClient(baseUrl: Env.apiBaseUrl);
final cacheClient = CacheClient();

final storageClient = LocalStorageClient(
plugin: await SharedPreferences.getInstance(),
);

final authenticationRepository = AuthenticationRepository(
cache: cacheClient,
firebaseAuth: firebaseAuth,
);

// Check if the user is already authenticated.
var user = await authenticationRepository.user.first;

// If the user is not already authenticated, authenticate anonymously.
if (user.isUnauthenticated) {
await authenticationRepository.signInAnonymously();
user = await authenticationRepository.user.first;
}

final puzzleRepository = PuzzleRepository(
cacheClient: cacheClient,
storageClient: storageClient,
Expand All @@ -37,6 +50,7 @@ void main() async {
return App(
apiClient: apiClient,
puzzleRepository: puzzleRepository,
authenticationRepository: authenticationRepository,
);
}),
);
Expand Down
18 changes: 16 additions & 2 deletions lib/main_production.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,32 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();

await Firebase.initializeApp(
name: 'sudoku-gemini',
options: DefaultFirebaseOptions.currentPlatform,
);

unawaited(
bootstrap(() async {
bootstrap((firebaseAuth) async {
final apiClient = SudokuDioClient(baseUrl: Env.apiBaseUrl);
final cacheClient = CacheClient();

final storageClient = LocalStorageClient(
plugin: await SharedPreferences.getInstance(),
);

final authenticationRepository = AuthenticationRepository(
cache: cacheClient,
firebaseAuth: firebaseAuth,
);

// Check if the user is already authenticated.
var user = await authenticationRepository.user.first;

// If the user is not already authenticated, authenticate anonymously.
if (user.isUnauthenticated) {
await authenticationRepository.signInAnonymously();
user = await authenticationRepository.user.first;
}

final puzzleRepository = PuzzleRepository(
cacheClient: cacheClient,
storageClient: storageClient,
Expand All @@ -37,6 +50,7 @@ void main() async {
return App(
apiClient: apiClient,
puzzleRepository: puzzleRepository,
authenticationRepository: authenticationRepository,
);
}),
);
Expand Down
18 changes: 16 additions & 2 deletions lib/main_staging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,32 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();

await Firebase.initializeApp(
name: 'sudoku-gemini-stg',
options: DefaultFirebaseOptions.currentPlatform,
);

unawaited(
bootstrap(() async {
bootstrap((firebaseAuth) async {
final apiClient = SudokuDioClient(baseUrl: Env.apiBaseUrl);
final cacheClient = CacheClient();

final storageClient = LocalStorageClient(
plugin: await SharedPreferences.getInstance(),
);

final authenticationRepository = AuthenticationRepository(
cache: cacheClient,
firebaseAuth: firebaseAuth,
);

// Check if the user is already authenticated.
var user = await authenticationRepository.user.first;

// If the user is not already authenticated, authenticate anonymously.
if (user.isUnauthenticated) {
await authenticationRepository.signInAnonymously();
user = await authenticationRepository.user.first;
}

final puzzleRepository = PuzzleRepository(
cacheClient: cacheClient,
storageClient: storageClient,
Expand All @@ -37,6 +50,7 @@ void main() async {
return App(
apiClient: apiClient,
puzzleRepository: puzzleRepository,
authenticationRepository: authenticationRepository,
);
}),
);
Expand Down
1 change: 1 addition & 0 deletions lib/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export 'json_map.dart';
export 'position.dart';
export 'sudoku.dart';
export 'ticker.dart';
export 'user.dart';
30 changes: 30 additions & 0 deletions lib/models/user.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:equatable/equatable.dart';

/// {@template user}
/// User model.
///
/// [User.unauthenticated] represents an unauthenticated user.
/// {@endtemplate}
class User extends Equatable {
/// {@macro user}
const User({
required this.id,
});

/// The current user's id.
final String id;

/// Represents an unauthenticated user.
static const unauthenticated = User(id: '');

/// Convenience getter to determine whether the current
/// user is unauthenticated.
bool get isUnauthenticated => this == User.unauthenticated;

/// Convenience getter to determine whether the current
/// user is authenticated.
bool get isAuthenticated => this != User.unauthenticated;

@override
List<Object?> get props => [id];
}
Loading

0 comments on commit 84920a8

Please sign in to comment.