Connect FlutterFlow to VWO using a Custom Action that wraps the VWO Feature Management & Experimentation (FME) mobile SDK — VWO's visual point-and-click editor does not work on compiled Flutter apps. Initialize the SDK with your SDK key, call getFlag to receive a variation assignment, store it in an App State variable, and drive Conditional Visibility in FlutterFlow widgets to show the correct variant to each user.
| Fact | Value |
|---|---|
| Tool | VWO |
| Category | Analytics |
| Method | Custom Action (Dart) |
| Difficulty | Intermediate |
| Time required | 35 minutes |
| Last updated | July 2026 |
Run A/B Tests in FlutterFlow with VWO Feature Flags — No Visual Editor
VWO is best known for its point-and-click visual A/B testing editor: you open a website, highlight an element, change the text or color, and VWO shows each variation to a percentage of visitors. That workflow is built for websites and relies on a JavaScript DOM that VWO can intercept and modify at runtime. A compiled Flutter app — which is what FlutterFlow generates — has no DOM. VWO's visual editor cannot reach inside a native Flutter app and change a button's color or rearrange widgets. This is the most important thing to understand before starting: if you are expecting to drag a visual test onto your FlutterFlow screens, that path does not exist.
What does exist — and works well for FlutterFlow — is VWO's Feature Management & Experimentation (FME) platform. Instead of VWO modifying your UI directly, your app asks VWO at runtime: 'For this user, which variation of flag X should I show?' VWO returns a variation assignment based on the user's ID and the experiment traffic allocation you configured. Your app receives the answer, stores it in a variable, and branches the FlutterFlow widget tree using Conditional Visibility. You are making the UI change — VWO is telling you which change to make for each user.
This is the server-side testing model, and it is more powerful than visual testing for app UX experiments because you can test entire screen layouts, navigation flows, and business logic — not just visual surface changes. The tradeoff is that the branching logic lives in your FlutterFlow app, which means each new experiment requires a FlutterFlow build update. Plan your flag keys and variant names thoughtfully so a single build can support multiple active experiments simultaneously.
VWO's FME tier includes a free Starter plan for feature flags; paid plans add advanced analytics and revenue tracking. Check VWO's current pricing for the plan that covers FME with the reporting depth your team needs.
Integration method
VWO integrates with FlutterFlow through a Custom Action wrapping the VWO FME (Feature Management & Experimentation) mobile SDK. On app start, the SDK is initialized with your SDK key and account ID. When a user reaches an experiment touchpoint, a second Custom Action calls getFlag with the flag key and a user context object, stores the returned variation in an App State variable, and FlutterFlow's Conditional Visibility rules branch the widget tree accordingly. There is no drag-and-drop visual editor path for Flutter — all UI branching is done in FlutterFlow using the variation value from App State.
Prerequisites
- A VWO account with FME (Feature Management & Experimentation) access — VWO Starter plan includes basic FME
- A VWO FME feature flag created in VWO's dashboard with at least two variations defined
- Your VWO FME SDK key and account ID from VWO's FME settings panel
- A FlutterFlow project with Custom Code enabled
- App State variables created in FlutterFlow to hold the variation value for each experiment
Step-by-step guide
Create a VWO FME feature flag and note your SDK key
Before writing any FlutterFlow code, set up the experiment in VWO's dashboard. Log in to VWO, navigate to FME (Feature Management & Experimentation) in the top nav, and click Create Feature Flag. Give the flag a clear, machine-readable key name like `checkout_flow` or `new_home_screen` — this is the flagKey you will reference in your Custom Action code, so avoid spaces or special characters. Use lowercase letters and underscores. Define your variations. VWO always includes a Control variation (the default experience). Add one or more additional variations — name them descriptively like `variant_a` or `single_page` rather than generic names like `B` or `v2`. These names are what getFlag will return, and you will reference them in FlutterFlow's Conditional Visibility rules. Set the traffic split: the percentage of users assigned to each variation. Start with a 50/50 split for a new experiment. You can adjust this in the VWO dashboard without releasing a new app build — this is one of the key advantages of FME over hardcoded feature branches. Find your SDK key: in VWO's FME section, go to Settings → SDK Keys (the path may vary slightly by plan — look for Environments or Integration). The SDK key is associated with a specific environment (production, staging). Copy the production SDK key and your account ID. Note that the SDK key is environment-scoped: using a staging key in production assigns users to staging experiment configurations, which is a common source of test contamination.
Pro tip: Flag key names cannot be changed after a flag is created in VWO without creating a new flag. Choose names that describe the experiment clearly and will still make sense six months from now when you are reviewing old experiment results.
Expected result: The VWO FME feature flag appears in your VWO dashboard with two or more variations defined and a traffic split set. You have the SDK key and account ID ready to use in the next step.
Add the VWO FME SDK as a Custom Action and initialize on app start
In FlutterFlow, open the left nav and click Custom Code. Click + Add and select Action. Name the action InitVWO. In the Dependencies field, add the VWO FME Flutter/mobile SDK package. Search pub.dev for the current VWO FME Flutter package — look for an official VWO package under the VWO or vwoFME namespace. Add the exact package name and FlutterFlow will resolve the version from pub.dev. In the Dart code editor, write the initialization. The VWO FME SDK initialization is asynchronous and returns a VWO client object. Store this client in a Dart-level static variable so subsequent Custom Actions can call getFlag on it. Wrap the entire function with a `kIsWeb` guard — VWO's mobile SDK is native-only and the web preview will not resolve variations. Import `package:flutter/foundation.dart` for `kIsWeb`. Initialize the SDK with your SDK key and account ID. You can include these directly in the Custom Action code — the SDK key is environment-scoped and is designed to be distributed in client apps (it is analogous to a publishable key: it can be used to look up experiment configurations, but it cannot modify VWO experiment settings or access your account data). Even so, the RapidDev recommendation is to route conversion events through a proxy if you want to eliminate any risk of users spoofing events. Wire the InitVWO Custom Action to your app's root widget or initial page's On Page Load action in FlutterFlow. The SDK fetches a configuration file from VWO's CDN on startup — this is a fast CDN request and does not add meaningful startup delay, but the configuration is cached after the first fetch. Custom Actions do not run in FlutterFlow's web-based Test Mode. Always test on a real device or Run Mode build.
1import 'package:flutter/foundation.dart';2// Import the VWO FME Flutter SDK package3// (check pub.dev for the current official VWO FME Flutter package name)4import 'package:vwo_fme_flutter_sdk/vwo_fme_flutter_sdk.dart';56// Store the VWO client as a module-level variable7// so GetVWOFlag Custom Action can access it8VWOClient? _vwoClient;910Future initVWO() async {11 if (kIsWeb) return;1213 final vwoInitOptions = VWOInitOptions(14 sdkKey: 'YOUR_VWO_FME_SDK_KEY',15 accountId: YOUR_VWO_ACCOUNT_ID, // integer, no quotes16 );1718 _vwoClient = await VWO.init(vwoInitOptions);19}Pro tip: If the VWO FME SDK is still loading when a user reaches an experiment touchpoint, the flag fetch will return a default value. This is expected and safe — define your default variation (usually 'control') explicitly as a fallback App State value before the SDK finishes initializing, so the UI never renders blank.
Expected result: The InitVWO Custom Action saves without errors. When run on a real device, the VWO client initializes and fetches flag configurations from VWO's CDN. No error logs appear in the device console. The VWO dashboard shows the SDK as active in the FME environment settings.
Call getFlag and store the variation in App State
Create a second Custom Action named GetVWOFlag. This action is the core of the integration: it asks VWO which variation to show for a specific user, then stores the answer in a FlutterFlow App State variable so Conditional Visibility rules can use it. The action needs two String arguments: flagKey (the VWO feature flag key you created, like `checkout_flow`) and userId (your app's unique user identifier — the same stable ID you use across Firestore or Supabase). The userId is the basis for VWO's variation assignment: a given userId always receives the same variation for the lifetime of the experiment, ensuring consistent experiences across app sessions. Never use a random ID generated fresh on each app launch — that would assign users to random variations every session. In the function body, build a VWO UserContext object with the userId. You can optionally add custom attributes to the context (like userPlan, country, or accountAge) that match VWO targeting rules you configured in the VWO dashboard for the flag. Then call `_vwoClient?.getFlag(flagKey, userContext)` — this returns a Feature Flag object from which you call `.isEnabled()` to check if the flag is on, and `.getVariable(variableKey, defaultValue)` to get the specific variation name or value. In FlutterFlow, create an App State variable before writing this Custom Action. For a binary test, create a boolean named `showVariantA`. For a multi-variant test, create a String named `checkoutFlowVariant` with a default value of `control`. Set a return value or use the Custom Action's output to update the App State variable. Wire GetVWOFlag to fire On Page Load of the screen where the experiment lives — pass the flagKey and the signed-in user's ID as arguments. If the VWO client is not yet initialized when getFlag is called (because the InitVWO call is still in progress), the SDK returns the default variation. This is graceful degradation — the user sees the control experience, not a blank screen.
1import 'package:flutter/foundation.dart';2import 'package:vwo_fme_flutter_sdk/vwo_fme_flutter_sdk.dart';34// Reference the module-level client from InitVWO5extern VWOClient? _vwoClient;67// Arguments: flagKey (String), userId (String)8// Returns: String (variation name, e.g. 'control' or 'variant_a')9Future<String> getVWOFlag(10 String flagKey,11 String userId,12) async {13 if (kIsWeb) return 'control'; // always show default on web1415 if (_vwoClient == null) {16 return 'control'; // graceful fallback if SDK not ready17 }1819 final userContext = VWOContext(userId: userId);2021 try {22 final flag = await _vwoClient!.getFlag(flagKey, userContext);23 if (flag.isEnabled()) {24 // Get the variation variable — 'variation_name' must match25 // the variable key you defined in the VWO flag settings26 return flag.getVariable('variation_name', 'control') as String;27 }28 return 'control';29 } catch (e) {30 return 'control'; // always fall back to control on error31 }32}Pro tip: Always define a safe default variation in the App State variable before calling getFlag. The flag fetch is async — if you initialize the UI before the flag resolves, a brief flash of the wrong variant (or an empty screen) can occur. Set the App State to 'control' before calling GetVWOFlag, then update it when the action returns.
Expected result: The GetVWOFlag Custom Action returns a variation string ('control' or 'variant_a') and the corresponding App State variable is updated. In FlutterFlow's App State panel, you can see the variable change value when testing on a device.
Branch the FlutterFlow UI with Conditional Visibility
With the variation stored in App State, you can now drive the UI branch in FlutterFlow without writing more Dart code. This is entirely visual configuration in FlutterFlow's UI builder. For a binary test (two variants), the simplest approach is to add two Container widgets to your screen that contain the different UI layouts — one for control, one for variant A. Select the control Container, open its Properties panel, and find the Conditional Visibility section. Add a condition: App State variable `checkoutFlowVariant` equals `control`. Do the same for the variant A Container, with the condition checking for `variant_a`. Now FlutterFlow will show only one Container at a time based on the App State value. For a boolean flag (feature on/off), use a single App State boolean like `showNewHomeScreen`. Wrap the new design in a Container with Conditional Visibility checking `showNewHomeScreen == true`, and wrap the old design with `showNewHomeScreen == false`. If you have more than two variants, stack multiple Containers each with their own Conditional Visibility rule checking the variation String value. This scales to three or four variants cleanly — beyond that, consider restructuring the experiment into multiple binary flags. Test the UI branching in FlutterFlow's web Test Mode by manually changing the App State variable value in the State panel — you can switch between variant values and see the Conditional Visibility update live in the preview canvas even though the actual VWO flag fetch does not run in Test Mode. This is the recommended way to verify your UI branching logic before testing on a real device.
Pro tip: Use FlutterFlow's State Management panel to set App State variables to specific values during development — this lets you preview both the control and variant layouts in the canvas without deploying to a device. Verify the Conditional Visibility rules show and hide the correct containers before testing the actual VWO flag integration.
Expected result: The experiment screen shows different UI layouts depending on the variation value in App State. Manually changing the App State variable in the FlutterFlow State panel immediately updates which container is visible in the preview canvas. On a real device, VWO assigns a variation and the correct UI appears.
Fire trackEvent on conversion and add a safe default for web preview
Conversion tracking closes the experiment loop: VWO needs to know when a user completed the target action (purchase, sign-up, feature activation) so it can compute conversion rates per variation. Without trackEvent calls, you can see which variation users were assigned to, but you cannot measure which variation performs better. Create a third Custom Action named TrackVWOConversion. It takes two String arguments: eventName (matching the conversion event name you defined in the VWO FME dashboard) and userId (same stable identifier used in GetVWOFlag). In the function body, call `_vwoClient?.trackEvent(eventName, userContext)` where userContext is a VWOContext built with the same userId. Add the kIsWeb guard and a null check for the client. Wire TrackVWOConversion to the On Tap action of the target widget in your experiment — for a checkout test, this is the 'Place Order' button. For a sign-up experiment, this is the 'Create Account' success callback. Pass the correct event name and the signed-in user's ID. For the web preview fallback: since Custom Actions do not run in FlutterFlow's web Test Mode, your experiment screen would show whichever variant is the current default App State value (which you set to 'control' as a safe default). This means the preview always shows the control variant — which is correct expected behavior. Add a visible note in your FlutterFlow canvas comment: 'Variation set by VWO FME SDK — test on device.' This prevents future confusion when team members wonder why the preview always shows the control. Finally, add a 'settings loaded' flag to prevent UI flicker. Create an App State boolean named `vwoSettingsLoaded` with a default of false. Set it to true at the end of InitVWO. Add a Conditional Visibility rule on the experiment section of your screen: show it only when `vwoSettingsLoaded == true`. While the VWO settings are loading, show a loading indicator or the control UI instead of a blank screen.
1import 'package:flutter/foundation.dart';2import 'package:vwo_fme_flutter_sdk/vwo_fme_flutter_sdk.dart';34extern VWOClient? _vwoClient;56// Arguments: eventName (String), userId (String)7Future trackVWOConversion(8 String eventName,9 String userId,10) async {11 if (kIsWeb) return;12 if (_vwoClient == null) return;1314 final userContext = VWOContext(userId: userId);15 try {16 await _vwoClient!.trackEvent(eventName, userContext);17 } catch (e) {18 // Log but do not rethrow — conversion tracking failure19 // should never block the user action itself20 debugPrint('VWO trackEvent error: $e');21 }22}Pro tip: Never make the user's main action (completing a purchase, submitting a form) dependent on trackEvent succeeding. Wrap trackEvent in a try-catch and let it fail silently — a dropped conversion event is better than a failed purchase experience. VWO conversion data is statistical; one lost event per thousand sessions has no meaningful impact on results.
Expected result: Tapping the conversion trigger widget (e.g. the 'Place Order' button) fires TrackVWOConversion. In the VWO FME dashboard, the experiment's conversion metrics update to show conversions attributed to the assigned variation. The UI never shows a blank state — it defaults to the control variant if VWO settings are still loading.
Common use cases
Checkout flow A/B test — single-page vs multi-step
Build a FlutterFlow app that tests whether a single-page checkout converts better than a three-step wizard. VWO FME assigns each user to 'control' (multi-step) or 'variant_a' (single-page) via getFlag. FlutterFlow's Conditional Visibility on the checkout container shows the correct layout for each user. Track purchases with trackEvent to measure conversion rates in the VWO results dashboard.
Add an App State boolean 'showSinglePageCheckout'. On the checkout screen, call the GetVWOFlag Custom Action for flag key 'checkout_flow', and set showSinglePageCheckout to true if the returned variation is 'variant_a'. Wrap the single-page checkout container in a Conditional Visibility rule that checks showSinglePageCheckout.
Copy this prompt to try it in FlutterFlow
Feature flag rollout for a new home screen redesign
Use VWO FME as a feature flag to gradually roll out a new home screen design to 10% of users, then 50%, then 100% — without requiring separate app store releases. The FME SDK fetches the flag configuration from VWO's CDN on each app start. When the flag is enabled for a user, FlutterFlow shows the new home screen container; when disabled, it shows the existing design.
Create a VWO feature flag named 'new_home_screen' in VWO FME. In FlutterFlow, add an App State boolean 'useNewHomeScreen'. On app start, call the GetVWOFlag Custom Action. If the flag is enabled, set useNewHomeScreen to true. Add Conditional Visibility to both home screen containers to show the correct one.
Copy this prompt to try it in FlutterFlow
Personalized onboarding by user segment with conversion tracking
Run an experiment testing two onboarding sequences: one focused on social proof, another on feature discovery. VWO FME assigns new users to a variant based on their signup attributes (industry, company size) passed in the user context. FlutterFlow shows the correct onboarding sequence using Conditional Visibility on the page navigator. trackEvent fires when the user completes onboarding or subscribes to track which sequence converts better.
Initialize VWO FME on app start with the user's ID and company size attribute. On the onboarding start screen, call getFlag for 'onboarding_flow' and store the variant. Use Conditional Visibility to navigate to either the social-proof onboarding or feature-discovery onboarding route. Fire trackEvent('onboarding_completed') when the user finishes the flow.
Copy this prompt to try it in FlutterFlow
Troubleshooting
getFlag always returns 'control' even after the SDK initializes
Cause: The userId passed to getFlag does not match a user assigned to a non-control variation, or the flag's traffic allocation excludes the current user's segment. It is also possible the SDK is returning the default because initialization has not completed by the time getFlag is called.
Solution: Add a `vwoSettingsLoaded` App State flag that is set to true at the end of InitVWO, and call GetVWOFlag only after this flag is true. In the VWO dashboard, verify the flag's traffic allocation is set to more than 0% for all variations. Also confirm the SDK key matches the environment where the flag is active (staging vs production keys have separate flag configurations).
Custom Action SDK does not execute in FlutterFlow preview — no variation assigned
Cause: Custom Actions do not run in FlutterFlow's browser-based Test Mode or the preview canvas. This is a FlutterFlow platform limitation, not a VWO issue.
Solution: Test VWO variation assignment exclusively in Run Mode on a real iOS or Android device. In the FlutterFlow preview, manually set the App State variable to different variation values using the State Management panel to verify your Conditional Visibility rules work correctly without relying on the VWO SDK call.
UI flickers between control and variant_a for a brief moment on screen load
Cause: The screen renders with the default App State value (control) before getFlag resolves, then updates to the assigned variation — causing a visible flicker if both containers render at full opacity during the transition.
Solution: Add a loading state: create an App State boolean `vwoSettingsLoaded` set to false initially and true after InitVWO and GetVWOFlag complete. Wrap the experiment containers in a Conditional Visibility that requires `vwoSettingsLoaded == true`. Show a loading indicator or the control layout while the flag is being fetched.
VWO visual editor A/B test does not appear in the Flutter app at all
Cause: VWO's visual editor creates JavaScript-based DOM mutations that only work on web apps. Compiled Flutter apps have no DOM — there is no mechanism for VWO to inject visual changes into a native Flutter widget tree at runtime.
Solution: Use VWO FME (Feature Management & Experimentation) feature flags instead of the visual editor. Create a flag in VWO FME with variations, call getFlag in a Custom Action, and implement the UI branching yourself using Conditional Visibility in FlutterFlow. The FME path is the only supported integration for native Flutter apps.
Best practices
- Use VWO FME feature flags with getFlag — VWO's visual editor cannot modify compiled Flutter app UI and is not a supported path for FlutterFlow.
- Always set a safe default variation in App State before calling GetVWOFlag to prevent blank or undefined UI states while the SDK initializes.
- Add a kIsWeb guard in every VWO Custom Action — the FME mobile SDK is native-only and will cause issues on web targets without the guard.
- Use a stable, persistent user ID (from Firestore or Supabase auth) as the userId for both getFlag and trackEvent — never generate a fresh random ID per session.
- Wrap trackEvent calls in try-catch blocks so a network failure tracking a conversion never blocks the user's actual action (purchase, form submit).
- Gate experiment UI on a 'settings loaded' App State boolean to prevent flickering between default and assigned variants during SDK initialization.
- Test Conditional Visibility rules in the FlutterFlow canvas by manually setting the App State variable — do not rely on the VWO SDK to run in browser Test Mode.
- Define conversion event names in VWO's FME dashboard before wiring trackEvent in FlutterFlow — names must match exactly for conversions to be attributed to the correct experiment.
Alternatives
Optimizely's Feature Experimentation platform offers a similar server-side flag decision model with a Flutter SDK, and is often preferred by enterprise teams that need tighter integration with existing Optimizely analytics.
Mixpanel tracks user events and funnels and can measure conversion outcomes without A/B variation assignment — a better fit if you want behavioral analytics without running controlled experiments.
Amplitude Experiment provides feature flagging and A/B testing with deep behavioral analytics integration, making it a strong choice if you already use Amplitude for product analytics.
Frequently asked questions
Can I use VWO's visual point-and-click editor to A/B test my FlutterFlow app?
No — VWO's visual editor works by injecting JavaScript that modifies the browser DOM at runtime. A compiled FlutterFlow app is a native Flutter binary with no DOM, so VWO's visual editor has nothing to attach to. The supported path for FlutterFlow is VWO FME (Feature Management & Experimentation): you create a feature flag in VWO, call getFlag from a Custom Action, and branch the FlutterFlow widget tree yourself using Conditional Visibility on an App State variable.
Does VWO FME work for FlutterFlow web builds?
The VWO FME mobile SDK (wrapped in a Custom Action) is native-only and will not execute in FlutterFlow's web export. For FlutterFlow web builds, you would need to use the VWO JavaScript SDK injected into the web app's index.html. The two SDKs operate independently — a user on mobile and a user on web can be assigned to the same experiment, but the integration code is different for each platform.
Is the VWO SDK key safe to include in the compiled Flutter app?
The VWO FME SDK key is environment-scoped and is designed to be distributed in client applications — it can only fetch flag configurations for your account, not modify experiment settings or access billing or analytics data. It is analogous to a publishable API key. You should still avoid committing it to public source code repositories, but its presence in a compiled app binary is architecturally acceptable for the FME SDK use case.
How do I target only a specific user segment with a VWO flag?
Pass custom attributes in the UserContext object when calling getFlag: for example, user plan, account age, or country. In the VWO FME dashboard, configure targeting rules on the flag that match those attributes — for example 'show variant_a only to users with plan == premium'. VWO evaluates these rules server-side during the getFlag call and returns the correct variation for each user based on their attributes.
What happens to users who are mid-experiment when I update the flag in VWO's dashboard?
Because the VWO FME SDK fetches flag configuration from VWO's CDN on each app start, users will receive the updated configuration on their next app launch. If you change the traffic split or disable a variation mid-experiment, users already assigned to that variation will be reassigned on their next launch. This can invalidate statistical results if done during an active experiment — make traffic allocation changes only before starting data collection or after concluding an experiment.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation