layou_auth 1.0.3 copy "layou_auth: ^1.0.3" to clipboard
layou_auth: ^1.0.3 copied to clipboard

Firebase Auth package with Google, Apple, and Email sign-in/linking support. Provides ready-to-use widgets and Riverpod providers.

πŸ” LayouAuth #

Stop blocking users. Start building trust.

A Flutter authentication module that lets users experience your app first, authenticate later.

pub package likes popularity License: MIT

Why LayouAuth? β€’ Quick Start β€’ Screenshots β€’ Examples β€’ Docs


🎯 Why LayouAuth? #

This README is for everyone - designers, product managers, and developers. We talk UX strategy first, implementation second.

The Problem: 40% Drop-off at Login #

For years, apps forced users to "Sign up" before even seeing the first screen.

The truth: people want to TRY, not fill forms.

Every authentication step between download and your app's "aha moment" is a leak in your funnel.

The LayouAuth Philosophy #

This isn't just a package. It's a UX mindset + technical implementation.

For designers & product people: LayouAuth removes the biggest friction point in mobile apps. It's built on the belief that users should experience your app's value before you ask for their trust.

For developers: Stop writing Firebase Auth boilerplate. Get a production-ready auth system with beautiful UI in minutes. No boilerplate. No complexity. Just authentication that works.

For everyone: If your app requires a sign-up wall before showing any value, ask yourself: "Am I protecting my users, or protecting myself from admitting my onboarding isn't compelling enough?"

Anonymous-first auth forces you to build something worth coming back to.

🎭 Real-world UX Strategies #

Here's what teams are testing in production:

The approach:

  • Users start anonymous by default
  • Multiple CTAs throughout the app to link their account
  • Prompt linking after meaningful engagement (post-paywall, after first save, etc.)

The results:

  • Zero friction at launch β†’ higher conversion
  • Users experience the "aha moment" before committing
  • Natural filter: if users don't want to link, the app didn't deliver value

Pro tip: For paid users, require account linking post-purchase. Anonymous + payment works for the initial conversion, but linking ensures they can restore purchases and access across devices.

πŸ“§ Email-only

The reality: It can feel restrictive, but if your app truly delivers value, retention stays strong. Some older apps still use this successfully.

🍎 Apple/Google only

The experiment: Radical but clean. Removes choice paralysis, but be aware:

  • Users might use "Hide My Email" with Apple Sign-In
  • You'll get private relay emails like abc123@privaterelay.appleid.com
  • It works, but plan for anonymous email addresses

The pattern that emerges: The best apps let users reach value instantly, then earn the right to ask for commitment.


✨ What You Get #

❌ Without LayouAuth #

// 500+ lines of boilerplate
class AuthService {
  Future<void> signInWithGoogle() async {
    try {
      final googleUser = await GoogleSignIn().signIn();
      if (googleUser == null) return;
      final googleAuth = await googleUser.authentication;
      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );
      await FirebaseAuth.instance.signInWithCredential(credential);
    } on FirebaseAuthException catch (e) {
      // Handle errors...
    }
  }
  // + 400 more lines for Apple, Email, linking...
}

// Then build UI from scratch
// Handle states, errors, loading...
// Wire up Riverpod...

βœ… With LayouAuth #

// Initialize once
void main() async {
  LayouAuth.initialize(
    config: LayouAuthConfig(
      providers: [
        GoogleProviderConfig(iosClientId: '...'),
        const AppleProviderConfig(),
        const EmailProviderConfig(),
      ],
    ),
  );
  runApp(const ProviderScope(child: MyApp()));
}

// Use anywhere
LayouAuthSheet.show(context, mode: LayouAuthMode.signIn);

That's it. Sign-in, linking, errors, loading states, UIβ€”all handled.

Features for Product & UX Teams #

  • 🎯 Instant engagement - Users reach your app's value in seconds, not after a form
  • πŸ“Š Built-in conversion funnel - Anonymous β†’ engaged β†’ linked = clear value signal
  • πŸ§ͺ A/B test ready - Test different linking prompts and timings
  • πŸ’‘ Progressive trust building - Earn the right to ask for commitment

