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()afterput()—changes may be lost if the app is killed before the buffer is written. -
Trying to read a key before initialization—
getPreferencesSyncorgetPreferencesmust be called first. -
Forgetting to handle errors—always check for
BusinessErrorin 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)
}
}
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.


Top comments (0)