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:
| Property | Type | Description |
|---|
source | String? | How the flow was triggered (e.g., “deep_link”, “app_launch”) |
campaign | String? | Campaign identifier for attribution |
referrer | String? | Referrer identifier |
userProperties | Map<String, dynamic>? | Custom user properties for flow personalization |
experimentOverrides | Map<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
| Method | Description |
|---|
onOnboardingStarted | Called when the onboarding flow begins |
onOnboardingStepChanged | Called each time the user advances to a new step |
onOnboardingCompleted | Called when the user finishes all steps; includes collected responses |
onOnboardingDismissed | Called 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.