Features for Developers #

  • πŸš€ Ready-to-use UI - Beautiful bottom sheets, inline sections, and individual buttons. Dark mode included.
  • πŸ”— Account Linking - Convert anonymous users to permanent accounts seamlessly
  • 🧩 Riverpod Integration - Providers for auth state, user info, and actions. No setup needed.
  • 🎨 Fully Customizable - Override strings, theme, or build your own UI with the builder API
  • πŸ›‘οΈ Type-safe Errors - No more catching FirebaseAuthException and parsing codes
  • 🌍 i18n Ready - All strings customizable. Use your own translation system

🎯 Supported Auth Methods #

Provider Sign In Link Account Status
πŸ”— Anonymous βœ… β€” Built-in
🌐 Google βœ… βœ… Fully supported
🍎 Apple βœ… βœ… iOS/macOS
πŸ“§ Email/Password βœ… βœ… Fully supported

πŸ†• Latest Enhancements #

  • πŸ—‘οΈ Account Deletion with automatic reauthentication
  • πŸ”„ Smart Credential Handling - If a provider is already linked elsewhere, offer to sign in
  • πŸ§ͺ Debug Mode - Long-press email field for test credentials
  • 🎣 Lifecycle Callbacks - Run code before/after sign-out or deletion

πŸ“Έ Screenshots #

πŸ”— Account Linking Flow #

Profile with Badge Link Options Sheet Email Form Settings Integration
User profile showing "Linked account" badge and "Link my account" button Clean bottom sheet with Google, Apple, and Email options Expandable email/password form with validation Settings page with "Link Account" entry and "Recommended" badge

🎨 What You Get Out of the Box #

  • βœ… Beautiful bottom sheets with handle bar and rounded corners
  • βœ… Smart error handling with inline error messages
  • βœ… Loading states with spinners on buttons
  • βœ… Expandable forms for email/password
  • βœ… Native platform look (Apple button black on iOS)
  • βœ… Dark mode support automatically
  • βœ… Consistent spacing and typography
  • βœ… Accessible with proper labels

⚑ Quick Start #

1️⃣ Install #

flutter pub add layou_auth

2️⃣ Initialize #

import 'package:layou_auth/layou_auth.dart';

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

  LayouAuth.initialize(
    config: LayouAuthConfig(
      providers: [
        GoogleProviderConfig(iosClientId: DefaultFirebaseOptions.ios.iosClientId),
        const AppleProviderConfig(),
        const EmailProviderConfig(),
      ],
    ),
  );

  runApp(const ProviderScope(child: MyApp()));
}

3️⃣ Use #

// Show auth bottom sheet
LayouAuthSheet.show(context, mode: LayouAuthMode.signIn);

That's it! πŸŽ‰ You have a complete auth system.

πŸ“ When to Prompt Account Linking #

// βœ… After a purchase (they paid, they see the value)
void onPurchaseComplete() {
  LayouAuthSheet.show(
    context,
    mode: LayouAuthMode.link,
    strings: LayouAuthStrings(
      linkAccountTitle: "Secure your purchase!",
      linkAccountSubtitle: "Link your account to access on all devices.",
    ),
  );
}

// βœ… After meaningful engagement (they're invested)
void onFirstContentCreated() {
  if (LayouAuth.currentUser?.isAnonymous == true) {
    LayouAuthSheet.show(context, mode: LayouAuthMode.link);
  }
}

// ❌ On app launch (they haven't seen value yet)
void initState() {
  // DON'T: Force login wall before showing any value
  // DO: Let them explore anonymously, prompt later
}

πŸ’‘ Examples #

Sign In #

LayouAuthSheet.show(
  context,
  mode: LayouAuthMode.signIn,
  onSuccess: (user, method) {
    print('Signed in with ${method.name}: ${user.email}');
  },
);
// Perfect for converting free users to permanent accounts
LayouAuthSheet.show(
  context,
  mode: LayouAuthMode.link,
  onSuccess: (user, method) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Account linked with ${method.name}!')),
    );
  },
);

Settings Page Integration #

