DEV Community

zhonghua
zhonghua

Posted on • Edited on

HarmonyOS Sports Project Development: Project Runtime Environment Switcher

Core Technologies of HarmonyOS ## Sports Development

When developing HarmonyOS sports projects, managing different runtime environments (such as development, testing, and production environments) is a common requirement. By properly switching runtime environments, developers can conveniently debug, test, and deploy their applications. This article will introduce how to implement a project runtime environment switcher to help you efficiently manage configurations for different environments in HarmonyOS development.

Preface

In modern software development, environment management is one of the key factors in ensuring the stability and maintainability of applications. Whether it is the development, testing, or production environment, each environment may have different configuration requirements, such as API addresses, log levels, and feature toggles. By implementing a runtime environment switcher, we can easily switch between different environments without modifying the code, thereby improving development efficiency and flexibility.

Image description

I. Design of the Environment Switcher

(I) Environment Configuration Types

To support configurations for different environments, we have defined the EnvironmentConfigs and CurrentEnvironment types.

export type EnvironmentConfigs = Map<string, Map<string, string>>;

export interface CurrentEnvironment {
  name: string;
  configs: Map<string, string>;
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. EnvironmentConfigs: A mapping table where the key is the environment name (e.g., production, development), and the value is the configuration mapping table for that environment.
  2. CurrentEnvironment: Represents the name and configurations of the current environment.

(II) Environment Type Enum

We have defined the supported environment types through an enumeration.

export enum EnvironmentType {
  TYPE_PRODUCTION = "production",
  TYPE_DEVELOP = "develop"
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Enum Type: Two environment types are defined through the enumeration: production environment (production) and development environment (develop). More environment types can be added as needed.

(III) Environment Management Class

The Environment class, which is the core of the entire environment switcher, is responsible for storing environment configurations, loading the saved environment, switching environments, and notifying callbacks.

export class Environment {
  private static instance: Environment;
  private static readonly ENVIRONMENT_STORAGE_KEY = 'current_environment';

  private currentEnvironment?: CurrentEnvironment;
  private environments: EnvironmentConfigs = new Map();
  private preferences: LibPreferencesSync;
  private environmentChangeCallbacks: Array<(newEnvironment: CurrentEnvironment) => void> = [];

  private constructor() {
    this.preferences = new LibPreferencesSync();
  }

  public static getInstance(): Environment {
    if (!Environment.instance) {
      Environment.instance = new Environment();
    }
    return Environment.instance;
  }

  public initEnvironments(evn: EnvironmentConfigs) {
    this.environments = evn;
    this.loadSavedEnvironment();
  }

  private loadSavedEnvironment() {
    if (!IS_PRODUCTION) {
      const savedEnvironmentName = this.preferences.getValue(Environment.ENVIRONMENT_STORAGE_KEY) as string;
      if (savedEnvironmentName && this.environments.has(savedEnvironmentName)) {
        this.currentEnvironment = {
          name: savedEnvironmentName,
          configs: this.environments.get(savedEnvironmentName)!
        };
      } else {
        this.currentEnvironment = {
          name: EnvironmentType.TYPE_DEVELOP,
          configs: this.environments.get(EnvironmentType.TYPE_DEVELOP)!
        };
      }
    } else {
      this.currentEnvironment = {
        name: EnvironmentType.TYPE_PRODUCTION,
        configs: this.environments.get(EnvironmentType.TYPE_PRODUCTION)!
      };
    }
  }

  public switchEnvironment(name: string) {
    const configs = this.environments.get(name);
    if (configs) {
      this.currentEnvironment = { name, configs };
      this.preferences.saveKeyValue(Environment.ENVIRONMENT_STORAGE_KEY, name);
      this.environmentChangeCallbacks.forEach(callback => callback(this.currentEnvironment!));
    }
  }

  public getCurrentEnvironment(): CurrentEnvironment {
    return this.currentEnvironment!;
  }

  public getAllEnvironmentNames(): string[] {
    return Array.from(this.environments.keys());
  }

  public registerEnvironmentChangeCallback(callback: (newEnvironment: CurrentEnvironment) => void) {
    this.environmentChangeCallbacks.push(callback);
  }

