DEV Community

Cover image for Dark Mode Resources, Caching Examples, Focus Acquisition, Cross-Module Navigation, Tabs Component Issues
kouwei qing
kouwei qing

Posted on

Dark Mode Resources, Caching Examples, Focus Acquisition, Cross-Module Navigation, Tabs Component Issues

[Daily HarmonyOS Next Knowledge] Dark Mode Resources, Caching Examples, Focus Acquisition, Cross-Module Navigation, Tabs Component Issues

1. Does HarmonyOS dark mode adaptation require preparing a separate set of resources?

For dark mode adaptation, should another set of resources be prepared?

There are two scenarios:

  1. The app follows the system to switch between dark mode and non-dark mode:

    • Color adaptation: Customize two sets of color resources (resources/dark/element/color.json and resources/base/element/color.json), and load color resource keys via $r. System-provided resources can also be used directly, which are layered parameters. The same resource ID has different values under different configurations (e.g., device type, light/dark mode). Using system resources allows developers to create apps with consistent visual styles without customizing two color resource sets, as colors will automatically switch between light and dark modes. https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-resource-manager-V5#resource9
    • Image resources: Use resource qualifier directories. Similar to color adaptation, place dark-mode images with the same name in the dark/media directory, and load image resource keys via $r. The system will automatically load corresponding values when switching between light and dark modes. For simple SVG icons, the fillColor attribute can be combined with system resources to change the drawing color, achieving dark mode adaptation without two sets of image resources.
  2. The app does not follow the system and switches between dark mode and non-dark mode internally:

2. HarmonyOS caching demo verification?

Reference code:

import { promptAction } from '@kit.ArkUI';
import { preferences } from '@kit.ArkData';

let context = getContext(this);
let preference: preferences.Preferences;
let preferenceTemp: preferences.Preferences;

// Object class
export class ImageModel {
  constructor(id: string, tokenInfo: string, imgInfo: string, urlInfo: string) {
    this.id = id
    this.token = tokenInfo
    this.img = imgInfo
    this.url = urlInfo
  }
  id?: string = '';
  token: string = '';
  img: string = '';
  url: string = ''
}

// Preference utility class
class PreferenceModel {

  // Initialization
  async getPreferencesFromStorage() {
    try {
      preference = await preferences.getPreferences(context,'ImageUrlData.db');
    } catch (err) {
      console.error('[PreferenceModel]', `Failed to get preferences, Cause: ${err}`);
    }
  }

  // Delete preferences
  async deletePreferences() {
    try {
      await preferences.deletePreferences(context, 'ImageUrlData.db');
    } catch(err) {
      console.error('[PreferenceModel]', `Failed to delete preferences, Cause: ${err}`);
    };
    preference = preferenceTemp;
    this.showToastMessage('删除数据库成功');
  }

  // Insert value
  async putPreference(Image:string, ImageInfo: ImageModel) {
    if (!preference) {
      await this.getPreferencesFromStorage();
    }
    try {
      await preference.put(Image, JSON.stringify(ImageInfo));
    } catch (err) {
      console.error('[PreferenceModel]', `Failed to put value, Cause: ${err}`);
    }
    await preference.flush();
  }

  // Get value
  async getPreference(Image: string) {
    let ImageInfo = '';
    if (!preference) {
      await this.getPreferencesFromStorage();
    }
    try {
      ImageInfo = (await preference.get(Image, '')).toString();
    } catch (err) {
      console.error('[PreferenceModel]', `Failed to get value, Cause: ${err}`);
    }
    if (ImageInfo === '') {
      this.showToastMessage('数据为空,请先写入数据');
      return;
    }
    this.showToastMessage('写入数据成功');
    return JSON.parse(ImageInfo);
  }

  async getImageData(Image: string) {
    return await this.getPreference(Image);
  }

  // Write data
  writeData(Image:string, ImageInfo: ImageModel) {
    this.putPreference(Image, ImageInfo);
    this.showToastMessage('写入数据成功');
  }

  showToastMessage(message: string) {
    promptAction.showToast({
      message: message,
      duration: 3000
    });
  };
}

/**
 * Preference operation
 */
export class LocalDataManager {
  preferenceModel = new PreferenceModel()
  private static localDataManager: LocalDataManager;

  private constructor() {
    this.preferenceModel.getPreferencesFromStorage()
  }

  static instance() {
    if (!LocalDataManager.localDataManager) {
      LocalDataManager.localDataManager = new LocalDataManager();
    }
    return LocalDataManager.localDataManager;
  }

  async queryImageInfo(Image:string) {
    return await this.preferenceModel.getImageData(Image)
  }

  insertImageInfo(Image:string, ImageInfo: ImageModel) {
    this.preferenceModel.writeData(Image, ImageInfo)
    return Image.length
  }

  updateImageInfo(Image:string, ImageInfo: ImageModel) {
    this.preferenceModel.writeData(Image, ImageInfo)
    return Image.length
  }

  deleteImageInfo() {
    this.preferenceModel.deletePreferences()
  }
}

