DEV Community

HarmonyOS
HarmonyOS

Posted on

Building a Persistent Dark Mode in HarmonyOS with ArkTS Preferences

Read the original article:Building a Persistent Dark Mode in HarmonyOS with ArkTS Preferences

Problem Description

Many HarmonyOS applications need to store small amounts of data locally—for example, user preferences, recently viewed items, or simple state flags.
Developers often rely on in-memory variables, but those values disappear when the app restarts.
We need a persistent and lightweight way to save and retrieve key–value pairs.

Background Knowledge

HarmonyOS provides the @kit.ArkData (new package name) module, which contains the preferences API for simple key–value storage.

  • Data is stored as key–value pairs (string, number, boolean, etc.) inside a preferences file on the device.
  • You can access values using synchronous or asynchronous methods.
  • flush() ensures that changes are physically written to disk.

Troubleshooting Process

Common pitfalls when working with preferences:

  • Not calling or awaiting flush() after put()—changes may be lost if the app is killed before the buffer is written.
  • Trying to read a key before initializationgetPreferencesSync or getPreferences must be called first.
  • Forgetting to handle errors—always check for BusinessError in callbacks.

Analysis Conclusion

By using the preferences API with proper initialization and explicit flush(), you ensure that user settings (like dark mode) persist between app launches.
This approach is lightweight and avoids race conditions when writing to storage.

Solution

Below is a single .ets file that implements a persistent Dark Mode toggle using @kit.ArkData preferences.
It stores the darkMode boolean value, updates the UI instantly, and remembers the setting after app restarts.

import { preferences } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';

class PreferencesUtil {
  private static instance: PreferencesUtil | null = null;
  private dataPreferences: preferences.Preferences | null = null;
  private readonly PREFERENCES_NAME = 'userSettings';
  private readonly DARK_MODE_KEY = 'IsDarkMode';

  private constructor() {}

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

  async initPreferences(context: common.UIAbilityContext): Promise<void> {
    const options: preferences.Options = { name: this.PREFERENCES_NAME };
    this.dataPreferences = preferences.getPreferencesSync(context, options);
    console.info('Preferences initialized');
  }

  getDarkModePreference(): boolean {
    if (!this.dataPreferences) return false;
    try {
      return this.dataPreferences.getSync(this.DARK_MODE_KEY, false) as boolean;
    } catch (err) {
      console.error('Failed to read preference:', err);
      return false;
    }
  }

  saveDarkModePreference(isDarkMode: boolean, callback?: () => void): void {
    if (!this.dataPreferences) return;
    try {
      this.dataPreferences.putSync(this.DARK_MODE_KEY, isDarkMode);
      this.dataPreferences.flush((err: BusinessError) => {
        if (err) {
          console.error(`Flush failed: ${err.code} - ${err.message}`);
        } else {
          console.info('DarkMode saved:', isDarkMode);
          callback?.();
        }
      });
    } catch (err) {
      console.error('Failed to save preference:', err);
    }
  }

  toggleDarkMode(callback?: (newState: boolean) => void): void {
    const newState = !this.getDarkModePreference();
    this.saveDarkModePreference(newState, () => callback?.(newState));
  }
}

@Entry
@Component
struct DarkModePage {
  @State isDarkMode: boolean = false;
  private preferencesUtil: PreferencesUtil = PreferencesUtil.getInstance();

  async aboutToAppear() {
    const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
    await this.preferencesUtil.initPreferences(ctx);
    this.isDarkMode = this.preferencesUtil.getDarkModePreference();
  }

  private toggleTheme() {
    this.preferencesUtil.toggleDarkMode((newState: boolean) => {
      this.isDarkMode = newState;
    });
  }

  build() {
    Column() {
      Text(`Dark Mode is ${this.isDarkMode ? 'ON' : 'OFF'}`)
        .fontSize(20)
        .margin({ bottom: 20 })
        .fontColor(this.isDarkMode ? '#FFFFFF' : '#000000')

      Button(this.isDarkMode ? 'Turn OFF' : 'Turn ON')
        .fontSize(18)
        .fontColor(this.isDarkMode ? '#000000' : '#FFFFFF')
        .backgroundColor(this.isDarkMode ? '#BBBBBB' : '#333333')
        .borderRadius(20)
        .padding({ left: 16, right: 16, top: 10, bottom: 10 })
        .onClick(() => this.toggleTheme())
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.isDarkMode ? '#000000' : '#FFFFFF')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}
Enter fullscreen mode Exit fullscreen mode

Verification Result

Launch the app and press Turn ON → background switches to black, text becomes white.

Close and relaunch the app → Dark Mode state is remembered.

2.png 3.png

Related Documents or Links

https://developer.huawei.com/consumer/en/doc/harmonyos-guides/data-persistence-by-preferences#preferences-constraints

Written by Hatice Akyel

Top comments (0)