# VWO

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 35 minutes
- Last updated: July 2026

## TL;DR

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.

## 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.

## Before you start

- 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

### 1. 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.

**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.

### 2. 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.

```
import 'package:flutter/foundation.dart';
// Import the VWO FME Flutter SDK package
// (check pub.dev for the current official VWO FME Flutter package name)
import 'package:vwo_fme_flutter_sdk/vwo_fme_flutter_sdk.dart';

// Store the VWO client as a module-level variable
// so GetVWOFlag Custom Action can access it
VWOClient? _vwoClient;

Future initVWO() async {
  if (kIsWeb) return;

  final vwoInitOptions = VWOInitOptions(
    sdkKey: 'YOUR_VWO_FME_SDK_KEY',
    accountId: YOUR_VWO_ACCOUNT_ID, // integer, no quotes
  );

  _vwoClient = await VWO.init(vwoInitOptions);
}
```

**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.

### 3. 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.

```
import 'package:flutter/foundation.dart';
import 'package:vwo_fme_flutter_sdk/vwo_fme_flutter_sdk.dart';

// Reference the module-level client from InitVWO
extern VWOClient? _vwoClient;

// Arguments: flagKey (String), userId (String)
// Returns: String (variation name, e.g. 'control' or 'variant_a')
Future<String> getVWOFlag(
  String flagKey,
  String userId,
) async {
  if (kIsWeb) return 'control'; // always show default on web

  if (_vwoClient == null) {
    return 'control'; // graceful fallback if SDK not ready
  }

  final userContext = VWOContext(userId: userId);

  try {
    final flag = await _vwoClient!.getFlag(flagKey, userContext);
    if (flag.isEnabled()) {
      // Get the variation variable — 'variation_name' must match
      // the variable key you defined in the VWO flag settings
      return flag.getVariable('variation_name', 'control') as String;
    }
    return 'control';
  } catch (e) {
    return 'control'; // always fall back to control on error
  }
}
```

**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.

### 4. 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.

**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.

### 5. 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.

```
import 'package:flutter/foundation.dart';
import 'package:vwo_fme_flutter_sdk/vwo_fme_flutter_sdk.dart';

extern VWOClient? _vwoClient;

// Arguments: eventName (String), userId (String)
Future trackVWOConversion(
  String eventName,
  String userId,
) async {
  if (kIsWeb) return;
  if (_vwoClient == null) return;

  final userContext = VWOContext(userId: userId);
  try {
    await _vwoClient!.trackEvent(eventName, userContext);
  } catch (e) {
    // Log but do not rethrow — conversion tracking failure
    // should never block the user action itself
    debugPrint('VWO trackEvent error: $e');
  }
}
```

**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.

## 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.

## 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.

Prompt example:

```
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.
```

### 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.

Prompt example:

```
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.
```

### 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.

Prompt example:

```
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.
```

## 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.

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/vwo
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/vwo
