DEV Community

HarmonyOS
HarmonyOS

Posted on

When using @State to modify a singleton instance, the UI cannot be refreshed.

Read the original article:When using @State to modify a singleton instance, the UI cannot be refreshed

Context

When a singleton class instance is decorated with @State, changing its internal properties does not trigger a UI refresh.
For example: in Page2, after toggling the color and returning to the Index page, the UI does not update accordingly.

Description

  • @State allows UI to refresh when the state variable itself changes.
  • However, it does not track property changes inside a class instance.
  • To observe inner property changes, @Observed is needed.
  • Alternatively, AppStorage can provide application-level state sharing, and variables with @StorageLink will synchronize automatically.

Solution / Approach

Option 1: Use @Observed

  • Decorate the singleton class with @Observed.
  • This makes @State capable of observing property changes within the instance.
@Observed
export class VoiceReadHelper {
  private static instance: VoiceReadHelper | null = null
  isSpeaking = false

  private constructor() { }

  static getInstance(): VoiceReadHelper {
    if (VoiceReadHelper.instance == null) {
      VoiceReadHelper.instance = new VoiceReadHelper()
    }
    return VoiceReadHelper.instance
  }
}
Enter fullscreen mode Exit fullscreen mode

Option 2: Use AppStorage + @StorageLink

  1. Save the singleton instance into AppStorage when created.
  2. Use @StorageLink in pages to bind the shared instance for two-way synchronization.
// VoiceReadHelper.ets
export class VoiceReadHelper {
  private static instance: VoiceReadHelper | null = null
  isSpeaking = false

  private constructor() { }

  static getInstance(): VoiceReadHelper {
    if (VoiceReadHelper.instance == null) {
      VoiceReadHelper.instance = new VoiceReadHelper()
      AppStorage.setOrCreate('VoiceReadHelper', VoiceReadHelper.instance)
    }
    return VoiceReadHelper.instance
  }
}
Copy codeCopy code
// Index.ets
import { VoiceReadHelper } from './VoiceReadHelper'

@Entry
@Component
struct Index {
  @StorageLink('voiceReadHelper') voiceReadHelper: VoiceReadHelper = VoiceReadHelper.getInstance()

  build() {
    Column() {
      Text()
        .width(100)
        .height(100)
        .backgroundColor(this.voiceReadHelper.isSpeaking ? '#61CFBE' : '#8981F7')
        .borderRadius(8)
        .margin({ bottom: 100 })
        .onClick(() => {
          this.voiceReadHelper.isSpeaking = !this.voiceReadHelper.isSpeaking
        })

      Text('Go to Page2').onClick(() => {
        this.getUIContext().getRouter().pushUrl({ url: 'pages/Page2' });
      })
    }
    .height('100%')
    .width('100%')
  }
}
Copy codeCopy code
// Page2.ets
import { VoiceReadHelper } from './VoiceReadHelper'

@Entry
@Component
struct Page2 {
  @StorageLink('voiceReadHelper') voiceReadHelper: VoiceReadHelper = VoiceReadHelper.getInstance()

  build() {
    Column() {
      Text()
        .width(100)
        .height(100)
        .backgroundColor(this.voiceReadHelper.isSpeaking ? '#61CFBE' : '#8981F7')
        .borderRadius(8)
        .margin({ bottom: 100 })
        .onClick(() => {
          this.voiceReadHelper.isSpeaking = !this.voiceReadHelper.isSpeaking
        })

      Text('This is Page2')
    }
    .height('100%')
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • @State cannot observe changes to properties inside a class instance.
  • Use @Observed to make properties inside the class observable.
  • Use AppStorage with @StorageLink for application-wide state sharing and automatic synchronization across pages.
  • Applicable for:
    • Theme switching
    • Configuration management (e.g., user preferences)
    • Message/notification systems

Additional Resources

https://developer.huawei.com/consumer/en/doc/harmonyos-guides/arkts-state-management-overview

https://developer.huawei.com/consumer/en/doc/harmonyos-guides/arkts-observed-and-objectlink

Written by Mehmet Algul

Top comments (0)