Skip to main content
The AppDNA Flutter SDK provides a server-driven onboarding module that presents onboarding flows configured in the AppDNA Console. Flows are delivered to the app via the config bundle and rendered natively.

Onboarding Module

Access the onboarding module through AppDNA.onboarding, which returns an AppDNAOnboardingModule instance:
final onboarding = AppDNA.onboarding;

Present an Onboarding Flow

Present a specific onboarding flow by its ID:
await AppDNA.onboarding.present(
  "main_flow",
  context: OnboardingContext(source: "deep_link"),
);
Or via the static method:
await AppDNA.presentOnboarding("main_flow");
Onboarding flows are configured in the AppDNA Console under Onboarding > Flows. The flow ID you pass to present() must match the ID configured in the Console.

OnboardingContext

The OnboardingContext class provides additional context about how the onboarding flow was triggered:
PropertyTypeDescription
sourceString?How the flow was triggered (e.g., “deep_link”, “app_launch”)
campaignString?Campaign identifier for attribution
referrerString?Referrer identifier
userPropertiesMap<String, dynamic>?Custom user properties for flow personalization
experimentOverridesMap<String, String>?Override experiment variants for testing

Example with Full Context

await AppDNA.onboarding.present(
  "main_flow",
  context: OnboardingContext(
    source: "deep_link",
    campaign: "summer_promo",
    referrer: "friend_invite",
    userProperties: {
      "tier": "free",
      "signup_method": "google",
    },
    experimentOverrides: {
      "onboarding_variant": "b",
    },
  ),
);
The experimentOverrides parameter is intended for development and testing only. In production, experiment variants are assigned automatically by the AppDNA backend.

AppDNAOnboardingDelegate

Implement the AppDNAOnboardingDelegate abstract class to receive onboarding lifecycle callbacks:
abstract class AppDNAOnboardingDelegate {
  void onOnboardingStarted(String flowId);
  void onOnboardingStepChanged(
    String flowId,
    String stepId,
    int stepIndex,
    int totalSteps,
  );
  void onOnboardingCompleted(
    String flowId,
    Map<String, dynamic> responses,
  );
  void onOnboardingDismissed(String flowId, int atStep);
}

Delegate Methods

MethodDescription
onOnboardingStartedCalled when the onboarding flow begins
onOnboardingStepChangedCalled each time the user advances to a new step
onOnboardingCompletedCalled when the user finishes all steps; includes collected responses
onOnboardingDismissedCalled when the user dismisses the flow before completing it

Set the Delegate

AppDNA.onboarding.setDelegate(myOnboardingDelegate);

Example Implementation

class MyOnboardingHandler implements AppDNAOnboardingDelegate {
  @override
  void onOnboardingStarted(String flowId) {
    print("Onboarding started: $flowId");
  }

  @override
  void onOnboardingStepChanged(
    String flowId,
    String stepId,
    int stepIndex,
    int totalSteps,
  ) {
    print("Step $stepIndex of $totalSteps: $stepId");
    // Update progress indicator
  }

  @override
  void onOnboardingCompleted(
    String flowId,
    Map<String, dynamic> responses,
  ) {
    print("Onboarding completed: $flowId");
    print("Responses: $responses");
    // Save user preferences from onboarding responses
    // Navigate to the main app screen
  }

  @override
  void onOnboardingDismissed(String flowId, int atStep) {
    print("Onboarding dismissed at step $atStep");
    // Track early exit, offer to resume later
  }
}

Full Example

import 'package:appdna_sdk/appdna_sdk.dart';
import 'package:flutter/material.dart';

class OnboardingScreen extends StatefulWidget {
  const OnboardingScreen({super.key});

  @override
  State<OnboardingScreen> createState() => _OnboardingScreenState();
}

class _OnboardingScreenState extends State<OnboardingScreen>
    implements AppDNAOnboardingDelegate {
  @override
  void initState() {
    super.initState();
    AppDNA.onboarding.setDelegate(this);
    _startOnboarding();
  }

  Future<void> _startOnboarding() async {
    await AppDNA.onboarding.present(
      "main_flow",
      context: OnboardingContext(source: "app_launch"),
    );
  }

  @override
  void onOnboardingStarted(String flowId) {
    print("Started: $flowId");
  }

  @override
  void onOnboardingStepChanged(
    String flowId,
    String stepId,
    int stepIndex,
    int totalSteps,
  ) {
    print("Step $stepIndex/$totalSteps");
  }

  @override
  void onOnboardingCompleted(
    String flowId,
    Map<String, dynamic> responses,
  ) {
    // Navigate to main app
    Navigator.of(context).pushReplacementNamed('/home');
  }

  @override
  void onOnboardingDismissed(String flowId, int atStep) {
    // Allow user to skip onboarding
    Navigator.of(context).pushReplacementNamed('/home');
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
}
Ensure the onboarding flow ID exists in the AppDNA Console before calling present(). Presenting a non-existent flow ID will result in a no-op with an error logged at the debug level.