diff --git a/examples/firebase_login/.gitignore b/examples/firebase_login/.gitignore
new file mode 100644
index 0000000000..7a7a6318a5
--- /dev/null
+++ b/examples/firebase_login/.gitignore
@@ -0,0 +1,49 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+
+# Firebase configurations
+firebase.json
+firebase_options.dart
diff --git a/examples/firebase_login/.metadata b/examples/firebase_login/.metadata
new file mode 100644
index 0000000000..6f1fe90616
--- /dev/null
+++ b/examples/firebase_login/.metadata
@@ -0,0 +1,42 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ - platform: android
+ create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ - platform: ios
+ create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ - platform: macos
+ create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ - platform: web
+ create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ - platform: windows
+ create_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+ base_revision: 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/examples/firebase_login/README.md b/examples/firebase_login/README.md
new file mode 100644
index 0000000000..5cd44cc4d0
--- /dev/null
+++ b/examples/firebase_login/README.md
@@ -0,0 +1,28 @@
+# Firebase Login
+
+Example flutter app built with Riverpod that demonstrates authentication with Firebase.
+
+
+
+## Features
+
+- Sign up with Email and Password
+- Sign in with Email and Password
+- Sign in with Google
+- Error handling
+
+## Getting Started
+
+1. Generate the native folders first by running `flutter create --platforms=android,ios,web,windows,macos .`
+2. Create your firebase project, and enable email/password authentication and google.
+3. Use [FlutterFire CLI](https://firebase.google.com/docs/flutter/setup?platform=ios) to connect the app with the firebase project.
+4. Follow the [platform integration steps](https://pub.dev/packages/google_sign_in#platform-integration) for the `google_sign_in` package.
+5. Run the app.
+6. To run the tests, run `flutter test`.
diff --git a/examples/firebase_login/analysis_options.yaml b/examples/firebase_login/analysis_options.yaml
new file mode 100644
index 0000000000..9abd469f87
--- /dev/null
+++ b/examples/firebase_login/analysis_options.yaml
@@ -0,0 +1,7 @@
+include: ../analysis_options.yaml
+# enable riverpod_lint
+analyzer:
+ errors:
+ prefer_relative_imports: ignore
+ plugins:
+ - custom_lint
diff --git a/examples/firebase_login/lib/auth/auth_repository.dart b/examples/firebase_login/lib/auth/auth_repository.dart
new file mode 100644
index 0000000000..0a864df1fe
--- /dev/null
+++ b/examples/firebase_login/lib/auth/auth_repository.dart
@@ -0,0 +1,301 @@
+import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
+import 'package:firebase_login/auth/user.dart';
+import 'package:flutter/foundation.dart' show kIsWeb;
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:google_sign_in/google_sign_in.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+
+part 'auth_repository.g.dart';
+
+/// {@template sign_up_with_email_and_password_failure}
+/// Thrown during the sign-up process if a failure occurs.
+///
+/// * Check the [Reference API][ref link] for updated error codes.
+///
+/// [ref link]: https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/createUserWithEmailAndPassword.html
+/// {@endtemplate}
+class SignUpWithEmailAndPasswordFailure implements Exception {
+ /// {@macro sign_up_with_email_and_password_failure}
+ const SignUpWithEmailAndPasswordFailure([
+ this.message = 'An unknown error occurred.',
+ ]);
+
+ /// Creates a failure message from a [firebase_auth.FirebaseAuthException] code.
+ ///
+ /// {@macro sign_up_with_email_and_password_failure}
+ factory SignUpWithEmailAndPasswordFailure.fromCode(String code) {
+ switch (code) {
+ case 'email-already-in-use':
+ return const SignUpWithEmailAndPasswordFailure(
+ 'An account already exists for this email address.',
+ );
+ case 'invalid-email':
+ return const SignUpWithEmailAndPasswordFailure(
+ 'The email is not valid or badly formatted.',
+ );
+ case 'operation-not-allowed':
+ return const SignUpWithEmailAndPasswordFailure(
+ 'This operation is not allowed. Please contact support.',
+ );
+ case 'weak-password':
+ return const SignUpWithEmailAndPasswordFailure(
+ 'The password provided is too weak. Please use a stronger password.',
+ );
+
+ default:
+ return const SignUpWithEmailAndPasswordFailure();
+ }
+ }
+
+ /// The associated error message.
+ final String message;
+
+ @override
+ String toString() => message;
+}
+
+/// {@template sign_in_with_email_and_password_failure}
+/// Thrown during the sign in process if a failure occurs.
+///
+/// * Check the [Reference API][ref link] for updated error codes.
+///
+/// [ref link]: https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/signInWithEmailAndPassword.html
+/// {@endtemplate}
+class SignInWithEmailAndPasswordFailure implements Exception {
+ /// {@macro sign_in_with_email_and_password_failure}
+ const SignInWithEmailAndPasswordFailure([
+ this.message = 'An unknown error occurred.',
+ ]);
+
+ /// Creates a failure message from a [firebase_auth.FirebaseAuthException] code.
+ ///
+ /// {@macro sign_in_with_email_and_password_failure}
+ factory SignInWithEmailAndPasswordFailure.fromCode(String code) {
+ switch (code) {
+ case 'invalid-email':
+ return const SignInWithEmailAndPasswordFailure(
+ 'The email is not valid or badly formatted.',
+ );
+ case 'user-disabled':
+ return const SignInWithEmailAndPasswordFailure(
+ 'This user account has been disabled. Please contact support.',
+ );
+ case 'user-not-found':
+ return const SignInWithEmailAndPasswordFailure(
+ 'No user found with this email. Please check the email or sign up.',
+ );
+ case 'wrong-password':
+ return const SignInWithEmailAndPasswordFailure(
+ 'Incorrect password. Please try again.',
+ );
+ case 'invalid-credential':
+ return const SignInWithEmailAndPasswordFailure(
+ 'The credential provided is invalid. Please try again or use a different method.',
+ );
+
+ default:
+ return const SignInWithEmailAndPasswordFailure();
+ }
+ }
+
+ /// The associated error message.
+ final String message;
+
+ @override
+ String toString() => message;
+}
+
+/// {@template sign_in_with_google_failure}
+/// Thrown during the sign in process if a failure occurs.
+///
+/// * Check the [Reference API][ref link] for updated error codes.
+///
+/// [ref link]: https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/signInWithCredential.html
+/// {@endtemplate}
+class SignInWithGoogleFailure implements Exception {
+ /// {@macro sign_in_with_google_failure}
+ const SignInWithGoogleFailure([
+ this.message = 'An unknown error occurred.',
+ ]);
+
+ /// Creates a failure message from a [firebase_auth.FirebaseAuthException] code.
+ ///
+ /// {@macro sign_in_with_google_failure}
+ factory SignInWithGoogleFailure.fromCode(String code) {
+ switch (code) {
+ case 'account-exists-with-different-credential':
+ return const SignInWithGoogleFailure(
+ 'An account already exists with a different credential. Please use a different sign-in method.',
+ );
+ case 'invalid-credential':
+ return const SignInWithGoogleFailure(
+ 'The credential provided is malformed or has expired.',
+ );
+ case 'user-disabled':
+ return const SignInWithGoogleFailure(
+ 'This user account has been disabled. Please contact support.',
+ );
+ case 'user-not-found':
+ return const SignInWithGoogleFailure(
+ 'No user found with this credential. Please check your credentials or sign up.',
+ );
+ case 'wrong-password':
+ return const SignInWithGoogleFailure(
+ 'Incorrect password. Please try again.',
+ );
+ case 'invalid-verification-code':
+ return const SignInWithGoogleFailure(
+ 'The verification code is invalid. Please check and try again.',
+ );
+ case 'invalid-verification-id':
+ return const SignInWithGoogleFailure(
+ 'The verification ID is invalid. Please restart the verification process.',
+ );
+
+ default:
+ return const SignInWithGoogleFailure();
+ }
+ }
+
+ /// The associated error message.
+ final String message;
+
+ @override
+ String toString() => message;
+}
+
+/// Thrown during the logout process if a failure occurs.
+class SignOutFailure implements Exception {}
+
+/// {@template auth_repository}
+/// Repository that handles authentication with email-password
+/// and google sign in.
+/// {@endtemplate}
+class AuthRepository {
+ /// {@macro auth_repository}
+ AuthRepository({
+ firebase_auth.FirebaseAuth? firebaseAuth,
+ GoogleSignIn? googleSignIn,
+ }) : _firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth.instance,
+ _googleSignIn = googleSignIn ?? GoogleSignIn();
+
+ final firebase_auth.FirebaseAuth _firebaseAuth;
+ final GoogleSignIn _googleSignIn;
+
+ /// Stream of [User] that notifies about changes to the user's
+ /// authentication state (such as sign-in or sign-out)
+ ///
+ /// Emits empty user, if the user is unauthenticated.
+ Stream get user {
+ return _firebaseAuth.authStateChanges().map((firebaseUser) {
+ return firebaseUser != null ? firebaseUser.toUser : User.empty();
+ });
+ }
+
+ /// Creates a new user with the provided email address and password.
+ ///
+ /// Throws [SignUpWithEmailAndPasswordFailure] when an exception
+ /// occurs during the operation.
+ Future signUpWithEmailAndPassword({
+ required String email,
+ required String password,
+ }) async {
+ try {
+ await _firebaseAuth.createUserWithEmailAndPassword(
+ email: email,
+ password: password,
+ );
+ } on firebase_auth.FirebaseAuthException catch (e) {
+ throw SignUpWithEmailAndPasswordFailure.fromCode(e.code);
+ } catch (_) {
+ throw const SignUpWithEmailAndPasswordFailure();
+ }
+ }
+
+ /// Signs in a user with the provided email address and password.
+ ///
+ /// Throws [SignInWithEmailAndPasswordFailure] when an exception
+ /// occurs during the operation.
+ Future signInWithEmailAndPassword({
+ required String email,
+ required String password,
+ }) async {
+ try {
+ await _firebaseAuth.signInWithEmailAndPassword(
+ email: email,
+ password: password,
+ );
+ } on firebase_auth.FirebaseAuthException catch (e) {
+ throw SignInWithEmailAndPasswordFailure.fromCode(e.code);
+ } catch (_) {
+ throw const SignInWithEmailAndPasswordFailure();
+ }
+ }
+
+ /// Triggers the google sign in flow.
+ ///
+ /// Throws [SignInWithGoogleFailure] when an exception occurs
+ /// during the operation.
+ Future signInWithGoogle() async {
+ try {
+ late final firebase_auth.AuthCredential credential;
+ if (kIsWeb) {
+ final googleProvider = firebase_auth.GoogleAuthProvider();
+ final userCredential = await _firebaseAuth.signInWithPopup(
+ googleProvider,
+ );
+ credential = userCredential.credential!;
+ } else {
+ final googleAccount = await _googleSignIn.signIn();
+ final googleAuthentication = await googleAccount!.authentication;
+ credential = firebase_auth.GoogleAuthProvider.credential(
+ accessToken: googleAuthentication.accessToken,
+ idToken: googleAuthentication.idToken,
+ );
+ }
+
+ await _firebaseAuth.signInWithCredential(credential);
+ } on firebase_auth.FirebaseAuthException catch (e) {
+ throw SignInWithGoogleFailure.fromCode(e.code);
+ } catch (_) {
+ throw const SignInWithGoogleFailure();
+ }
+ }
+
+ /// Signs out currently signed in user.
+ ///
+ /// Throws [SignOutFailure] when an exception occurs during
+ /// the operation.
+ Future signOut() async {
+ try {
+ await Future.wait([
+ _firebaseAuth.signOut(),
+ _googleSignIn.signOut(),
+ ]);
+ } catch (_) {
+ throw SignOutFailure();
+ }
+ }
+}
+
+extension on firebase_auth.User {
+ /// Maps a [firebase_auth.User] into a [User].
+ User get toUser {
+ return User(id: uid, email: email, name: displayName, photo: photoURL);
+ }
+}
+
+@riverpod
+firebase_auth.FirebaseAuth firebaseAuth(Ref ref) =>
+ firebase_auth.FirebaseAuth.instance;
+
+@riverpod
+GoogleSignIn googleSignIn(Ref ref) => GoogleSignIn();
+
+@riverpod
+AuthRepository authRepository(Ref ref) {
+ return AuthRepository(
+ firebaseAuth: ref.read(firebaseAuthProvider),
+ googleSignIn: ref.read(googleSignInProvider),
+ );
+}
diff --git a/examples/firebase_login/lib/auth/auth_repository.g.dart b/examples/firebase_login/lib/auth/auth_repository.g.dart
new file mode 100644
index 0000000000..0a6479d3e4
--- /dev/null
+++ b/examples/firebase_login/lib/auth/auth_repository.g.dart
@@ -0,0 +1,60 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'auth_repository.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+String _$firebaseAuthHash() => r'073d2de7c8941748647f37dbb00de1c08ef8758b';
+
+/// See also [firebaseAuth].
+@ProviderFor(firebaseAuth)
+final firebaseAuthProvider =
+ AutoDisposeProvider.internal(
+ firebaseAuth,
+ name: r'firebaseAuthProvider',
+ debugGetCreateSourceHash:
+ const bool.fromEnvironment('dart.vm.product') ? null : _$firebaseAuthHash,
+ dependencies: null,
+ allTransitiveDependencies: null,
+);
+
+@Deprecated('Will be removed in 3.0. Use Ref instead')
+// ignore: unused_element
+typedef FirebaseAuthRef = AutoDisposeProviderRef;
+String _$googleSignInHash() => r'0b3da4c5bf629e3f7401a2a78c79cccd40689ce1';
+
+/// See also [googleSignIn].
+@ProviderFor(googleSignIn)
+final googleSignInProvider = AutoDisposeProvider.internal(
+ googleSignIn,
+ name: r'googleSignInProvider',
+ debugGetCreateSourceHash:
+ const bool.fromEnvironment('dart.vm.product') ? null : _$googleSignInHash,
+ dependencies: null,
+ allTransitiveDependencies: null,
+);
+
+@Deprecated('Will be removed in 3.0. Use Ref instead')
+// ignore: unused_element
+typedef GoogleSignInRef = AutoDisposeProviderRef;
+String _$authRepositoryHash() => r'e962172aeebc941ae9bb814668c8a82aa4a94225';
+
+/// See also [authRepository].
+@ProviderFor(authRepository)
+final authRepositoryProvider = AutoDisposeProvider.internal(
+ authRepository,
+ name: r'authRepositoryProvider',
+ debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
+ ? null
+ : _$authRepositoryHash,
+ dependencies: null,
+ allTransitiveDependencies: null,
+);
+
+@Deprecated('Will be removed in 3.0. Use Ref instead')
+// ignore: unused_element
+typedef AuthRepositoryRef = AutoDisposeProviderRef;
+// ignore_for_file: type=lint
+// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
diff --git a/examples/firebase_login/lib/auth/user.dart b/examples/firebase_login/lib/auth/user.dart
new file mode 100644
index 0000000000..dbcb7ade9b
--- /dev/null
+++ b/examples/firebase_login/lib/auth/user.dart
@@ -0,0 +1,36 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'user.freezed.dart';
+
+/// {@template user}
+/// A user model.
+/// {@endtemplate}
+@freezed
+class User with _$User {
+ /// {@macro user}
+ const factory User({
+ /// Unique identifier for the user.
+ required String id,
+
+ /// The associated email address of the user.
+ String? email,
+
+ /// Name of the user.
+ String? name,
+
+ /// Display photo URL of the user.
+ String? photo,
+ }) = _User;
+
+ /// {@macro user}
+ const User._();
+
+ /// Represents an unauthenticated user.
+ factory User.empty() => const User(id: '');
+
+ /// Whether the user is unauthenticated.
+ bool get isUnauthenticated => this == User.empty();
+
+ /// Whether the user is authenticated.
+ bool get isAuthenticated => !isUnauthenticated;
+}
diff --git a/examples/firebase_login/lib/auth/user.freezed.dart b/examples/firebase_login/lib/auth/user.freezed.dart
new file mode 100644
index 0000000000..c5dccd5dd5
--- /dev/null
+++ b/examples/firebase_login/lib/auth/user.freezed.dart
@@ -0,0 +1,214 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'user.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+/// @nodoc
+mixin _$User {
+ /// Unique identifier for the user.
+ String get id => throw _privateConstructorUsedError;
+
+ /// The associated email address of the user.
+ String? get email => throw _privateConstructorUsedError;
+
+ /// Name of the user.
+ String? get name => throw _privateConstructorUsedError;
+
+ /// Display photo URL of the user.
+ String? get photo => throw _privateConstructorUsedError;
+
+ /// Create a copy of User
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $UserCopyWith get copyWith => throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $UserCopyWith<$Res> {
+ factory $UserCopyWith(User value, $Res Function(User) then) =
+ _$UserCopyWithImpl<$Res, User>;
+ @useResult
+ $Res call({String id, String? email, String? name, String? photo});
+}
+
+/// @nodoc
+class _$UserCopyWithImpl<$Res, $Val extends User>
+ implements $UserCopyWith<$Res> {
+ _$UserCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of User
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? email = freezed,
+ Object? name = freezed,
+ Object? photo = freezed,
+ }) {
+ return _then(_value.copyWith(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as String,
+ email: freezed == email
+ ? _value.email
+ : email // ignore: cast_nullable_to_non_nullable
+ as String?,
+ name: freezed == name
+ ? _value.name
+ : name // ignore: cast_nullable_to_non_nullable
+ as String?,
+ photo: freezed == photo
+ ? _value.photo
+ : photo // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> {
+ factory _$$UserImplCopyWith(
+ _$UserImpl value, $Res Function(_$UserImpl) then) =
+ __$$UserImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({String id, String? email, String? name, String? photo});
+}
+
+/// @nodoc
+class __$$UserImplCopyWithImpl<$Res>
+ extends _$UserCopyWithImpl<$Res, _$UserImpl>
+ implements _$$UserImplCopyWith<$Res> {
+ __$$UserImplCopyWithImpl(_$UserImpl _value, $Res Function(_$UserImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of User
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? email = freezed,
+ Object? name = freezed,
+ Object? photo = freezed,
+ }) {
+ return _then(_$UserImpl(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as String,
+ email: freezed == email
+ ? _value.email
+ : email // ignore: cast_nullable_to_non_nullable
+ as String?,
+ name: freezed == name
+ ? _value.name
+ : name // ignore: cast_nullable_to_non_nullable
+ as String?,
+ photo: freezed == photo
+ ? _value.photo
+ : photo // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$UserImpl extends _User {
+ const _$UserImpl({required this.id, this.email, this.name, this.photo})
+ : super._();
+
+ /// Unique identifier for the user.
+ @override
+ final String id;
+
+ /// The associated email address of the user.
+ @override
+ final String? email;
+
+ /// Name of the user.
+ @override
+ final String? name;
+
+ /// Display photo URL of the user.
+ @override
+ final String? photo;
+
+ @override
+ String toString() {
+ return 'User(id: $id, email: $email, name: $name, photo: $photo)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$UserImpl &&
+ (identical(other.id, id) || other.id == id) &&
+ (identical(other.email, email) || other.email == email) &&
+ (identical(other.name, name) || other.name == name) &&
+ (identical(other.photo, photo) || other.photo == photo));
+ }
+
+ @override
+ int get hashCode => Object.hash(runtimeType, id, email, name, photo);
+
+ /// Create a copy of User
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$UserImplCopyWith<_$UserImpl> get copyWith =>
+ __$$UserImplCopyWithImpl<_$UserImpl>(this, _$identity);
+}
+
+abstract class _User extends User {
+ const factory _User(
+ {required final String id,
+ final String? email,
+ final String? name,
+ final String? photo}) = _$UserImpl;
+ const _User._() : super._();
+
+ /// Unique identifier for the user.
+ @override
+ String get id;
+
+ /// The associated email address of the user.
+ @override
+ String? get email;
+
+ /// Name of the user.
+ @override
+ String? get name;
+
+ /// Display photo URL of the user.
+ @override
+ String? get photo;
+
+ /// Create a copy of User
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$UserImplCopyWith<_$UserImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/examples/firebase_login/lib/home/home.dart b/examples/firebase_login/lib/home/home.dart
new file mode 100644
index 0000000000..128a0e677a
--- /dev/null
+++ b/examples/firebase_login/lib/home/home.dart
@@ -0,0 +1,42 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../auth/auth_repository.dart';
+import '../main.dart';
+
+class HomePage extends ConsumerWidget {
+ const HomePage({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final authProvider = ref.read(authRepositoryProvider);
+ final user = ref.watch(userProvider).value;
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Home Page'),
+ actions: [
+ IconButton(
+ onPressed: authProvider.signOut,
+ icon: const Icon(Icons.logout),
+ ),
+ ],
+ ),
+ body: Padding(
+ padding: const EdgeInsets.only(bottom: 2 * kToolbarHeight),
+ child: Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 16,
+ children: [
+ Text('User ID: ${user?.id ?? ''}'),
+ Text('Name: ${user?.name ?? ''}'),
+ Text('Email: ${user?.email ?? ''}'),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/examples/firebase_login/lib/main.dart b/examples/firebase_login/lib/main.dart
new file mode 100644
index 0000000000..323e47de11
--- /dev/null
+++ b/examples/firebase_login/lib/main.dart
@@ -0,0 +1,54 @@
+import 'dart:async';
+
+import 'package:firebase_core/firebase_core.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+
+import 'auth/auth_repository.dart';
+import 'auth/user.dart';
+import 'firebase_options.dart';
+import 'home/home.dart';
+import 'sign_in/sign_in.dart';
+
+part 'main.g.dart';
+
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+
+ runApp(
+ const ProviderScope(
+ child: App(),
+ ),
+ );
+}
+
+class App extends ConsumerWidget {
+ const App({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final user = ref.watch(userProvider).value;
+
+ return MaterialApp(
+ title: 'Riverpod - Firebase Login Example',
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
+ useMaterial3: false,
+ ),
+ home: user == null
+ ? const SignInPage()
+ : user.isAuthenticated
+ ? const HomePage()
+ : const SignInPage(),
+ );
+ }
+}
+
+@riverpod
+Stream user(Ref ref) {
+ return ref.watch(authRepositoryProvider).user;
+}
diff --git a/examples/firebase_login/lib/main.g.dart b/examples/firebase_login/lib/main.g.dart
new file mode 100644
index 0000000000..3c2ae50021
--- /dev/null
+++ b/examples/firebase_login/lib/main.g.dart
@@ -0,0 +1,26 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'main.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+String _$userHash() => r'3e037008e28e00fe8b6f74b7f3c60209ef6f0624';
+
+/// See also [user].
+@ProviderFor(user)
+final userProvider = AutoDisposeStreamProvider.internal(
+ user,
+ name: r'userProvider',
+ debugGetCreateSourceHash:
+ const bool.fromEnvironment('dart.vm.product') ? null : _$userHash,
+ dependencies: null,
+ allTransitiveDependencies: null,
+);
+
+@Deprecated('Will be removed in 3.0. Use Ref instead')
+// ignore: unused_element
+typedef UserRef = AutoDisposeStreamProviderRef;
+// ignore_for_file: type=lint
+// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
diff --git a/examples/firebase_login/lib/sign_in/sign_in.dart b/examples/firebase_login/lib/sign_in/sign_in.dart
new file mode 100644
index 0000000000..44f7b2fbd0
--- /dev/null
+++ b/examples/firebase_login/lib/sign_in/sign_in.dart
@@ -0,0 +1,184 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+import '../auth/auth_repository.dart';
+import '../sign_up/sign_up.dart';
+
+part 'sign_in.freezed.dart';
+
+class SignInPage extends ConsumerWidget {
+ const SignInPage({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final state = ref.watch(signInProvider);
+ final stateNotifier = ref.watch(signInProvider.notifier);
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Sign in to your account'),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ spacing: 16,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const SignInForm(),
+ ElevatedButton(
+ key: const Key('signInWithGoogle_elevatedButton'),
+ onPressed: state.isFormSubmitting
+ ? null
+ : stateNotifier.signInWithGoogle,
+ child: state.isFormSubmitting
+ ? const CircularProgressIndicator.adaptive()
+ : const Text('Sign in with google'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (_) => const SignUpPage(),
+ ),
+ ),
+ child: const Text('Do not have an account? Register'),
+ ),
+ state.maybeMap(
+ failure: (failure) => Text(
+ failure.message,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ ),
+ orElse: () => const SizedBox.shrink(),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class SignInForm extends ConsumerStatefulWidget {
+ const SignInForm({super.key});
+ @override
+ ConsumerState createState() => _SignInFormState();
+}
+
+class _SignInFormState extends ConsumerState {
+ final _emailController = TextEditingController();
+ final _passwordController = TextEditingController();
+
+ @override
+ void dispose() {
+ _emailController.dispose();
+ _passwordController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final state = ref.watch(signInProvider);
+ final stateNotifier = ref.watch(signInProvider.notifier);
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ spacing: 16,
+ children: [
+ TextFormField(
+ key: const Key('email_textFormField'),
+ controller: _emailController,
+ decoration: const InputDecoration(
+ labelText: 'Email address',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ TextFormField(
+ key: const Key('password_textFormField'),
+ controller: _passwordController,
+ obscureText: true,
+ decoration: const InputDecoration(
+ labelText: 'Password',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ ElevatedButton(
+ key: const Key('signInWithEmailAndPassword_elevatedButton'),
+ onPressed: state.isFormSubmitting
+ ? null
+ : () => stateNotifier.signInWithEmailAndPassword(
+ email: _emailController.text,
+ password: _passwordController.text,
+ ),
+ child: state.isFormSubmitting
+ ? const CircularProgressIndicator.adaptive()
+ : const Text('Sign in to your account'),
+ ),
+ ],
+ );
+ }
+}
+
+final signInProvider = StateNotifierProvider(
+ (ref) => SignInStateNotifier(
+ authRepository: ref.read(authRepositoryProvider),
+ ),
+);
+
+@freezed
+sealed class SignInState with _$SignInState {
+ const factory SignInState.initial() = _Initial;
+ const factory SignInState.loading() = _Loading;
+ const factory SignInState.success() = _Success;
+ const factory SignInState.failure(String message) = _Failure;
+}
+
+class SignInStateNotifier extends StateNotifier {
+ SignInStateNotifier({
+ required AuthRepository authRepository,
+ }) : _authRepository = authRepository,
+ super(const SignInState.initial());
+
+ final AuthRepository _authRepository;
+
+ Future signInWithEmailAndPassword({
+ required String email,
+ required String password,
+ }) async {
+ state = const SignInState.loading();
+ try {
+ await _authRepository.signInWithEmailAndPassword(
+ email: email,
+ password: password,
+ );
+ state = const SignInState.success();
+ } on SignInWithEmailAndPasswordFailure catch (e) {
+ state = SignInState.failure(e.message);
+ } catch (_) {
+ state = const SignInState.failure('An unexpected error occured!');
+ }
+ }
+
+ Future signInWithGoogle() async {
+ state = const SignInState.loading();
+ try {
+ await _authRepository.signInWithGoogle();
+ state = const SignInState.success();
+ } on SignInWithGoogleFailure catch (e) {
+ state = SignInState.failure(e.message);
+ } catch (_) {
+ state = const SignInState.failure('An unexpected error occured!');
+ }
+ }
+}
+
+extension SignInStateExtension on SignInState {
+ bool get isFormSubmitting {
+ return switch (this) {
+ SignInState.loading => true,
+ _ => false,
+ };
+ }
+}
diff --git a/examples/firebase_login/lib/sign_in/sign_in.freezed.dart b/examples/firebase_login/lib/sign_in/sign_in.freezed.dart
new file mode 100644
index 0000000000..401e1f94d6
--- /dev/null
+++ b/examples/firebase_login/lib/sign_in/sign_in.freezed.dart
@@ -0,0 +1,593 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'sign_in.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+/// @nodoc
+mixin _$SignInState {
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $SignInStateCopyWith<$Res> {
+ factory $SignInStateCopyWith(
+ SignInState value, $Res Function(SignInState) then) =
+ _$SignInStateCopyWithImpl<$Res, SignInState>;
+}
+
+/// @nodoc
+class _$SignInStateCopyWithImpl<$Res, $Val extends SignInState>
+ implements $SignInStateCopyWith<$Res> {
+ _$SignInStateCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of SignInState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+abstract class _$$InitialImplCopyWith<$Res> {
+ factory _$$InitialImplCopyWith(
+ _$InitialImpl value, $Res Function(_$InitialImpl) then) =
+ __$$InitialImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$InitialImplCopyWithImpl<$Res>
+ extends _$SignInStateCopyWithImpl<$Res, _$InitialImpl>
+ implements _$$InitialImplCopyWith<$Res> {
+ __$$InitialImplCopyWithImpl(
+ _$InitialImpl _value, $Res Function(_$InitialImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignInState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$InitialImpl implements _Initial {
+ const _$InitialImpl();
+
+ @override
+ String toString() {
+ return 'SignInState.initial()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$InitialImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return initial();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return initial?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (initial != null) {
+ return initial();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return initial(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return initial?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (initial != null) {
+ return initial(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Initial implements SignInState {
+ const factory _Initial() = _$InitialImpl;
+}
+
+/// @nodoc
+abstract class _$$LoadingImplCopyWith<$Res> {
+ factory _$$LoadingImplCopyWith(
+ _$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
+ __$$LoadingImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$LoadingImplCopyWithImpl<$Res>
+ extends _$SignInStateCopyWithImpl<$Res, _$LoadingImpl>
+ implements _$$LoadingImplCopyWith<$Res> {
+ __$$LoadingImplCopyWithImpl(
+ _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignInState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$LoadingImpl implements _Loading {
+ const _$LoadingImpl();
+
+ @override
+ String toString() {
+ return 'SignInState.loading()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$LoadingImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return loading();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return loading?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (loading != null) {
+ return loading();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return loading(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return loading?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (loading != null) {
+ return loading(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Loading implements SignInState {
+ const factory _Loading() = _$LoadingImpl;
+}
+
+/// @nodoc
+abstract class _$$SuccessImplCopyWith<$Res> {
+ factory _$$SuccessImplCopyWith(
+ _$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
+ __$$SuccessImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$SuccessImplCopyWithImpl<$Res>
+ extends _$SignInStateCopyWithImpl<$Res, _$SuccessImpl>
+ implements _$$SuccessImplCopyWith<$Res> {
+ __$$SuccessImplCopyWithImpl(
+ _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignInState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$SuccessImpl implements _Success {
+ const _$SuccessImpl();
+
+ @override
+ String toString() {
+ return 'SignInState.success()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$SuccessImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return success();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return success?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (success != null) {
+ return success();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return success(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return success?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (success != null) {
+ return success(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Success implements SignInState {
+ const factory _Success() = _$SuccessImpl;
+}
+
+/// @nodoc
+abstract class _$$FailureImplCopyWith<$Res> {
+ factory _$$FailureImplCopyWith(
+ _$FailureImpl value, $Res Function(_$FailureImpl) then) =
+ __$$FailureImplCopyWithImpl<$Res>;
+ @useResult
+ $Res call({String message});
+}
+
+/// @nodoc
+class __$$FailureImplCopyWithImpl<$Res>
+ extends _$SignInStateCopyWithImpl<$Res, _$FailureImpl>
+ implements _$$FailureImplCopyWith<$Res> {
+ __$$FailureImplCopyWithImpl(
+ _$FailureImpl _value, $Res Function(_$FailureImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignInState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? message = null,
+ }) {
+ return _then(_$FailureImpl(
+ null == message
+ ? _value.message
+ : message // ignore: cast_nullable_to_non_nullable
+ as String,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$FailureImpl implements _Failure {
+ const _$FailureImpl(this.message);
+
+ @override
+ final String message;
+
+ @override
+ String toString() {
+ return 'SignInState.failure(message: $message)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$FailureImpl &&
+ (identical(other.message, message) || other.message == message));
+ }
+
+ @override
+ int get hashCode => Object.hash(runtimeType, message);
+
+ /// Create a copy of SignInState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$FailureImplCopyWith<_$FailureImpl> get copyWith =>
+ __$$FailureImplCopyWithImpl<_$FailureImpl>(this, _$identity);
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return failure(message);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return failure?.call(message);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (failure != null) {
+ return failure(message);
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return failure(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return failure?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (failure != null) {
+ return failure(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Failure implements SignInState {
+ const factory _Failure(final String message) = _$FailureImpl;
+
+ String get message;
+
+ /// Create a copy of SignInState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$FailureImplCopyWith<_$FailureImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/examples/firebase_login/lib/sign_up/sign_up.dart b/examples/firebase_login/lib/sign_up/sign_up.dart
new file mode 100644
index 0000000000..160270a821
--- /dev/null
+++ b/examples/firebase_login/lib/sign_up/sign_up.dart
@@ -0,0 +1,160 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+import '../auth/auth_repository.dart';
+
+part 'sign_up.freezed.dart';
+
+class SignUpPage extends ConsumerWidget {
+ const SignUpPage({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final state = ref.watch(signUpProvider);
+
+ ref.listen(signUpProvider, (previous, next) {
+ next.maybeWhen(
+ success: () => Navigator.pop(context),
+ orElse: () {},
+ );
+ });
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Create a new account'),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ spacing: 16,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const SignUpForm(),
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('Already have an account? Sign in'),
+ ),
+ state.maybeMap(
+ failure: (failure) => Text(
+ failure.message,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ ),
+ orElse: () => const SizedBox.shrink(),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class SignUpForm extends ConsumerStatefulWidget {
+ const SignUpForm({super.key});
+ @override
+ ConsumerState createState() => _SignUpFormState();
+}
+
+class _SignUpFormState extends ConsumerState {
+ final _emailController = TextEditingController();
+ final _passwordController = TextEditingController();
+
+ @override
+ void dispose() {
+ _emailController.dispose();
+ _passwordController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final state = ref.watch(signUpProvider);
+ final stateNotifier = ref.watch(signUpProvider.notifier);
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ spacing: 16,
+ children: [
+ TextFormField(
+ key: const Key('email_textFormField'),
+ controller: _emailController,
+ decoration: const InputDecoration(
+ labelText: 'Email address',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ TextFormField(
+ key: const Key('password_textFormField'),
+ controller: _passwordController,
+ obscureText: true,
+ decoration: const InputDecoration(
+ labelText: 'Password',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ ElevatedButton(
+ onPressed: state.isFormSubmitting
+ ? null
+ : () => stateNotifier.signUpWithEmailAndPassword(
+ email: _emailController.text,
+ password: _passwordController.text,
+ ),
+ child: state.isFormSubmitting
+ ? const CircularProgressIndicator.adaptive()
+ : const Text('Create your account'),
+ ),
+ ],
+ );
+ }
+}
+
+final signUpProvider = StateNotifierProvider(
+ (ref) => SignUpStateNotifier(
+ authRepository: ref.read(authRepositoryProvider),
+ ),
+);
+
+@freezed
+sealed class SignUpState with _$SignUpState {
+ const factory SignUpState.initial() = _Initial;
+ const factory SignUpState.loading() = _Loading;
+ const factory SignUpState.success() = _Success;
+ const factory SignUpState.failure(String message) = _Failure;
+}
+
+class SignUpStateNotifier extends StateNotifier {
+ SignUpStateNotifier({
+ required AuthRepository authRepository,
+ }) : _authRepository = authRepository,
+ super(const SignUpState.initial());
+
+ final AuthRepository _authRepository;
+
+ Future signUpWithEmailAndPassword({
+ required String email,
+ required String password,
+ }) async {
+ state = const SignUpState.loading();
+ try {
+ await _authRepository.signUpWithEmailAndPassword(
+ email: email,
+ password: password,
+ );
+ state = const SignUpState.success();
+ } on SignUpWithEmailAndPasswordFailure catch (e) {
+ state = SignUpState.failure(e.message);
+ } catch (_) {
+ state = const SignUpState.failure('An unexpected error occured!');
+ }
+ }
+}
+
+extension SignUpStateExtension on SignUpState {
+ bool get isFormSubmitting {
+ return switch (this) {
+ SignUpState.loading => true,
+ _ => false,
+ };
+ }
+}
diff --git a/examples/firebase_login/lib/sign_up/sign_up.freezed.dart b/examples/firebase_login/lib/sign_up/sign_up.freezed.dart
new file mode 100644
index 0000000000..8c59615c89
--- /dev/null
+++ b/examples/firebase_login/lib/sign_up/sign_up.freezed.dart
@@ -0,0 +1,593 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'sign_up.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+/// @nodoc
+mixin _$SignUpState {
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $SignUpStateCopyWith<$Res> {
+ factory $SignUpStateCopyWith(
+ SignUpState value, $Res Function(SignUpState) then) =
+ _$SignUpStateCopyWithImpl<$Res, SignUpState>;
+}
+
+/// @nodoc
+class _$SignUpStateCopyWithImpl<$Res, $Val extends SignUpState>
+ implements $SignUpStateCopyWith<$Res> {
+ _$SignUpStateCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of SignUpState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+abstract class _$$InitialImplCopyWith<$Res> {
+ factory _$$InitialImplCopyWith(
+ _$InitialImpl value, $Res Function(_$InitialImpl) then) =
+ __$$InitialImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$InitialImplCopyWithImpl<$Res>
+ extends _$SignUpStateCopyWithImpl<$Res, _$InitialImpl>
+ implements _$$InitialImplCopyWith<$Res> {
+ __$$InitialImplCopyWithImpl(
+ _$InitialImpl _value, $Res Function(_$InitialImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignUpState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$InitialImpl implements _Initial {
+ const _$InitialImpl();
+
+ @override
+ String toString() {
+ return 'SignUpState.initial()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$InitialImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return initial();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return initial?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (initial != null) {
+ return initial();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return initial(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return initial?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (initial != null) {
+ return initial(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Initial implements SignUpState {
+ const factory _Initial() = _$InitialImpl;
+}
+
+/// @nodoc
+abstract class _$$LoadingImplCopyWith<$Res> {
+ factory _$$LoadingImplCopyWith(
+ _$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
+ __$$LoadingImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$LoadingImplCopyWithImpl<$Res>
+ extends _$SignUpStateCopyWithImpl<$Res, _$LoadingImpl>
+ implements _$$LoadingImplCopyWith<$Res> {
+ __$$LoadingImplCopyWithImpl(
+ _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignUpState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$LoadingImpl implements _Loading {
+ const _$LoadingImpl();
+
+ @override
+ String toString() {
+ return 'SignUpState.loading()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$LoadingImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return loading();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return loading?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (loading != null) {
+ return loading();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return loading(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return loading?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (loading != null) {
+ return loading(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Loading implements SignUpState {
+ const factory _Loading() = _$LoadingImpl;
+}
+
+/// @nodoc
+abstract class _$$SuccessImplCopyWith<$Res> {
+ factory _$$SuccessImplCopyWith(
+ _$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
+ __$$SuccessImplCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$$SuccessImplCopyWithImpl<$Res>
+ extends _$SignUpStateCopyWithImpl<$Res, _$SuccessImpl>
+ implements _$$SuccessImplCopyWith<$Res> {
+ __$$SuccessImplCopyWithImpl(
+ _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignUpState
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+
+class _$SuccessImpl implements _Success {
+ const _$SuccessImpl();
+
+ @override
+ String toString() {
+ return 'SignUpState.success()';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType && other is _$SuccessImpl);
+ }
+
+ @override
+ int get hashCode => runtimeType.hashCode;
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return success();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return success?.call();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (success != null) {
+ return success();
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return success(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return success?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (success != null) {
+ return success(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Success implements SignUpState {
+ const factory _Success() = _$SuccessImpl;
+}
+
+/// @nodoc
+abstract class _$$FailureImplCopyWith<$Res> {
+ factory _$$FailureImplCopyWith(
+ _$FailureImpl value, $Res Function(_$FailureImpl) then) =
+ __$$FailureImplCopyWithImpl<$Res>;
+ @useResult
+ $Res call({String message});
+}
+
+/// @nodoc
+class __$$FailureImplCopyWithImpl<$Res>
+ extends _$SignUpStateCopyWithImpl<$Res, _$FailureImpl>
+ implements _$$FailureImplCopyWith<$Res> {
+ __$$FailureImplCopyWithImpl(
+ _$FailureImpl _value, $Res Function(_$FailureImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of SignUpState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? message = null,
+ }) {
+ return _then(_$FailureImpl(
+ null == message
+ ? _value.message
+ : message // ignore: cast_nullable_to_non_nullable
+ as String,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$FailureImpl implements _Failure {
+ const _$FailureImpl(this.message);
+
+ @override
+ final String message;
+
+ @override
+ String toString() {
+ return 'SignUpState.failure(message: $message)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$FailureImpl &&
+ (identical(other.message, message) || other.message == message));
+ }
+
+ @override
+ int get hashCode => Object.hash(runtimeType, message);
+
+ /// Create a copy of SignUpState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$FailureImplCopyWith<_$FailureImpl> get copyWith =>
+ __$$FailureImplCopyWithImpl<_$FailureImpl>(this, _$identity);
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function() initial,
+ required TResult Function() loading,
+ required TResult Function() success,
+ required TResult Function(String message) failure,
+ }) {
+ return failure(message);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function()? initial,
+ TResult? Function()? loading,
+ TResult? Function()? success,
+ TResult? Function(String message)? failure,
+ }) {
+ return failure?.call(message);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function()? initial,
+ TResult Function()? loading,
+ TResult Function()? success,
+ TResult Function(String message)? failure,
+ required TResult orElse(),
+ }) {
+ if (failure != null) {
+ return failure(message);
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(_Initial value) initial,
+ required TResult Function(_Loading value) loading,
+ required TResult Function(_Success value) success,
+ required TResult Function(_Failure value) failure,
+ }) {
+ return failure(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(_Initial value)? initial,
+ TResult? Function(_Loading value)? loading,
+ TResult? Function(_Success value)? success,
+ TResult? Function(_Failure value)? failure,
+ }) {
+ return failure?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(_Initial value)? initial,
+ TResult Function(_Loading value)? loading,
+ TResult Function(_Success value)? success,
+ TResult Function(_Failure value)? failure,
+ required TResult orElse(),
+ }) {
+ if (failure != null) {
+ return failure(this);
+ }
+ return orElse();
+ }
+}
+
+abstract class _Failure implements SignUpState {
+ const factory _Failure(final String message) = _$FailureImpl;
+
+ String get message;
+
+ /// Create a copy of SignUpState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$FailureImplCopyWith<_$FailureImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/examples/firebase_login/pubspec.yaml b/examples/firebase_login/pubspec.yaml
new file mode 100644
index 0000000000..1e75768d1d
--- /dev/null
+++ b/examples/firebase_login/pubspec.yaml
@@ -0,0 +1,33 @@
+name: firebase_login
+description: Example flutter app built with Riverpod that demonstrates
+ authentication with Firebase.
+version: 1.0.0+1
+publish_to: none
+
+environment:
+ sdk: ">=3.0.0 <4.0.0"
+
+dependencies:
+ cupertino_icons: ^1.0.8
+ firebase_auth: ^5.4.0
+ firebase_core: ^3.10.0
+ flutter:
+ sdk: flutter
+ flutter_riverpod: ^2.6.1
+ freezed_annotation: ^2.4.4
+ google_sign_in: ^6.2.2
+ riverpod_annotation: ^2.6.1
+
+dev_dependencies:
+ build_runner: ^2.4.14
+ custom_lint: ^0.7.1
+ flutter_lints: ^5.0.0
+ flutter_test:
+ sdk: flutter
+ freezed: ^2.5.8
+ mocktail: ^1.0.4
+ riverpod_generator: ^2.6.4
+ riverpod_lint: ^2.6.4
+
+flutter:
+ uses-material-design: true
diff --git a/examples/firebase_login/pubspec_overrides.yaml b/examples/firebase_login/pubspec_overrides.yaml
new file mode 100644
index 0000000000..74695a9c8d
--- /dev/null
+++ b/examples/firebase_login/pubspec_overrides.yaml
@@ -0,0 +1,14 @@
+# melos_managed_dependency_overrides: flutter_riverpod,riverpod,riverpod_lint,riverpod_analyzer_utils
+dependency_overrides:
+ flutter_riverpod:
+ path: ../../packages/flutter_riverpod
+ riverpod:
+ path: ../../packages/riverpod
+ riverpod_analyzer_utils:
+ path: ../../packages/riverpod_analyzer_utils
+ riverpod_lint:
+ path: ../../packages/riverpod_lint
+ riverpod_annotation:
+ path: ../../packages/riverpod_annotation
+ riverpod_generator:
+ path: ../../packages/riverpod_generator
diff --git a/examples/firebase_login/samples/demo.gif b/examples/firebase_login/samples/demo.gif
new file mode 100644
index 0000000000..1a097ab3a9
Binary files /dev/null and b/examples/firebase_login/samples/demo.gif differ
diff --git a/examples/firebase_login/samples/error.png b/examples/firebase_login/samples/error.png
new file mode 100644
index 0000000000..447966fafd
Binary files /dev/null and b/examples/firebase_login/samples/error.png differ
diff --git a/examples/firebase_login/samples/home.png b/examples/firebase_login/samples/home.png
new file mode 100644
index 0000000000..d0a6924382
Binary files /dev/null and b/examples/firebase_login/samples/home.png differ
diff --git a/examples/firebase_login/samples/signup.png b/examples/firebase_login/samples/signup.png
new file mode 100644
index 0000000000..ee9258852f
Binary files /dev/null and b/examples/firebase_login/samples/signup.png differ
diff --git a/examples/firebase_login/test/app_test.dart b/examples/firebase_login/test/app_test.dart
new file mode 100644
index 0000000000..fe4ba03e3a
--- /dev/null
+++ b/examples/firebase_login/test/app_test.dart
@@ -0,0 +1,54 @@
+// ignore_for_file: prefer_const_constructors
+
+import 'package:firebase_login/auth/auth_repository.dart';
+import 'package:firebase_login/auth/user.dart';
+import 'package:firebase_login/home/home.dart';
+import 'package:firebase_login/main.dart';
+import 'package:firebase_login/sign_in/sign_in.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mocktail/mocktail.dart';
+
+import 'pump_app.dart';
+
+void main() {
+ late AuthRepository authRepository;
+
+ setUp(() {
+ authRepository = MockAuthRepository();
+ when(() => authRepository.user).thenAnswer((_) => Stream.empty());
+ });
+
+ group('App', () {
+ testWidgets(
+ 'renders SignInPage when the user stream is empty',
+ (tester) async {
+ await tester.pumpApp(App(), authRepository: authRepository);
+ expect(find.byType(SignInPage), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ 'renders SignInPage when the user stream emits User.empty',
+ (tester) async {
+ when(() => authRepository.user).thenAnswer(
+ (_) => Stream.value(User.empty()),
+ );
+ await tester.pumpApp(App(), authRepository: authRepository);
+ await tester.pumpAndSettle();
+ expect(find.byType(SignInPage), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ 'renders HomePage when the user stream emits authenticated user',
+ (tester) async {
+ when(() => authRepository.user).thenAnswer(
+ (_) => Stream.value(User(id: 'abc')),
+ );
+ await tester.pumpApp(App(), authRepository: authRepository);
+ await tester.pumpAndSettle();
+ expect(find.byType(HomePage), findsOneWidget);
+ },
+ );
+ });
+}
diff --git a/examples/firebase_login/test/home_test.dart b/examples/firebase_login/test/home_test.dart
new file mode 100644
index 0000000000..c3e82a4ece
--- /dev/null
+++ b/examples/firebase_login/test/home_test.dart
@@ -0,0 +1,29 @@
+// ignore_for_file: prefer_const_constructors
+
+import 'package:firebase_login/auth/auth_repository.dart';
+import 'package:firebase_login/home/home.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mocktail/mocktail.dart';
+
+import 'pump_app.dart';
+
+void main() {
+ late AuthRepository authRepository;
+
+ setUp(() {
+ authRepository = MockAuthRepository();
+ when(() => authRepository.signOut()).thenAnswer((_) => Future.value());
+ });
+
+ group('Home', () {
+ testWidgets('triggers signOut on icon click', (tester) async {
+ await tester.pumpApp(
+ MaterialApp(home: HomePage()),
+ authRepository: authRepository,
+ );
+ await tester.tap(find.byType(IconButton));
+ verify(() => authRepository.signOut()).called(1);
+ });
+ });
+}
diff --git a/examples/firebase_login/test/pump_app.dart b/examples/firebase_login/test/pump_app.dart
new file mode 100644
index 0000000000..c31a7cdefc
--- /dev/null
+++ b/examples/firebase_login/test/pump_app.dart
@@ -0,0 +1,22 @@
+import 'package:firebase_login/auth/auth_repository.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mocktail/mocktail.dart';
+
+class MockAuthRepository extends Mock implements AuthRepository {}
+
+extension WidgetTesterExtension on WidgetTester {
+ Future pumpApp(Widget child, {AuthRepository? authRepository}) {
+ return pumpWidget(
+ ProviderScope(
+ overrides: [
+ authRepositoryProvider.overrideWithValue(
+ authRepository ?? MockAuthRepository(),
+ ),
+ ],
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/examples/firebase_login/test/sign_in_test.dart b/examples/firebase_login/test/sign_in_test.dart
new file mode 100644
index 0000000000..c826a13f0b
--- /dev/null
+++ b/examples/firebase_login/test/sign_in_test.dart
@@ -0,0 +1,67 @@
+// ignore_for_file: prefer_const_constructors
+
+import 'package:firebase_login/auth/auth_repository.dart';
+import 'package:firebase_login/sign_in/sign_in.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mocktail/mocktail.dart';
+
+import 'pump_app.dart';
+
+void main() {
+ late AuthRepository authRepository;
+
+ const emailKey = Key('email_textFormField');
+ const passwordKey = Key('password_textFormField');
+ const emailSignInKey = Key('signInWithEmailAndPassword_elevatedButton');
+ const googleSignInKey = Key('signInWithGoogle_elevatedButton');
+
+ setUp(() {
+ authRepository = MockAuthRepository();
+ when(
+ () => authRepository.signInWithEmailAndPassword(
+ email: any(named: 'email'),
+ password: any(named: 'password'),
+ ),
+ ).thenAnswer((_) async {});
+ when(() => authRepository.signInWithGoogle()).thenAnswer((_) async {});
+ });
+
+ group('SignIn', () {
+ testWidgets(
+ 'triggers signInWithEmailAndPassword on submit button click',
+ (tester) async {
+ await tester.pumpApp(
+ MaterialApp(home: SignInPage()),
+ authRepository: authRepository,
+ );
+
+ await tester.enterText(find.byKey(emailKey), 'email@email.com');
+ await tester.enterText(find.byKey(passwordKey), 's3cRe7');
+
+ await tester.tap(find.byKey(emailSignInKey));
+ verify(
+ () => authRepository.signInWithEmailAndPassword(
+ email: 'email@email.com',
+ password: 's3cRe7',
+ ),
+ ).called(1);
+ },
+ );
+
+ testWidgets(
+ 'triggers signInWithGoogle on google button click',
+ (tester) async {
+ await tester.pumpApp(
+ MaterialApp(home: SignInPage()),
+ authRepository: authRepository,
+ );
+
+ await tester.tap(find.byKey(googleSignInKey));
+ verify(
+ () => authRepository.signInWithGoogle(),
+ ).called(1);
+ },
+ );
+ });
+}
diff --git a/examples/firebase_login/test/sign_up_test.dart b/examples/firebase_login/test/sign_up_test.dart
new file mode 100644
index 0000000000..213ee25f57
--- /dev/null
+++ b/examples/firebase_login/test/sign_up_test.dart
@@ -0,0 +1,49 @@
+// ignore_for_file: prefer_const_constructors
+
+import 'package:firebase_login/auth/auth_repository.dart';
+import 'package:firebase_login/sign_up/sign_up.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mocktail/mocktail.dart';
+
+import 'pump_app.dart';
+
+void main() {
+ late AuthRepository authRepository;
+
+ const emailKey = Key('email_textFormField');
+ const passwordKey = Key('password_textFormField');
+
+ setUp(() {
+ authRepository = MockAuthRepository();
+ when(
+ () => authRepository.signUpWithEmailAndPassword(
+ email: any(named: 'email'),
+ password: any(named: 'password'),
+ ),
+ ).thenAnswer((_) async {});
+ });
+
+ group('SignUp', () {
+ testWidgets(
+ 'triggers signUpWithEmailAndPassword on submit button click',
+ (tester) async {
+ await tester.pumpApp(
+ MaterialApp(home: SignUpPage()),
+ authRepository: authRepository,
+ );
+
+ await tester.enterText(find.byKey(emailKey), 'email@email.com');
+ await tester.enterText(find.byKey(passwordKey), 's3cRe7');
+
+ await tester.tap(find.byType(ElevatedButton));
+ verify(
+ () => authRepository.signUpWithEmailAndPassword(
+ email: 'email@email.com',
+ password: 's3cRe7',
+ ),
+ ).called(1);
+ },
+ );
+ });
+}