// In your settings page
Card(
  child: ListTile(
    leading: const Icon(Icons.link),
    title: const Text('Link Account'),
    subtitle: const Text('Secure your notes by linking with Google, Apple, or Email'),
    trailing: const Chip(label: Text('Recommended')),
    onTap: () => LayouAuthSheet.show(context, mode: LayouAuthMode.link),
  ),
)

Delete Account with Confirmation #

// New! With automatic reauthentication
ElevatedButton(
  onPressed: () async {
    final confirmed = await LayouDeleteAccountSheet.show(
      context,
      title: 'Delete Account?',
      message: 'This will permanently delete your data.',
      onBeforeDelete: () async {
        // Delete user documents
        await firestore.collection('users').doc(userId).delete();
      },
    );

    if (confirmed == true) {
      // User deleted and automatically logged out
      context.go(Routes.welcome);
    }
  },
  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
  child: const Text('Delete My Account'),
);

Sign Out with Callbacks #

// Clean up before/after logout
final service = ref.read(layouAuthServiceProvider);

await service.signOut(
  onBeforeLogout: () async {
    // Cancel subscriptions, clear cache, etc.
    await subscriptionService.cancelAll();
  },
  onAfterLogout: () async {
    // Navigate to home, track analytics, etc.
    analytics.logLogout();
    context.go(Routes.home);
  },
);

🎯 Advanced Usage #

Custom Strings (i18n) #

LayouAuthSheet.show(
  context,
  mode: LayouAuthMode.link,
  strings: LayouAuthStrings(
    linkAccountTitle: t.auth.linkTitle,
    linkAccountSubtitle: t.auth.linkSubtitle,
    googleButton: t.auth.continueWithGoogle,
    appleButton: t.auth.continueWithApple,
    emailButton: t.auth.continueWithEmail,
    closeButton: t.common.close,
  ),
);

Custom Theme #

LayouAuthSheet.show(
  context,
  theme: LayouAuthTheme(
    buttonBorderRadius: 16.0,
    buttonSpacing: 16.0,
    googleButtonStyle: OutlinedButton.styleFrom(...),
    appleButtonStyle: ElevatedButton.styleFrom(...),
  ),
);

Full Custom UI (Builder) #

LayouAuthSheet.show(
  context,
  builder: (context, state, actions) {
    return YourCustomWidget(
      isLoading: state.isLoading,
      error: state.error,
      onGoogleTap: actions.linkWithGoogle,
      onAppleTap: actions.linkWithApple,
    );
  },
);

Individual Buttons #

LayouGoogleButton(
  label: 'Continue with Google',
  isLoading: _isLoading,
  onPressed: () async {
    final result = await ref.read(layouAuthActionsProvider.notifier).signInWithGoogle();
    result.when(
      success: (user) => print('Success: ${user.email}'),
      error: (error) => print('Error: ${error.message}'),
    );
  },
)

Inline Section (Settings) #

LayouAuthSection(
  title: 'Link your account',
  subtitle: 'Keep your data safe',
  showGoogle: true,
  showApple: true,
  showEmail: true,
  onSuccess: (user, method) {
    showSuccessSnackbar('Account linked!');
  },
)

πŸ§ͺ Debug Mode Features #

Auto-fill Test Credentials #

In debug mode, long-press the email field to generate test credentials:

LayouEmailForm(
  onSubmit: _handleSubmit,
  debugCredentialsMessage: 'Test credentials generated!', // Optional snackbar
)

Result:

  • Email: test_XXX@yopmail.com (random number)
  • Password: azerty123

Perfect for rapid testing without typing!


πŸ”Œ Riverpod Providers #

Access auth state and actions anywhere in your app:

// Current user
final user = ref.watch(layouCurrentUserProvider);
if (user != null) {
  print('Logged in: ${user.email}');
}

// Auth state stream
ref.listen(layouAuthStateProvider, (previous, next) {
  if (next != null) {
    print('User signed in');
  }
});

// Check auth status
final isAuthenticated = ref.watch(layouIsAuthenticatedProvider);
final isAnonymous = ref.watch(layouIsAnonymousProvider);

