# App Onboarding with Deferred Deep Linking: Complete User Journey > Create seamless app onboarding using deferred deep linking. Learn user flow optimization, first-time experience design, and retention strategies. - Canonical: https://redirectly.app/blog/app-onboarding-deferred-deep-linking - Site: [Redirectly](https://redirectly.app) — deferred deep linking for Flutter & React Native - Published: 2024-01-05 - Author: Dmytro Vyrych - Category: UX · 9 min read Design perfect onboarding experiences that work even when users install your app after clicking a marketing link. Learn how to create seamless user journeys from click to first value with deferred deep linking. ## Table of Contents 1. Overview & Benefits 2. User Journey Design 3. Implementation 4. Onboarding Flows 5. Best Practices ## Overview & Benefits **What is Deferred Deep Linking for Onboarding?** Deferred deep linking allows users to click a marketing link, install your app, and then land exactly where they intended - even if they didn't have the app installed when they first clicked. This creates seamless onboarding experiences that maintain context and drive higher engagement rates. ### Key Benefits - Seamless user experience - Higher conversion rates - Context preservation - Reduced friction - Better user retention - Improved engagement ### Use Cases - Marketing campaign onboarding - Referral program flows - Feature-specific onboarding - Content deep linking - Social media campaigns - Email marketing flows ## User Journey Design ### Complete User Journey 1. **User Clicks Marketing Link** — User clicks a link from email, social media, or advertisement 2. **App Store Redirect** — User is redirected to app store to install the app 3. **App Installation** — User installs and opens the app for the first time 4. **Deferred Deep Link Activation** — App detects the original intent and navigates to the right screen 5. **Contextual Onboarding** — User receives personalized onboarding based on their original intent ## Implementation ### 1. Onboarding Service Setup Create a service to handle deferred deep linking and onboarding: ```dart class OnboardingService { static final FlutterRedirectly _redirectly = FlutterRedirectly(); static bool _hasProcessedInitialLink = false; static Future initialize() async { await _redirectly.initialize(RedirectlyConfig( apiKey: 'YOUR_API_KEY', baseUrl: 'https://redirectly.app', enableDebugLogging: true, )); // Listen for app install events _redirectly.onAppInstalled.listen(_handleAppInstall); // Check for initial link on app start await _checkInitialLink(); } static Future _checkInitialLink() async { if (_hasProcessedInitialLink) return; final initialLink = await _redirectly.getInitialLink(); if (initialLink != null) { await _processOnboardingLink(initialLink); _hasProcessedInitialLink = true; } } static void _handleAppInstall(AppInstallEvent event) { if (event.matched && event.link != null) { _processOnboardingLink(event.link!); } } static Future _processOnboardingLink(LinkClickEvent event) async { if (event.error != null) { print('Onboarding link error: ${event.error}'); return; } final uri = Uri.parse(event.originalUrl); final onboardingData = _extractOnboardingData(uri); if (onboardingData != null) { // Store onboarding context await _storeOnboardingContext(onboardingData); // Navigate to appropriate onboarding flow _navigateToOnboardingFlow(onboardingData); } } static Map? _extractOnboardingData(Uri uri) { // Extract onboarding parameters from the link final source = uri.queryParameters['source']; final campaign = uri.queryParameters['campaign']; final feature = uri.queryParameters['feature']; final referrer = uri.queryParameters['ref']; if (source != null) { return { 'source': source, 'campaign': campaign, 'feature': feature, 'referrer': referrer, 'timestamp': DateTime.now().toIso8601String(), }; } return null; } static Future _storeOnboardingContext(Map data) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('onboarding_context', jsonEncode(data)); } static void _navigateToOnboardingFlow(Map data) { final source = data['source'] as String; switch (source) { case 'marketing': _navigateToMarketingOnboarding(data); break; case 'referral': _navigateToReferralOnboarding(data); break; case 'feature': _navigateToFeatureOnboarding(data); break; default: _navigateToDefaultOnboarding(); } } static void _navigateToMarketingOnboarding(Map data) { Get.offAllNamed('/onboarding/marketing', arguments: { 'campaign': data['campaign'], 'source': data['source'], }); } static void _navigateToReferralOnboarding(Map data) { Get.offAllNamed('/onboarding/referral', arguments: { 'referrer': data['referrer'], 'source': data['source'], }); } static void _navigateToFeatureOnboarding(Map data) { Get.offAllNamed('/onboarding/feature', arguments: { 'feature': data['feature'], 'source': data['source'], }); } static void _navigateToDefaultOnboarding() { Get.offAllNamed('/onboarding/default'); } } ``` ### 2. Initialize in main.dart Initialize the onboarding service when your app starts: ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize onboarding service await OnboardingService.initialize(); runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( title: 'My App', theme: ThemeData( primarySwatch: Colors.blue, ), home: AuthService.isLoggedIn() ? HomeScreen() : OnboardingWrapper(), routes: { '/onboarding/default': (context) => DefaultOnboardingScreen(), '/onboarding/marketing': (context) => MarketingOnboardingScreen(), '/onboarding/referral': (context) => ReferralOnboardingScreen(), '/onboarding/feature': (context) => FeatureOnboardingScreen(), '/home': (context) => HomeScreen(), }, ); } } class OnboardingWrapper extends StatelessWidget { @override Widget build(BuildContext context) { return FutureBuilder?>( future: _getOnboardingContext(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return SplashScreen(); } final context = snapshot.data; if (context != null) { // Navigate to appropriate onboarding based on context WidgetsBinding.instance.addPostFrameCallback((_) { OnboardingService._navigateToOnboardingFlow(context); }); } return DefaultOnboardingScreen(); }, ); } Future?> _getOnboardingContext() async { final prefs = await SharedPreferences.getInstance(); final contextString = prefs.getString('onboarding_context'); if (contextString != null) { return jsonDecode(contextString) as Map; } return null; } } ``` ## Onboarding Flows ### Marketing Campaign Onboarding Create personalized onboarding experiences for users coming from marketing campaigns: ```dart class MarketingOnboardingScreen extends StatefulWidget { final String? campaign; final String? source; const MarketingOnboardingScreen({ Key? key, this.campaign, this.source, }) : super(key: key); @override _MarketingOnboardingScreenState createState() => _MarketingOnboardingScreenState(); } class _MarketingOnboardingScreenState extends State { int _currentStep = 0; final PageController _pageController = PageController(); final List _steps = [ OnboardingStep( title: 'Welcome to Our App!', description: 'Thanks for joining us from our marketing campaign.', image: 'assets/onboarding/welcome.png', ), OnboardingStep( title: 'Discover Features', description: 'Explore the amazing features we have to offer.', image: 'assets/onboarding/features.png', ), OnboardingStep( title: 'Get Started', description: 'You're all set! Start exploring the app.', image: 'assets/onboarding/complete.png', ), ]; @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: [ // Progress indicator LinearProgressIndicator( value: (_currentStep + 1) / _steps.length, backgroundColor: Colors.grey[300], valueColor: AlwaysStoppedAnimation(Colors.blue), ), // Onboarding content Expanded( child: PageView.builder( controller: _pageController, onPageChanged: (index) { setState(() => _currentStep = index); }, itemCount: _steps.length, itemBuilder: (context, index) { final step = _steps[index]; return OnboardingPage( step: step, campaign: widget.campaign, source: widget.source, ); }, ), ), // Navigation buttons Padding( padding: EdgeInsets.all(24.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (_currentStep > 0) TextButton( onPressed: _previousStep, child: Text('Back'), ) else SizedBox.shrink(), ElevatedButton( onPressed: _nextStep, child: Text(_currentStep == _steps.length - 1 ? 'Get Started' : 'Next'), ), ], ), ), ], ), ), ); } void _previousStep() { if (_currentStep > 0) { _pageController.previousPage( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, ); } } void _nextStep() { if (_currentStep < _steps.length - 1) { _pageController.nextPage( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, ); } else { _completeOnboarding(); } } void _completeOnboarding() { // Track onboarding completion AnalyticsService.trackEvent('onboarding_completed', { 'source': widget.source, 'campaign': widget.campaign, 'flow': 'marketing', }); // Navigate to main app Get.offAllNamed('/home'); } } ``` ## Best Practices ### User Experience - Keep onboarding flows short and focused - Use clear, actionable language - Provide visual feedback and progress indicators - Allow users to skip non-essential steps - Test onboarding flows on different devices - Gather user feedback and iterate ### Technical Implementation - Handle edge cases gracefully - Implement proper error handling - Use analytics to track completion rates - Store onboarding state persistently - Optimize for performance - Test deferred deep linking thoroughly **Pro Tips for Success** - Personalize onboarding based on the source of the user - Use deferred deep linking to maintain context across the user journey - A/B test different onboarding flows to optimize conversion rates - Track key metrics like completion rates and time to first value - Provide clear value propositions early in the onboarding process - Use progressive disclosure to avoid overwhelming new users ## 🎉 Perfect Onboarding Experiences Await! You now have a complete onboarding system with deferred deep linking that provides seamless user experiences. This setup will help you convert more users and improve retention rates significantly. ## Related Articles & Resources - [Deferred Deep Linking Guide](https://redirectly.app/deferred-deep-linking.md) — Master deferred deep linking and install attribution for your mobile apps. - [Marketing Campaigns with Deep Linking](https://redirectly.app/blog/marketing-campaigns-deep-linking.md) — Maximize your marketing ROI with deep linking campaigns and detailed attribution tracking. --- Full site index for AI agents: https://redirectly.app/llms.txt