Onboarding
The onboarding flow guides new users through initial app setup, including welcome screen, currency selection, and initial balance configuration.
Overview
Section titled “Overview”The onboarding consists of three screens:
- Welcome Page - App introduction and branding
- Currency Selector - Choose default currency
- Initial Balance - Set starting cash balance
Flow Architecture
Section titled “Flow Architecture”OnboardView (Container)├── WelcomePage├── CurrencySelectorPage└── AddInitialBalancePageWelcome Page
Section titled “Welcome Page”First impression screen with:
- App logo and branding
- Brief app description
- Get Started button
Implementation
Section titled “Implementation”Located in: lib/app/pages/auth/onboard/pages/welcome_page.dart
class WelcomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Logo AppLogo(), SizedBox(height: 32), // Description Text('Manage your finances with ease'), SizedBox(height: 48), // CTA Button ShadButton( onPressed: () => context.router.push(const CurrencySelectorRoute()), child: Text('Get Started'), ), ], ), ), ); }}Currency Selector
Section titled “Currency Selector”Allows users to select their default currency from a searchable list.
Features
Section titled “Features”- Searchable currency list
- Flag icons for visual identification
- Popular currencies at top
- Automatic symbol preview
Supported Currencies
Section titled “Supported Currencies”Over 150 currencies supported including:
- USD ($) - US Dollar
- EUR (€) - Euro
- GBP (£) - British Pound
- JPY (¥) - Japanese Yen
- And many more…
Implementation
Section titled “Implementation”Located in: lib/app/pages/auth/onboard/pages/currency_selector_page.dart
class CurrencySelectorPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currencies = CurrencyMock.all;
return Scaffold( appBar: AppBar(title: Text('Select Currency')), body: ListView.builder( itemCount: currencies.length, itemBuilder: (context, index) { final currency = currencies[index]; return ListTile( leading: CurrencyFlag(currency.flagName), title: Text(currency.code), subtitle: Text(currency.countryName), trailing: Text(currency.symbol), onTap: () { ref.read(authUserServiceProvider.notifier) .updateCurrency(currency); context.router.push(const AddInitialBalanceRoute()); }, ); }, ), ); }}Initial Balance Setup
Section titled “Initial Balance Setup”Final step where users set their starting cash balance.
Features
Section titled “Features”- Large numeric display
- Numeric keypad for input
- Skip option available
- Formatted currency display
Implementation
Section titled “Implementation”Located in: lib/app/pages/auth/onboard/pages/add_initial_balance_page.dart
class AddInitialBalancePage extends ConsumerStatefulWidget { @override ConsumerState createState() => _AddInitialBalancePageState();}
class _AddInitialBalancePageState extends ConsumerState<AddInitialBalancePage> { final _amountController = TextEditingController();
@override Widget build(BuildContext context) { final currency = ref.watch(userCurrencyProvider);
return Scaffold( appBar: AppBar( title: Text('Initial Balance'), actions: [ TextButton( onPressed: _completeOnboarding, child: Text('Skip'), ), ], ), body: Column( children: [ // Amount Display Text( '${currency.symbol}${_amountController.text}', style: Theme.of(context).textTheme.headlineLarge, ),
// Numeric Pad NumericPad.balance(controller: _amountController),
// Continue Button ShadButton( onPressed: _saveAndContinue, child: Text('Continue'), ), ], ), ); }
void _saveAndContinue() { final amount = double.tryParse(_amountController.text) ?? 0; ref.read(authUserServiceProvider.notifier) .updateInitialBalance(amount); _completeOnboarding(); }
void _completeOnboarding() { // Mark onboarding as complete and navigate to main app context.router.replace(const ClientRoute()); }}Route Guard
Section titled “Route Guard”The OnboardRouteGuard ensures users complete onboarding before accessing the main app:
class OnboardRouteGuard extends AutoRouteGuard { @override void onNavigation(NavigationResolver resolver, StackRouter router) { final authService = ProviderScope.containerOf(resolver.context) .read(authUserServiceProvider);
final hasCompletedOnboarding = authService.value?.hasCompletedOnboarding ?? false;
if (hasCompletedOnboarding) { resolver.next(true); } else { resolver.redirect(const OnboardRoute()); } }}Customization
Section titled “Customization”Changing Welcome Content
Section titled “Changing Welcome Content”Edit welcome_page.dart:
Text( 'Your Custom App Description', style: ShadTheme.of(context).textTheme.h2,)Adding Steps
Section titled “Adding Steps”Add new routes to the onboard flow in _app_router.dart:
AutoRoute( page: OnboardRoute.page, path: '/onboard', children: [ AutoRoute(page: WelcomeRoute.page, path: ''), AutoRoute(page: CurrencySelectorRoute.page, path: 'currency-selector'), AutoRoute(page: YourNewRoute.page, path: 'new-step'), // Add this AutoRoute(page: AddInitialBalanceRoute.page, path: 'add-initial-balance'), ],)Styling
Section titled “Styling”The onboarding uses the app’s theme automatically. To customize:
- Change color scheme in Settings > Appearance
- Modify theme configuration in
lib/app/core/theme/
Data Persistence
Section titled “Data Persistence”Onboarding progress is stored via AuthUserService:
class AuthUserService extends AsyncNotifier<User> { Future<void> completeOnboarding() async { final currentUser = state.value!; await updateUser(currentUser.copyWith( hasCompletedOnboarding: true, )); }}Best Practices
Section titled “Best Practices”- Keep it short - 3-4 steps maximum
- Allow skipping - Not all users want to set up immediately
- Show value - Explain why each step is important
- Save progress - Store data even if user skips
- Test on devices - Ensure smooth animations on all screen sizes