Skip to content

Onboarding

The onboarding flow guides new users through initial app setup, including welcome screen, currency selection, and initial balance configuration.

The onboarding consists of three screens:

  1. Welcome Page - App introduction and branding
  2. Currency Selector - Choose default currency
  3. Initial Balance - Set starting cash balance
OnboardView (Container)
├── WelcomePage
├── CurrencySelectorPage
└── AddInitialBalancePage

First impression screen with:

  • App logo and branding
  • Brief app description
  • Get Started button

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'),
),
],
),
),
);
}
}

Allows users to select their default currency from a searchable list.

  • Searchable currency list
  • Flag icons for visual identification
  • Popular currencies at top
  • Automatic symbol preview

Over 150 currencies supported including:

  • USD ($) - US Dollar
  • EUR (€) - Euro
  • GBP (£) - British Pound
  • JPY (¥) - Japanese Yen
  • And many more…

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());
},
);
},
),
);
}
}

Final step where users set their starting cash balance.

  • Large numeric display
  • Numeric keypad for input
  • Skip option available
  • Formatted currency display

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());
}
}

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());
}
}
}

Edit welcome_page.dart:

Text(
'Your Custom App Description',
style: ShadTheme.of(context).textTheme.h2,
)

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'),
],
)

The onboarding uses the app’s theme automatically. To customize:

  1. Change color scheme in Settings > Appearance
  2. Modify theme configuration in lib/app/core/theme/

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,
));
}
}
  1. Keep it short - 3-4 steps maximum
  2. Allow skipping - Not all users want to set up immediately
  3. Show value - Explain why each step is important
  4. Save progress - Store data even if user skips
  5. Test on devices - Ensure smooth animations on all screen sizes