  public unregisterEnvironmentChangeCallback(callback: (newEnvironment: CurrentEnvironment) => void) {
    this.environmentChangeCallbacks = this.environmentChangeCallbacks.filter(cb => cb !== callback);
  }
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Singleton Pattern: The getInstance method ensures the global uniqueness of the Environment instance.
  2. Environment Initialization: The initEnvironments method initializes the environment configurations.
  3. Loading Saved Environment: In the loadSavedEnvironment method, the corresponding environment configuration is loaded based on the stored environment name.
  4. Environment Switching: The switchEnvironment method switches the environment and notifies all registered callback functions.
  5. Callback Mechanism: Supports registering and unregistering environment change callbacks to facilitate operations when the environment is switched.

II. Using the Environment Switcher

(I) Environment Switch Dialog

To facilitate environment switching for users, we have implemented an environment switch dialog called EnvironmentDialog.

@CustomDialog
export struct EnvironmentDialog {
  public controller: CustomDialogController;
  private themeManager: ThemeManager = ThemeManager.getInstance();
  private environment: Environment = Environment.getInstance();
  public onEnvironmentChanged?: () => void;

  build() {
    Column() {
      Text('Select Environment')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.themeManager.getTextPrimaryColor())
        .margin({ top: 24, bottom: 16 })

      List() {
        ForEach(this.environment.getAllEnvironmentNames(), (envname: string) => {
          ListItem() {
            Row() {
              Column() {
                Text(envname)
                  .fontSize(16)
                  .fontColor(this.themeManager.getTextPrimaryColor())
                  .margin({ bottom: 4 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)

              if (this.environment.getCurrentEnvironment().name === envname) {
                Image($r('app.media.base_icon_select'))
                  .width(24)
                  .height(24)
                  .margin({ left: 8 })
              }
            }
            .width('100%')
            .padding(16)
            .backgroundColor(this.themeManager.getSurfaceColor())
            .borderRadius(8)
            .onClick(() => {
              this.environment.switchEnvironment(envname);
              this.onEnvironmentChanged?.();
              this.controller.close();
            })
          }
          .margin({ bottom: 8 })
        }, (envname: string) => envname)
      }
      .width('100%')
      .layoutWeight(1)

      Button('Close')
        .width('100%')
        .height(48)
        .backgroundColor(this.themeManager.getPrimaryColor())
        .margin({ top: 16 })
        .onClick(() => {
          this.controller.close();
        })
    }
    .width('90%')
    .padding(16)
    .backgroundColor(this.themeManager.getBackgroundColor())
    .borderRadius(16)
  }
}
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Environment List: The ForEach loop iterates over all environment names and generates a list item for each environment.
  2. Current Environment Indicator: If the current environment matches the list item environment, a selection icon is displayed.
  3. Environment Switching: When a list item is clicked, the switchEnvironment method is called to switch the environment, and the dialog is closed.
  4. Callback Notification: After the environment switch, the onEnvironmentChanged callback function is called to notify the outside that the environment has been switched.

(II) Callback Mechanism for Environment Switching

To perform related operations when the environment is switched, we can implement this by registering callback functions.

const environment = Environment.getInstance();

environment.registerEnvironmentChangeCallback((newEnvironment) => {
  console.log(`Environment switched to: ${newEnvironment.name}`);
  // Perform related operations after the environment switch here, such as reloading configurations, refreshing the interface, etc.
});
Enter fullscreen mode Exit fullscreen mode

Core Points Analysis:

  1. Register Callback: The registerEnvironmentChangeCallback method is used to register a callback function.
  2. Callback Execution: The callback function is automatically called when the environment is switched.

III. Summary

By implementing a project runtime environment switcher, we can easily manage configurations for different environments in HarmonyOS sports projects. The environment switcher not only supports dynamic environment switching but also provides a callback mechanism to facilitate related operations when the environment is switched. In this way, developers can quickly switch between development, testing, and production environments without modifying the code, thereby improving development efficiency and flexibility.

In actual development, you can further extend and optimize the environment switcher based on the specific needs of your project. For example:

  • Support More Environment Types: Add more environment types according to project requirements, such as testing environments, pre-release environments, etc.
  • Dynamic Configuration Loading: Dynamically load environment configurations from a remote server.
  • Integration with Build Tools: Integrate the environment switcher with build tools to support specifying the runtime environment during the build process.

I hope this article provides valuable insights for your HarmonyOS development journey! If you have any questions or suggestions, feel free to reach out and discuss.

Top comments (0)