// Use preferences for caching
@Entry
@Component
struct NeiMengGuNongJi {
  private localDataManager: LocalDataManager = LocalDataManager.instance();
  @State token_Info: string = ''
  private img: string = 'image00'
  private url: string = 'url000'
  @State num: number = 0
  @State keyInfo: string = ''
  @State imageData: ImageModel = new ImageModel('','','','')

  build() {
    Column() {
      Button('点击存入数据库')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(()=> {
          for (let i = 1; i <= 10; i++) {
            let tokenInfo  = "token00"+i
            let image = this.img+i
            let urlInfo = this.url+i
            let ImageInfo = new ImageModel("", tokenInfo, image, urlInfo);
            this.localDataManager.insertImageInfo(image, ImageInfo)
          }
        })
      TextInput({placeholder:'输入key值:例如image001'})
        .type(InputType.Normal)
        .onChange((key: string) => {
          this.keyInfo = key
        })
      Button('通过key值:'+this.keyInfo+", 获取value值")
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .onClick(async()=> {
          if(this.keyInfo !== ''){
            this.imageData = await this.localDataManager.queryImageInfo(this.keyInfo) as ImageModel
            console.info("imageData="+JSON.stringify(this.imageData))
          }
        })
      Text("key: "+this.keyInfo+", value: " + (this.imageData.token === ''? '' : JSON.stringify(this.imageData))).fontSize(40)
    }
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Why does focusControl.requestFocus(id: string) lose focus after navigating back to the page in HarmonyOS?

When using focusControl.requestFocus(id: string), the focus is lost after navigating back to the page.

Solution: Mark a status in onAppear when the page returns to maintain focus.

4. Can HarmonyOS router navigate between different modules?

The entry module depends on the feature module basecore, which has a BaseIndex.ets in its pages. Using router.pushUrl({url:‘pages/BaseIndex’}, router.RouterMode.Standard ,(err)=>{}) in the entry module has no effect.

  1. A feature is an ability-type module, and each feature maintains its own routing stack. Navigating to other pages requires using startAbility, while router is for intra-page navigation. These cannot be mixed.
  2. To avoid cross-module navigation, consider a single UIAbility with multiple Hsps.

For cross-module navigation, refer to the document:

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/uiability-intra-device-interaction-V5

When an app contains multiple UIAbilities, there may be scenarios where one UIAbility starts another. For example, starting a payment UIAbility from the entry UIAbility in a payment app.

Suppose the app has two UIAbilities: EntryAbility and FuncAbility (in the same or different modules). To start FuncAbility from EntryAbility's page:

In EntryAbility, use startAbility() with a want object as the entry parameter, including bundleName (target app's bundle name), abilityName (target ability name), moduleName (if target UIAbility is in a different module), and parameters (custom parameters). The context can be obtained as shown:

import { common, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG: string = '[Page_UIAbilityComponentsInteractive]';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
  private context = getContext(this) as common.UIAbilityContext;

  build() {
    Column() {
      //...
      List({ initialIndex: 0 }) {
        ListItem() {
          Row() {
            //...
          }
          .onClick(() => {
            let wantInfo: Want = {
              deviceId: '', // Empty deviceId means local device
              bundleName: 'com.samples.stagemodelabilitydevelop',
              moduleName: 'entry', // moduleName is optional
              abilityName: 'FuncAbilityA',
              parameters: {
                // Custom information
                info: '来自EntryAbility Page_UIAbilityComponentsInteractive页面'
              },
            };
            this.context.startAbility(wantInfo).then(() => {
              hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
            }).catch((error: BusinessError) => {
              hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
            });
          })
        }
        //...
      }
      //...
    }
    //...
  }
}
Enter fullscreen mode Exit fullscreen mode

In FuncAbility's onCreate() or onNewWant() lifecycle callback, receive parameters from EntryAbility:

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';

export default class FuncAbilityA extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // Receive parameters from the calling UIAbility
    let funcAbilityWant = want;
    let info = funcAbilityWant?.parameters?.info;
  }
  //...
}
Enter fullscreen mode Exit fullscreen mode

After completing operations in FuncAbility, stop the current UIAbility instance using terminateSelf():

import { common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = '[Page_FromStageModel]';
const DOMAIN_NUMBER: number = 0xFF00;

@Entry
@Component
struct Page_FromStageModel {
  build() {
    Column() {
      //...
      Button('FuncAbilityB')
        .onClick(() => {
          let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
          context.terminateSelf((err) => {
            if (err.code) {
              hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self. Code is ${err.code}, message is ${err.message}`);
              return;
            }
          });
        })
    }
    //...
  }
}
Enter fullscreen mode Exit fullscreen mode

To close all UIAbility instances of the app, use ApplicationContext.killAllProcesses().

5. Issues with TabContent in HarmonyOS Tabs component

Is the content of TabContent in the Tabs component automatically centered?

TabContent content is centered by default. Solutions:

  1. Adjust the child component's height freely via the margin attribute (suitable when child components do not change).
  2. Add a Column container in TabContent and configure its attributes to achieve the layout.

Top comments (0)