// Check linked providers
final hasGoogle = ref.watch(layouHasGoogleProvider);
final hasApple = ref.watch(layouHasAppleProvider);
final hasEmail = ref.watch(layouHasEmailProvider);
final linkedProviders = ref.watch(layouLinkedProvidersProvider);

// Auth actions
final actions = ref.read(layouAuthActionsProvider.notifier);
await actions.signInWithGoogle();
await actions.linkWithApple();
await actions.signOut();
await actions.deleteUser();

πŸ›‘οΈ Error Handling #

All errors are type-safe. No more parsing Firebase error codes!

final result = await actions.signInWithGoogle();

result.when(
  success: (user) => print('Welcome ${user.email}'),
  error: (e) => switch (e) {
    UserCancelledException() => null, // User cancelled, do nothing
    CredentialAlreadyInUseException() => showCredentialInUseDialog(),
    NetworkException() => showError('Check your connection'),
    UserNotFoundException() => showError('No account found'),
    WrongPasswordException() => showError('Incorrect password'),
    WeakPasswordException() => showError('Password too weak'),
    _ => showError('Something went wrong'),
  },
);

Available Exceptions #

Exception When it happens
UserCancelledException User cancelled Google/Apple sign-in
NoUserException No authenticated user found
SignInFailedException Generic sign-in failure
LinkingFailedException Generic linking failure
CredentialAlreadyInUseException Provider already linked elsewhere
EmailAlreadyInUseException Email already registered
WeakPasswordException Password too weak
InvalidEmailException Invalid email format
UserNotFoundException User not found (sign-in)
WrongPasswordException Incorrect password
UserDisabledException Account disabled
NetworkException Network error
RequiresRecentLoginException Sensitive operation needs reauth
UnknownAuthException Unknown error

πŸš€ Smart Credential Handling #

New! When linking a provider that's already used on another account, LayouAuth offers to sign in instead:

// User tries to link Google account
// But that Google account exists on another account
// β†’ LayouAuth shows: "Account Already Exists. Sign in instead?"
// β†’ User clicks "Sign In"
// β†’ Seamlessly switches to the existing account

Customize the messaging:

LayouAuthSheet.show(
  context,
  mode: LayouAuthMode.link,
  credentialInUseTitle: 'Account Exists',
  credentialInUseMessage: 'This {provider} is already registered. Sign in?',
  credentialInUseSignInButton: 'Yes, Sign In',
  credentialInUseCancelButton: 'No, Thanks',
);

🎣 Lifecycle Hooks #

Run custom code at key moments:

Global Hooks (in config) #

LayouAuth.initialize(
  config: LayouAuthConfig(
    providers: [...],
    onSignedIn: (user, method) async {
      // Create user in Firestore
      await firestore.collection('users').doc(user.uid).set({
        'email': user.email,
        'signInMethod': method.name,
        'createdAt': FieldValue.serverTimestamp(),
      });
      // Track analytics
      analytics.logSignIn(method: method.name);
    },
    onAccountLinked: (user, method) async {
      // Award bonus for linking account
      await creditsService.addBonus(5);
      analytics.logAccountLinked(method: method.name);
    },
    onSignedOut: () async {
      // Clear local cache
      await cacheService.clearAll();
    },
    onUserDeleted: (uid) async {
      // Cloud Functions handles Firestore cleanup
      // But you can do client-side cleanup here
      await localDb.clear();
    },
  ),
);

Per-operation Callbacks #

// Sign out
await service.signOut(
  onBeforeLogout: () async {
    // Cancel subscriptions
    await subscriptionService.cancelAll();
  },
  onAfterLogout: () async {
    // Navigate
    context.go(Routes.home);
  },
);

// Delete account
await service.deleteUser(
  onBeforeDelete: () async {
    // Delete user-specific data
    await deleteUserDocuments();
  },
  onAfterDelete: () async {
    // Analytics
    analytics.logAccountDeleted();
  },
);

πŸ”§ Platform Setup #

