Skip to content

Software Development at Program Tom LTD

Place for coding, programming, development and software in general.

Menu
  • Blog
  • PDF Booklets
  • Dev Utils & Content
  • Java Spring Boot Or Web Apps
  • English
    • български
    • English
    • Español
    • Português
    • हिन्दी
    • Русский
    • Deutsch
    • Français
    • Italiano
    • العربية
  • About Us
Menu
Feature Flags - Enable Functionality from the BackEnd

The 2 ways to approach Feature Flags

Posted on November 17, 2025 by Toma Velev

Feature flags (or feature toggles) allow you to enable/disable app features dynamically without redeploying code. This is useful for A/B testing, gradual rollouts, or quick fixes. Below, I’ll explain two approaches, with pros/cons, followed by code snippets in Spring Boot (backend) and Flutter (frontend).

Approach 1: Unified Single Endpoint (Batch Fetch)

Explanation: The backend exposes one endpoint that returns a single JSON object (e.g., a map) containing all feature flags’ states (enabled/disabled). The mobile app fetches this once (e.g., on startup) and caches it locally. This is efficient for apps with many flags, as it minimizes network calls. However, it requires updating the entire map if one flag changes, and the response can grow large.

Pros: Low latency after initial fetch; simple to implement and cache.
Cons: Over-fetches unused flags; harder to target per-user flags without segmentation.

Backend (Spring Boot) Snippet:

@RestController
@RequestMapping("/api")
public class FeatureFlagController {

    @GetMapping("/flags")
    public Map<String, Boolean> getAllFlags() {
        Map<String, Boolean> flags = new HashMap<>();
        flags.put("userProfile", true);
        flags.put("darkMode", false);
        flags.put("chatFeature", true);
        // In production, fetch from DB/cache (e.g., Redis) based on user ID
        return flags;
    }
}
  • This returns: {"userProfile": true, "darkMode": false, "chatFeature": true}.

Frontend (Flutter) Snippet:

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; // For caching

class FeatureFlagService {
  static const String _baseUrl = 'https://your-api.com/api';
  static Map<String, bool> _flags = {};

  static Future<void> fetchFlags() async {
    try {
      final response = await http.get(Uri.parse('$_baseUrl/flags'));
      if (response.statusCode == 200) {
        _flags = Map<String, bool>.from(json.decode(response.body));
        // Cache locally
        final prefs = await SharedPreferences.getInstance();
        await prefs.setString('flags', json.encode(_flags));
      }
    } catch (e) {
      // Fallback to cache
      final prefs = await SharedPreferences.getInstance();
      final cached = prefs.getString('flags');
      if (cached != null) {
        _flags = Map<String, bool>.from(json.decode(cached));
      }
    }
  }

  static bool isEnabled(String flag) => _flags[flag] ?? false;
}

// Usage in widget
class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    FeatureFlagService.fetchFlags(); // Call on app start
  }

  @override
  Widget build(BuildContext context) {
    if (FeatureFlagService.isEnabled('userProfile')) {
      return UserProfileWidget(); // Show if enabled
    }
    return SizedBox.shrink(); // Hide if disabled
  }
}
  • Fetch once in main.dart (e.g., via WidgetsBinding.instance.addPostFrameCallback), then check isEnabled() anywhere.

Approach 2: Per-Component Sub-Endpoints (Lazy/Granular Fetch)

Explanation: Each app component (e.g., a widget or feature module) has its own backend endpoint for its flag(s) and related config (e.g., the API URL for that feature). The mobile app fetches flags lazily—only when the component is about to render or on user interaction. This keeps payloads small and allows fine-grained control (e.g., user-specific flags).

Pros: Scalable for large apps; only fetch what’s needed; easy to add endpoints without touching others.
Cons: More network calls (potential latency); requires careful error handling per fetch.

Backend (Spring Boot) Snippet:

@RestController
@RequestMapping("/api")
public class FeatureFlagController {

    // Example for "userProfile" feature
    @GetMapping("/flags/user-profile")
    public FeatureConfig getUserProfileFlag() {
        // In production, check DB/cache for user-specific flag
        return new FeatureConfig(true, "https://your-api.com/user/profile"); // Enabled + config
    }

    // Example for "chat" feature
    @GetMapping("/flags/chat")
    public FeatureConfig getChatFlag() {
        return new FeatureConfig(false, "https://your-api.com/chat/ws"); // Disabled + config
    }
}

// Simple DTO for flag + config
@Data
@AllArgsConstructor
class FeatureConfig {
    private boolean enabled;
    private String endpoint;
}
  • This returns (for /flags/user-profile): {"enabled": true, "endpoint": "https://your-api.com/user/profile"}.

Frontend (Flutter) Snippet:

import 'package:http/http.dart' as http;
import 'dart:convert';

class FeatureConfig {
  final bool enabled;
  final String endpoint;
  FeatureConfig({required this.enabled, required this.endpoint});
}

class FeatureFlagService {
  static const String _baseUrl = 'https://your-api.com/api';

  static Future<FeatureConfig?> fetchFlag(String feature) async {
    try {
      final response = await http.get(Uri.parse('$_baseUrl/flags/$feature'));
      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        return FeatureConfig(
          enabled: data['enabled'],
          endpoint: data['endpoint'],
        );
      }
    } catch (e) {
      // Handle error, e.g., default to disabled
    }
    return null; // Or default: FeatureConfig(enabled: false, endpoint: '')
  }
}

// Usage in a modular widget
class UserProfileWidget extends StatefulWidget {
  @override
  _UserProfileWidgetState createState() => _UserProfileWidgetState();
}

class _UserProfileWidgetState extends State<UserProfileWidget> {
  FeatureConfig? _config;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadFlag();
  }

  Future<void> _loadFlag() async {
    _config = await FeatureFlagService.fetchFlag('user-profile');
    setState(() => _isLoading = false);
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) return CircularProgressIndicator();
    if (_config?.enabled != true) return SizedBox.shrink(); // Hide if disabled

    return FutureBuilder<String>(
      future: http.get(Uri.parse(_config!.endpoint)).then((r) => r.body), // Use the config endpoint
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text('Profile Data: ${snapshot.data}'); // Render feature
        }
        return SizedBox.shrink();
      },
    );
  }
}
  • Each widget fetches its own flag on init; use a provider (e.g., Riverpod) for caching across instances to avoid redundant calls.

These snippets are minimal—add authentication (e.g., JWT), error handling, and caching (e.g., Hive for Flutter) in production.

  • Prompt-to-Production: How AI is Forcing Us to Build Higher Quality Software
  • Debug Web View Flutter App
  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels

Categories

  • Apps (25)
  • ChatGPT (27)
  • Choosing a Framework (38)
  • Flutter (281)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)

Tags

Algorithms (9) crypto (29) flutterdev (39) General (86) Java (7) QR & Bar Codes (3) Software Dev Choices (33) Spring Boot (1) standards (1) Theme (3) User Authentication & Authorization (9) User Experience (10) Utilities (19) WordPress (11)

Product categories

  • All Technologies (87)
    • Flutter Apps (26)
    • GPT (4)
    • Java (39)
    • Native Android (3)
    • PHP (9)
    • Spring (Boot) / Quarkus (36)
    • Utils (15)
    • Vaadin 24+ (28)
    • Vaadin 8 (1)
  • Apps (18)
    • Employees DB (1)
    • Notes (6)
    • Personal Budget (1)
    • Recipes Book (1)
    • Stuff Organizer (1)
    • To-Do (2)
  • PDF Books (3)
  • Source Code Generators (8)

Recent Posts

  • Prompt-to-Production: How AI is Forcing Us to Build Higher Quality Software
  • Debug Web View Flutter App
  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels

Post Categories

  • Apps (25)
  • ChatGPT (27)
  • Choosing a Framework (38)
  • Flutter (281)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)