Skip to content

Feature: Environment Switcher #316

@MaatheusGois

Description

@MaatheusGois

📋 Feature Description

Add an Environment Switcher to DebugSwift that allows developers to quickly toggle between different environments (dev, staging, production) and manage environment-specific configurations.

🎯 Motivation

Most apps support multiple environments with different:

  • API base URLs
  • Feature flags
  • Authentication endpoints
  • Analytics keys
  • Third-party SDK configurations
  • Database connections

Currently, developers switch environments by:

  • Changing build schemes
  • Modifying configuration files
  • Rebuilding the app

This is time-consuming and error-prone. An in-app environment switcher would dramatically improve development workflow.

✨ Proposed Features

Core Functionality

  • Quick Environment Switch: Toggle environments instantly
  • Environment Profiles: Predefined environment configurations
  • Custom Environments: Create and manage custom environments
  • Configuration Management: Store environment-specific settings
  • Auto Data Clear: Optionally clear data on environment switch
  • Environment Indicator: Visual badge showing current environment

Environment Configuration

  • API Base URL: Environment-specific endpoints
  • Feature Flags: Enable/disable features per environment
  • Authentication: Different auth servers
  • Analytics: Environment-specific tracking
  • Database: Separate databases per environment
  • Custom Keys: Any environment-specific values

Advanced Features

  • Environment History: Track environment switches
  • Quick Switch Gesture: Shake or swipe to change environment
  • Environment Comparison: Compare settings across environments
  • Import/Export: Share environment configurations
  • Network Stub Integration: Auto-mock for specific environments
  • Certificate Pinning: Per-environment SSL configurations

🎨 UI/UX Design

Environment Switcher Screen

┌─────────────────────────────────────────┐
│ Environment Switcher          [✓ Active] │
├─────────────────────────────────────────┤
│ Current Environment: Production         │
├─────────────────────────────────────────┤
│ Available Environments                   │
│                                          │
│ ● Development                           │
│   https://dev-api.example.com           │
│   [Select]                              │
│                                          │
│ ◯ Staging                               │
│   https://staging-api.example.com       │
│   [Select]                              │
│                                          │
│ ◯ Production (Current)                  │
│   https://api.example.com               │
│   [✓ Active]                            │
│                                          │
│ ◯ QA                                    │
│   https://qa-api.example.com            │
│   [Select]                              │
├─────────────────────────────────────────┤
│ [+ Add Custom Environment]              │
├─────────────────────────────────────────┤
│ Options                                  │
│ ☑ Clear network history on switch      │
│ ☑ Clear user data on switch            │
│ ☑ Restart app after switch             │
│ ☐ Show environment badge               │
└─────────────────────────────────────────┘

Environment Detail Screen

┌─────────────────────────────────────────┐
│ < Environments    Development      [Edit]│
├─────────────────────────────────────────┤
│ General                                  │
│ Name: Development                       │
│ Icon: 🔧                                │
│ Color: Blue                             │
├─────────────────────────────────────────┤
│ API Configuration                        │
│ Base URL:                               │
│   https://dev-api.example.com           │
│                                          │
│ Auth URL:                               │
│   https://dev-auth.example.com          │
│                                          │
│ GraphQL Endpoint:                       │
│   https://dev-api.example.com/graphql   │
├─────────────────────────────────────────┤
│ Feature Flags                           │
│ ☑ New UI Enabled                        │
│ ☑ Debug Logging                         │
│ ☐ Beta Features                         │
│ ☑ Mock Payments                         │
├─────────────────────────────────────────┤
│ Custom Configuration                     │
│ • Analytics Key: dev-analytics-123      │
│ • Database Name: app_dev.db             │
│ • Mock Data: Enabled                    │
│                                          │
│ [+ Add Custom Key]                      │
├─────────────────────────────────────────┤
│ Actions                                  │
│ [Export Configuration]                   │
│ [Duplicate Environment]                  │
│ [Delete Environment]                     │
└─────────────────────────────────────────┘

Environment Badge (when enabled)

┌────────────────────────────┐
│ App Screen        [DEV 🔧] │  <- Badge in corner
│                            │
│                            │
└────────────────────────────┘

🔧 Technical Implementation

Architecture

public class EnvironmentManager {
    public static let shared = EnvironmentManager()
    
    public var currentEnvironment: Environment {
        didSet {
            handleEnvironmentChange(from: oldValue, to: currentEnvironment)
        }
    }
    
    public var availableEnvironments: [Environment]
    public var onEnvironmentChanged: ((Environment, Environment) -> Void)?
    
    public func switchEnvironment(_ environment: Environment, clearData: Bool = true)
    public func addCustomEnvironment(_ environment: Environment)
    public func exportConfiguration() -> Data
    public func importConfiguration(_ data: Data) throws
}

public struct Environment: Codable, Identifiable {
    public let id: UUID
    public var name: String
    public var icon: String
    public var color: UIColor
    public var configuration: EnvironmentConfiguration
    
    public init(
        name: String,
        icon: String = "🌍",
        color: UIColor = .blue,
        configuration: EnvironmentConfiguration
    )
}

