π LayouAuth
Stop blocking users. Start building trust.
A Flutter authentication module that lets users experience your app first, authenticate later.
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:
Anonymous-first (recommended by layou_auth)
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
|
β With LayouAuth
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
FirebaseAuthExceptionand 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}');
},
);
Link Anonymous Account
// 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)
-
Enable capability in Xcode:
- Open
ios/Runner.xcworkspace - Select your target β Signing & Capabilities
- Click
+ Capabilityβ Add "Sign in with Apple"
- Open
-
Configure in Apple Developer Console:
- Go to Certificates, Identifiers & Profiles
- Select your App ID β Enable "Sign in with Apple"
-
Add to config:
const AppleProviderConfig()
π Google Sign-In
-
Add configuration files:
- iOS: Add
GoogleService-Info.plisttoios/Runner/ - Android: Add
google-services.jsontoandroid/app/
- iOS: Add
-
Get iOS client ID:
GoogleProviderConfig( iosClientId: DefaultFirebaseOptions.ios.iosClientId, // Or manually: 'YOUR_IOS_CLIENT_ID.apps.googleusercontent.com' ) -
Enable in Firebase Console:
- Authentication β Sign-in method β Google β Enable
π§ Email/Password
No platform setup needed! Just enable in Firebase:
-
Firebase Console β Authentication β Sign-in method β Email/Password β Enable
-
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 authenticationMulti-factor authenticationPassword reset UIEmail verification UIAnonymous user migration toolsBiometric authenticationSession management UI
π Credits
Built with β€οΈ by the Layou team.
Special thanks to:
- firebase_auth - The foundation
- google_sign_in - Google integration
- sign_in_with_apple - Apple integration
- riverpod - State management
π 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!
Made with β€οΈ for the Flutter community
Libraries
- layou_auth
- Firebase Auth package with Google, Apple, and Email sign-in/linking support.