πŸ“± iOS/macOS (Apple Sign-In)
  1. Enable capability in Xcode:

    • Open ios/Runner.xcworkspace
    • Select your target β†’ Signing & Capabilities
    • Click + Capability β†’ Add "Sign in with Apple"
  2. Configure in Apple Developer Console:

  3. Add to config:

    const AppleProviderConfig()
    
🌐 Google Sign-In
  1. Add configuration files:

    • iOS: Add GoogleService-Info.plist to ios/Runner/
    • Android: Add google-services.json to android/app/
  2. Get iOS client ID:

    GoogleProviderConfig(
      iosClientId: DefaultFirebaseOptions.ios.iosClientId,
      // Or manually: 'YOUR_IOS_CLIENT_ID.apps.googleusercontent.com'
    )
    
  3. Enable in Firebase Console:

    • Authentication β†’ Sign-in method β†’ Google β†’ Enable
πŸ“§ Email/Password

No platform setup needed! Just enable in Firebase:

  1. Firebase Console β†’ Authentication β†’ Sign-in method β†’ Email/Password β†’ Enable

  2. Optional config:

    const EmailProviderConfig(
      passwordMinLength: 8, // Default: 6
    )
    

πŸ“– Documentation #

Core Concepts #

  • Sign In: Authenticate a user (creates new session)
  • Link Account: Attach a provider to existing anonymous user
  • Anonymous User: Temporary user without credentials
  • Permanent User: User with at least one auth provider linked

Common Patterns #

1. Start Anonymous, Link Later (Freemium)

// App start - create anonymous user
await LayouAuth.auth.signInAnonymously();

// User wants premium features - prompt to link
if (isPremiumFeature && isAnonymous) {
  LayouAuthSheet.show(
    context,
    mode: LayouAuthMode.link,
    onSuccess: (user, method) {
      enablePremiumFeatures();
    },
  );
}

2. Force Link Before Action

// User tries to save data
Future<void> saveNote() async {
  if (ref.read(layouIsAnonymousProvider)) {
    // Must link first
    final result = await LayouAuthSheet.show(
      context,
      mode: LayouAuthMode.link,
    );
    if (result != true) return; // User cancelled
  }

  // Now safe to save
  await notesService.save(note);
}

3. Settings Page with Recommended Badge

// Show "Recommended" badge if anonymous
final isAnonymous = ref.watch(layouIsAnonymousProvider);

ListTile(
  title: const Text('Link Account'),
  subtitle: const Text('Secure your data across devices'),
  trailing: isAnonymous
    ? const Chip(label: Text('Recommended'))
    : null,
  onTap: () => LayouAuthSheet.show(context, mode: LayouAuthMode.link),
)

🀝 Contributing #

Contributions are welcome! Please read CONTRIBUTING.md first.

Development Setup #

# Clone the repo
git clone https://github.com/yelkamel/layou_auth.git

# Install dependencies
flutter pub get

# Run tests
flutter test

# Run example app
cd example
flutter run

Reporting Issues #

Found a bug? Open an issue with:

  • Flutter version
  • Device/OS
  • Steps to reproduce
  • Expected vs actual behavior

πŸ“‹ Roadmap #

  • ❌ Phone authentication
  • ❌ Multi-factor authentication
  • ❌ Password reset UI
  • ❌ Email verification UI
  • ❌ Anonymous user migration tools
  • ❌ Biometric authentication
  • ❌ Session management UI

πŸ™ Credits #

Built with ❀️ by the Layou team.

Special thanks to:


πŸ“„ License #

MIT License - see LICENSE file for details.


🌟 Show Your Support #

If LayouAuth saved you time, give it a ⭐️ on GitHub and a πŸ‘ on pub.dev!


⬆ Back to top

Made with ❀️ for the Flutter community

1
likes
120
points
35
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Firebase Auth package with Google, Apple, and Email sign-in/linking support. Provides ready-to-use widgets and Riverpod providers.

Repository (GitHub)
View/report issues
Contributing

Topics

#firebase #authentication #riverpod #google-sign-in #apple-sign-in

License

MIT (license)

Dependencies

crypto, firebase_auth, flutter, flutter_riverpod, google_sign_in, riverpod_annotation, sign_in_with_apple

More

Packages that depend on layou_auth