public struct EnvironmentConfiguration: Codable {
    public var apiBaseURL: String
    public var authURL: String?
    public var graphQLEndpoint: String?
    public var featureFlags: [String: Bool]
    public var customValues: [String: String]
    
    // Convenience accessors
    public func value(forKey key: String) -> String?
    public func bool(forKey key: String) -> Bool
    public func isFeatureEnabled(_ feature: String) -> Bool
}

Usage in App

// Define environments in AppDelegate or App struct
func setupEnvironments() {
    let development = Environment(
        name: "Development",
        icon: "🔧",
        color: .blue,
        configuration: EnvironmentConfiguration(
            apiBaseURL: "https://dev-api.example.com",
            authURL: "https://dev-auth.example.com",
            featureFlags: [
                "newUI": true,
                "debugLogging": true,
                "mockPayments": true
            ],
            customValues: [
                "analyticsKey": "dev-analytics-123",
                "databaseName": "app_dev.db"
            ]
        )
    )
    
    let staging = Environment(
        name: "Staging",
        icon: "🧪",
        color: .orange,
        configuration: EnvironmentConfiguration(
            apiBaseURL: "https://staging-api.example.com",
            authURL: "https://staging-auth.example.com",
            featureFlags: [
                "newUI": true,
                "debugLogging": false,
                "mockPayments": false
            ],
            customValues: [
                "analyticsKey": "staging-analytics-456"
            ]
        )
    )
    
    let production = Environment(
        name: "Production",
        icon: "🚀",
        color: .green,
        configuration: EnvironmentConfiguration(
            apiBaseURL: "https://api.example.com",
            authURL: "https://auth.example.com",
            featureFlags: [:],
            customValues: [:]
        )
    )
    
    DebugSwift.Environment.availableEnvironments = [
        development, staging, production
    ]
    
    DebugSwift.Environment.currentEnvironment = development
}

// Access environment configuration
let apiURL = DebugSwift.Environment.current.configuration.apiBaseURL
let isFeatureEnabled = DebugSwift.Environment.current.configuration.isFeatureEnabled("newUI")

// Listen for environment changes
DebugSwift.Environment.onEnvironmentChanged = { old, new in
    print("Switched from \(old.name) to \(new.name)")
    
    // Clear network history
    DebugSwift.Network.shared.clearNetworkHistory()
    
    // Reconnect services
    APIClient.shared.reconnect()
    
    // Optionally restart app
    if DebugSwift.Environment.restartOnSwitch {
        exit(0) // System will restart the app
    }
}

Environment Badge

class EnvironmentBadgeView: UIView {
    private let label = UILabel()
    
    func show(environment: Environment) {
        label.text = "\(environment.icon) \(environment.name)"
        backgroundColor = environment.color.withAlphaComponent(0.8)
        
        // Position in top-right corner
        if let window = UIApplication.shared.windows.first {
            window.addSubview(self)
            self.frame = CGRect(
                x: window.bounds.width - 100,
                y: window.safeAreaInsets.top,
                width: 90,
                height: 30
            )
        }
    }
}

Environment Persistence

extension EnvironmentManager {
    private let currentEnvironmentKey = "debugswift.current.environment"
    
    func saveCurrentEnvironment() {
        if let data = try? JSONEncoder().encode(currentEnvironment) {
            UserDefaults.standard.set(data, forKey: currentEnvironmentKey)
        }
    }
    
    func loadCurrentEnvironment() {
        guard let data = UserDefaults.standard.data(forKey: currentEnvironmentKey),
              let environment = try? JSONDecoder().decode(Environment.self, from: data) else {
            return
        }
        currentEnvironment = environment
    }
}

📝 Implementation Checklist

Phase 1: Basic Environment Switching

  • Environment data structure
  • Environment storage and persistence
  • Basic environment switcher UI
  • Environment configuration management
  • Current environment indicator

Phase 2: Enhanced Features

  • Custom environment creation
  • Feature flags management
  • Auto data clear on switch
  • Environment badge overlay
  • Import/export configurations

Phase 3: Advanced Features

  • Quick switch gesture (shake)
  • Environment comparison view
  • History tracking
  • Network integration (auto-clear)
  • Certificate pinning per environment
  • Environment validation

🧪 Testing Requirements

  • Environment switching tests
  • Configuration persistence tests
  • Feature flag tests
  • Import/export tests
  • Data clearing tests
  • UI state restoration tests

📚 Documentation

### Environment Switcher
- Quick toggle between dev/staging/production
- Environment-specific configurations
- Feature flags management
- Auto data clearing on switch
- Custom environments

// Setup environments
DebugSwift.Environment.configure(
    environments: [development, staging, production],
    default: development
)

// Access current environment
let apiURL = DebugSwift.Environment.current.apiBaseURL

// Listen for changes
DebugSwift.Environment.onEnvironmentChanged = { old, new in
    // Handle environment switch
}

🎯 Success Criteria

  • Switch environments without rebuilding
  • Manage environment-specific configurations
  • Clear data automatically on switch
  • Visual environment indicator
  • Import/export configurations
  • Feature flags per environment
  • Minimal setup required
  • Persist environment across launches

📊 Priority

High - Major developer productivity improvement.

🏷️ Labels

enhancement, feature, app-tools, developer-experience, high-priority

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions