Controlling Rollouts in Production: Feature Flags Done Right in Flutter
In today’s fast-moving mobile app ecosystem, delivering personalized and controlled user experiences is more critical than ever—especially in fitness applications, where user engagement, monetization, and retention hinge on offering just the right set of features at the right time.
While building our cross-platform fitness app with Flutter, we faced a common but challenging requirement: supporting six different subscription plans—Standard and Premium, each with Weekly, Monthly, and Annual durations. Each plan unlocks a unique combination of features, timelines, and access levels. Managing these variations with hardcoded logic or repetitive conditionals would not only bloat the codebase but also introduce risks during feature rollouts and updates.
To solve this, we implemented a robust Feature Flagging system—a dynamic, scalable, and modular solution for Advanced Feature Management. With this architecture, features can be toggled, tested, or tiered without needing to ship a new version of the app. This enabled us to:
In this article, we'll explore how we designed and implemented this system, the Flutter classes that power it, and how it transformed our ability to ship and manage features confidently in production.
Want to watch instead? Watch this 30 mins + tutorial on getting yourself ready with feature flags in Flutter
As our fitness app evolved, we quickly realized that managing user access through just two broad categories—Standard and Premium—wasn’t going to scale. While it seemed straightforward on the surface, the underlying complexity was significant. Here’s why:
These limitations signaled the need for a smarter feature management system, one that treated features as independent capabilities rather than static properties of a plan. That’s when we decided to implement a custom Feature Flagging system tailored to our app's unique requirements.
To address the growing complexity of subscription-based feature management, we designed a Feature Flagging System tailored for scale, clarity, and flexibility. Here’s how it works:
We began by formalizing all possible subscription types using an enum called SubscriptionPlans. This allowed us to treat each plan (Standard Weekly, Premium Monthly, etc.) as a distinct entity, making the logic around access control explicit and type-safe:
enum SubscriptionPlans { |
We also implemented a utility function getSubscriptionPlan() to derive the appropriate enum based on backend values like plan name and duration. This minimized the chances of misclassification and centralized plan parsing logic.
All current and upcoming features were declared as string constants in a dedicated Features class. This approach provided:
class Features { |
We created a featuresAccessMap to define which features were available to each plan. This configuration is declarative, making it easy to audit, test, and expand as new plans or features roll out.
final Map<SubscriptionPlans, Set<String>> featuresAccessMap = { |
This effectively decoupled plan logic from app logic—making the feature access system data-driven instead of hardcoded.
At the heart of this system is the FeatureManager singleton. It is initialized once with the current user’s plan and provides a simple hasFeature() method for any feature check throughout the app.
FeatureManager.initialize(userPlan); |
This pattern ensured:
Together, these components formed a powerful, testable, and scalable solution to the problem of managing subscription-based features in a growing app ecosystem.
Implementing a robust feature flagging system wasn’t just a backend or architectural decision — it directly enhanced the user experience, performance, and flexibility of our fitness application. Here’s how it translated into practical, real-world impact:
New users get immediate access to the Free Trial plan, which unlocks nearly every premium feature for 7 days. This onboarding flow is powered entirely by the feature flagging system:
With a single source of truth (FeatureManager), the app can instantly determine what to show or hide:
if (FeatureManager.instance.hasFeature(Features.advancedNutritionAI)) { |
This dynamic control:
When users upgrade or downgrade their subscription (e.g., from Standard Monthly to Premium Annual), the app doesn’t need a restart or reinitialization of all layers:
This allows for:
Some features are still under development or being A/B tested. Our system makes it easy to expose these features to only certain plans (or even specific user cohorts later on):
This keeps development decoupled and makes gradual releases safer.
By explicitly tying features to plans in a single map, we also unlocked several strategic advantages:
This system not only simplified engineering but also created a foundation for flexible growth, faster iteration, and a personalized fitness journey for every user.
Designing a scalable and dynamic feature flagging system was a game-changer for our fitness app. Here are the key takeaways from the journey:
Instead of just "Standard" and "Premium," we acknowledged the nuance across timeframes (weekly, monthly, annually) and built a dedicated enum to represent each unique plan. This clarity helped eliminate ambiguity across both logic and UI.
By maintaining a central map (featuresAccessMap) linking plans to features, and routing all access checks through FeatureManager.hasFeature(), we ensured:
Thanks to runtime initialization (FeatureManager.initialize()), any change in plan — whether due to payment, trial expiry, or manual switching — reflects across the app instantly and without app restarts.
Our modular Features class and mapping approach means we can:
Product and marketing teams now have a concrete map of what’s tied to which plan. That directly feeds into:
We’re already thinking ahead:
Building this system wasn't just about writing cleaner code — it was about enabling product scalability, personalized UX, and monetization from the ground up. Whether you're building a fitness app, a productivity tool, or any subscription-based product, investing in smart feature management early pays dividends in the long run.