This topic uses stock apps as a typical example to detail how the feature of one-time development for multi-device deployment is applied in actual development. The following describes how to develop two typical pages (stock watchlist page and individual stock details page) on bar phones, bi-fold phones, tri-fold phones, widescreen foldable phones, tablets, and PCs. The following practices will focus on these product forms and provide examples from the perspectives of UX design, project management, and page development for mobile devices and PCs, helping you develop stock apps.
Before reading this topic, you are advised to learn the ArkUI framework and the capabilities of one-time development for multi-device deployment.
The following practices will focus on these product forms and provide examples from the perspectives of UX design, project management, and page development, helping you develop stock apps that can be deployed on multiple devices.
Stock apps are designed to enable users to handle financial services more conveniently. Common scenarios include wealth management, stock, and fund. Core scenarios include data viewing and stock transaction.
Stock apps have the following characteristics:
When used across devices, stock apps need to ensure basic user experience and access to all necessary functions when handling financial services for users, and improve the interaction efficiency on large-screen devices.
For details about the UX design of stock apps, see Self-Selected Stock in the multi-device responsive design guide for financial services. See the following figure for reference.

You are advised to create a template project after learning how to create and configure a layered architecture project by referring to Cross-Device Project Deployment and Release. Then, modify the project based on the development requirements of the stock app to ensure that the project architecture meets the actual service requirements.
When creating a project, you may encounter the problem of how to divide the project structure directory. Considering the reusability and maintainability of the project, this topic uses the stock app as an example to provide a recommended solution for reference.
The HarmonyOS project architecture uses a layered architecture consisting of three layers: product customization layer, basic feature layer, and common capability layer. It provides a clear, efficient, and scalable design architecture. For details, see Layered Architecture Design.
The stock app organizes its directories based on the three-layer project architecture of common (common capability layer), features (basic feature layer), and products (product customization layer). The features layer is divided into three independent modules based on service functions: stockdeal (including the stock purchase dialog, K-line chart, and transaction details components), stockdetail (including the stock details page, multi-windows entry, and information table), and stockmarket (including the market list component). Base capabilities (base), which are referenced by different pages, such as common constants, media playback tools, and window management tools, are included in the common layer. The products layer includes the default and pc modules, which customizes the standard startup process and the entry capability in collaborative scenarios.
├──commons
│ └──base/src/main
│ └──ets
│ ├──baseviews // Common view components
│ ├──models // Common data models
│ └──utils // Common utilities
├──features
│ ├──stockdeal/src/main
│ │ └──ets
│ │ ├──chartmodels // Chart models
│ │ ├──models // Stock transaction data models
│ │ ├──viewmodels // Stock transaction view models
│ │ └──views // Stock trading view components
│ ├──stockdetail/src/main
│ │ ├──ets
│ │ │ ├──models // Stock details data models
│ │ │ ├──viewmodels // Stock details view models
│ │ │ ├──pages // Stock details page
│ │ │ └──views // Stock details view components
│ └──stockmarket/src/main
│ └──ets
│ ├──models // Stock market data models
│ ├──viewmodels // Stock market view models
│ └──views // Stock market view components
└──products
├──default/src/main
│ ├──ets
│ │ ├──entryability // App entry on the mobile client
│ │ ├──entrybackupability // Backup entry class
│ │ ├──pages // Home page on the mobile client
│ │ ├──splitScreenAbility // Split-screen ability
│ │ └──splitScreenBackupAbility // Split-screen backup ability
│ └──resources // Static resources
└──pc/src/main
├──ets
│ ├──pages // PC pages
│ ├──pcability // PC program entry
│ └──pcbackupability // PC program backup entry
└──resources // Static resources Window Mode
Multi-Device Stock Page provides the full-screen, split-screen, floating window, and freeform window modes for different devices. For details, see Window Mode. The split-screen and floating window modes can be easily accessed through the system. By monitoring window size changes, the app refreshes the UI based on the breakpoints to automatically apply layouts suitable for distinct window modes, such as the full-screen, split-screen, floating window, and freeform window modes.
Multitask view, which is accessed via system UI components, allows an app to run in multiple windows at the same time. For details, see Feature Development: Multi-Stock Price Comparison in Stock Details Page.
Window Orientation
Set setPreferredOrientation() to enable the app to automatically rotate with the sensor. On bar-type phones, only the portrait mode is recommended. On large screens, such as bi-fold (unfolded), tri-fold (G state), and tablets, rotation in four orientations controlled by the rotation switch in Control Panel is recommended. In a stock app, you are advised to set this attribute to FOLLOW_DESKTOP in the module.json5 file. For details, see Window Orientation.
Immersive Window
You can implement the immersive windows in different modes (full-screen, split-screen, and floating window modes) based on the UX design. For details, see Immersive Window. You can implement the immersive windows in full-screen, split-screen, and floating window modes using setWindowLayoutFullscreen() and dynamically avoid the safe area.
Horizontal/Vertical Breakpoint | sm/md | sm/lg | md | lg |
|---|---|---|---|---|
Stock watchlist page |
|
|
|
|
UI Development
No. | Description | Implementation |
|---|---|---|
1 | Bottom/Side tab | The responsive component HdsTabs is used. In addition, the Tabs component is used on devices with earlier API versions. |
2 | Stock index | The last component is fixed. The List component is used to implement the extension capability for other components. As the device width increases, a page can show more content. |
3 | Stock list - toolbar | The Blank component is added between the text and feature buttons to implement the stretching capability. |
4 | Stock list | The List component is used to set a fixed width and the Scroll component is used to enable the stock list to be scrollable. In addition, different justifyContent values can be set for various columns to implement distinct alignment modes. |
Horizontal/Vertical Breakpoint | sm/md | sm/lg | md | lg, xl |
|---|---|---|---|---|
Individual stock details page |
|
|
|
|
UI Development
No. | Description | How to Implement |
|---|---|---|
1 | Transaction operation bar | The layoutWeight attribute is set for the transaction button and the Blank component is used with breakpoints to implement adaptive stretching of the button. |
2 | Title | The title is displayed in the center and the operation buttons are justified. The Blank component is used for the blank space to implement the stretching capability of the adaptive layout. |
3 | Trend list | The Grid component is used with breakpoints to control the number of columns displayed at different breakpoints. The list can change from two columns to multiple columns adaptively. |
4 | Middle tab | The space attribute of the List component is used with breakpoints to control the spacing between list items at different breakpoints. |
5 | Line chart and bar chart | The layoutWeight attribute is used to implement the stretching capability. |
6 | Discussion tab | The space attribute of the List component is used with breakpoints to control the spacing between list items at different breakpoints. |
Feature Development: Multi-Stock Price Comparison
The app uses the system component MultiWindowEntryInAPP to configure its bundleName and UIAbility to be started (other apps cannot be started). Tapping the component displays a split-screen view for dual-stock comparison; tapping the component again based on the split-screen view displays a multitask view for triple-stock comparison.
The following uses Mate X5 (a foldable phone) as an example to show the display effect in split-screen and multitask views.
- | Split-Screen View for Dual-Stock Comparison | Multitask View for Triple-Stock Comparison |
|---|---|---|
Individual stock details page - stock comparison |
|
|
Constraints
The MultiWindowEntryInAPP component is applicable only when the current device and its screen state support the multitask view. Currently, the following device types support the multitask view:
For other device types or states, this component will not respond to any taps.
You are advised to set a close button in the upper left corner of the split window. In this example, a back button is used, which is a specific requirement for returning to the previous page in the stock comparison scenario.
How to Develop
The app uses the MultiWindowEntryInAPP component to proactively enter the split-screen view or multitask view. The development procedure is as follows:
import { MultiWindowEntryInAPP } from '@kit.UIDesignKit';
import { TextModifier } from '@kit.ArkUI';
import { Want } from '@kit.AbilityKit'; @Component
export struct MultiWindowEntryComponent {
@Link textModifier: TextModifier;
@Link want: Want;
@State isShowMultiWindowEntry: boolean = false;
// ...
build() {
Row() {
MultiWindowEntryInAPP({
want: this.want,
isShowSubtitle: false,
multiWindowEntryInAPPStyle: {
iconOptions: {
iconSize: 24,
iconColor: $r('sys.color.font_primary'),
iconWeight: FontWeight.Normal,
backgroundColor: $r('sys.color.comp_background_tertiary')
},
subtitleOptions: {
modifier: this.textModifier.fontColor($r('app.color.text_primary_color'))
}
}
})
.id('MultiWindowEntryInAPP')
}
.visibility(this.isShowMultiWindowEntry ? Visibility.Visible : Visibility.None)
}
} import { MultiWindowEntryComponent } from './MultiWindowEntryComponent';
@Component
export struct TopTitleBar {
// ...
@State textModifier: TextModifier = new TextModifier();
@State splitScreenWant: Want = {
// Modify the bundleName, moduleName and abilityName of the current application,
// and launch the UIAbility within the application.
bundleName: 'com.example.multiticketclass',
moduleName: 'multiticketclassdefaultsample',
abilityName: 'SplitScreenAbility',
};
// ...
build() {
Row() {
// ...
// The area displayed by the icon on the right side
Row({ space: 16 }) {
// split screen
Row() {
MultiWindowEntryComponent({
textModifier: this.textModifier,
want: this.splitScreenWant
})
}
.visibility(this.getMultiWindowVisibility())
}
}
// ...
}
} Window Routing Solution for MultiWindowEntryInAPP
You are advised to use the app-level multi-instance to manage the routing of split-screen pages. The following table describes the differences between page-level multi-instance and app-level multi-instance. For split-screen routing management in the stock comparison scenario, the app-level multi-instance is used.
Scenario | Route Stack Feature | Route Reconstruction Required | Core Solutions |
|---|---|---|---|
Page-level multi-instance | A UIAbility is created when the page loads, and the route stack is reconstructed based on the current node. | Yes | The route table is generated based on the current route node, and the route solution requires manual configuration. |
App-level multi-instance (recommended) | Each UIAbility creates an independent and identical route stack. | No | An independent route stack (with the same route table) is created upon a window startup. |
Route Stack Pop-out Solution for MultiWindowEntryInAPP
let eventData: emitter.EventData = {
data: {
'isStart': 1,
'id': 1
}
};
let innerEvent: emitter.InnerEvent = {
eventId: 1,
priority: emitter.EventPriority.HIGH
};
export default class SplitScreenAbility extends UIAbility {
// ...
onCreate(): void {
// ...
emitter.emit(innerEvent, eventData);
}
onNewWant(): void {
// ...
emitter.emit(innerEvent, eventData);
}
// ...
} @Component
export struct TopTitleBar {
// ...
private innerEvent: emitter.InnerEvent = { eventId: 1 };
private callBack: Callback<emitter.EventData> = (eventData: emitter.EventData) => {
Logger.info(`eventData:${eventData}`);
if (this.pageInfos?.pop) {
this.pageInfos.pop();
}
};
aboutToAppear(): void {
this.viewModel.loadData();
if (this.context.abilityInfo.name === 'MultiticketclassdefaultAbility') {
emitter.on(this.innerEvent, this.callBack);
}
}
aboutToDisappear(): void {
emitter.off(this.innerEvent.eventId, this.callBack);
}
// ...
}
} Button Visibility Policy for MultiWindowEntryInAPP
Hide the split-screen button on devices that do not support multitask view for the MultiWindowEntryInAPP component. The main logic of the solution is as follows:
public onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => {
this.mainWindowInfo.windowSize = windowSize;
if (this.uiContext) {
this.mainWindowInfo.widthBp = this.uiContext.getWindowWidthBreakpoint();
this.mainWindowInfo.heightBp = this.uiContext.getWindowHeightBreakpoint();
}
};
// ...
updateWindowInfo(): void {
try {
// ...
// Register for window size change monitoring, update window size and width/height breakpoint.
this.mainWindow.on('windowSizeChange', this.onWindowSizeChange);
// ...
AppStorage.setOrCreate(KEY_MAIN_WINDOW_INFO, this.mainWindowInfo);
} catch (error) {
let err = error as BusinessError;
Logger.error(`Failed to update window info. Code: ${err.code}, message: ${err.message}`);
}
} private timerId: number = -1;
aboutToAppear(): void {
this.checkMultiWindowEnabled();
}
aboutToDisappear(): void {
if (this.timerId !== -1) {
clearTimeout(this.timerId);
}
}
private checkMultiWindowEnabled(): void {
this.timerId = setTimeout(() => {
const frameNode = this.getUIContext()?.getFrameNodeById('MultiWindowEntryInAPP');
const inspectorInfo = JSON.stringify(frameNode?.getInspectorInfo() as InspectorInfo);
if (inspectorInfo?.search('"enabled":true') && inspectorInfo?.search('"enabled":true') !== -1) {
this.isShowMultiWindowEntry = true;
} else {
this.isShowMultiWindowEntry = false;
}
}) as number;
} Row() {
MultiWindowEntryInAPP({
want: this.want,
isShowSubtitle: false,
multiWindowEntryInAPPStyle: {
// ...
}
})
.id('MultiWindowEntryInAPP')
}
.visibility(this.isShowMultiWindowEntry ? Visibility.Visible : Visibility.None) This section describes how to reuse the code logic and layout based on the existing mobile UI development solution to efficiently develop stock apps for PCs.
Long video apps support full-screen and freeform window modes on PCs. For details, see Window Mode. By monitoring window size changes, the app refreshes the UI based on the breakpoints to automatically apply layouts suitable for distinct window modes, such as the full-screen and freeform window modes modes.
According to the UX design specifications, the immersive effect needs to be implemented in both full-screen and freeform window modes. For details, see Immersive Window. In full-screen mode, the immersive effect is implemented by calling window.setWindowLayoutFullscreen(). In freeform window mode, the title bar is hidden by calling window.setWindowDecorVisible(false), and only the three buttons in the upper right corner are retained. In this way, the page content extends to the title bar area, achieving an immersive display effect.