We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All
Best PracticesIndustry SolutionsNavigation & TransportBike Sharing

Bike Sharing

Overview

This solution targets apps that provide shared rental and instant delivery, such as bike sharing. It employs technologies like live view, map navigation, and code scanning to enhance user experience.

Final Effect

Solution Overview

Introduction to the Scenario

To simplify the app usage process and improve user experience, you are advised to develop the app as follows:

1. Users can scan the QR code in the app or from the system entry to directly access the bike unlocking page.

2. Display a live view to show the cycling status after users tap the unlock button.

3. Following actions like returning the bike and making payments, the live view updates the status in real time.

In this way, users do not need to repeatedly search for the app and feature entry, making the entire process more straightforward.

Advantages

In this scenario, system capabilities like live view, map navigation, and code scanning are employed to enhance user convenience and efficiency.

1. Live view: helps users focus on tasks as well as quickly view and handle them. Widgets can be displayed on the lock screen and notification panel, and capsules can be displayed on the status bar. Users can tap the capsules to expand floating widgets, which makes it easy to view key information. This variety of display means ensures timely information delivery, reducing the needs for users to access apps or service pages.

2. Map Kit: offers personalized map presentation, map searches, and route planning. It provides a smooth interaction experience with gestures, such as scaling, rotating, and moving the map.

Scenario Analysis

Typical Scenarios

No.

Scenario

Description

Implementation

1

Scan to unlock

Scan the QR code from the home page or the bike sharing page to access the bike unlocking page.

Scan Kit: instant QR code scanning

2

Route planning

When a destination is selected, the shortest route is displayed.

Map Kit: fast route planning and drawing

3

Status display in live view

User can check the cycling status in real time.

Live View Kit: Users can check the cycling status on the lock screen.

Implementation

Process Flowchart

The business process diagram on the left depicts the existing process for the cycling scenario, whereas the one on the right reveals the enhanced process. Following optimization, actions like switching between apps and locating the feature entry have been removed, thereby streamlining user interactions and significantly improving user experience.

Cycling States

Sequence Diagram

Scan to Unlock

Final Effect

On the home page or the bicycle sharing page, users can tap to scan the QR code or select one from photos. For details, see Scan-to-Access Capability.

Sequence Diagram

The following diagram describes the service process.

Key Points

1. Scan Kit for QR code scanning: Leveraging multiple computer vision technologies and AI algorithms, Scan Kit can automatically scan QR codes that are far away from the camera and recognize QR codes in low-light environments, as well as barcodes that are damaged, unclear, and in a hard-to-read position for accurate scanning in all kinds of environments. This leads to a higher barcode recognition rate and improved user experience.

2. Requesting system camera permission: Add the ohos.permission.CAMERA permission to the requestPermissions field in the module.json5 file of the entry module.

Collapse
Word wrap
Dark theme
Copy code
  1. "requestPermissions": [
  2. // ...
  3. {
  4. "name": "ohos.permission.CAMERA",
  5. "reason": "$string:reason_camera",
  6. "usedScene": {
  7. "abilities": [
  8. "EntryAbility"
  9. ],
  10. "when": "always"
  11. }
  12. }
  13. ]
  14. },

3. Support for multiple code types, including commonly used QR codes and barcodes.

Core Code

Collapse
Word wrap
Dark theme
Copy code
  1. import { scanBarcode, scanCore } from '@kit.ScanKit';
  2. import { CyclingConstants, CyclingStatus } from '../constants/CyclingConstants';
  3. import { BusinessError } from '@kit.BasicServicesKit';
  4. import Logger from './Logger';
  5. export class ScanUtil {
  6. public static scan(obj: Object, uiContext: UIContext): void {
  7. let options: scanBarcode.ScanOptions = {
  8. scanTypes: [scanCore.ScanType.ALL, scanCore.ScanType.ONE_D_CODE],
  9. enableMultiMode: true,
  10. enableAlbum: true
  11. };
  12. try {
  13. scanBarcode.startScanForResult(uiContext?.getHostContext(), options).then((result: scanBarcode.ScanResult) => {
  14. Logger.info('[BicycleSharing]', 'Promise scan result: %{public}s', JSON.stringify(result));
  15. if (result.scanType === CyclingConstants.SCAN_TYPE) {
  16. AppStorage.setOrCreate(CyclingConstants.CYCLING_STATUS, CyclingStatus.WAITING_UNLOCK);
  17. uiContext?.getRouter().pushUrl({ url: 'pages/ConfirmUnlock' });
  18. }
  19. }).catch((error: BusinessError) => {
  20. Logger.error(0x0001, '[BicycleSharing]', 'Promise error: %{public}s', JSON.stringify(error));
  21. });
  22. } catch (error) {
  23. Logger.error(0x0001, '[BicycleSharing]', 'failReason: %{public}s', JSON.stringify(error));
  24. }
  25. }
  26. }

Route Planning

Final Effect

On the bike search page, you can tab any location for route planning.

Sequence Diagram

Key Points

1. Map Kit enables you to implement personalized map display, map search, and route planning at ease.

2. Enable Map Kit in AppGallery Connect by referring to Enabling Map Kit. Configure client_id in the module.json5 file of the entry module in the project.

3. Before enabling the my-location icon, ensure that your app can obtain user location information. The ohos.permission.LOCATION and ohos.permission.APPROXIMATELY_LOCATION permissions are required.

Core Code

1. Import Map Kit.

Collapse
Word wrap
Dark theme
Copy code
  1. import { MapComponent, mapCommon, map } from '@kit.MapKit';

2. Integrate the map component and initialize the map page.

Collapse
Word wrap
Dark theme
Copy code
  1. aboutToAppear(): void {
  2. // Initialize the map.
  3. this.callback = async (err, mapController) => {
  4. let hasPermissions = false;
  5. if (!err) {
  6. this.mapController = mapController;
  7. this.mapController.on('mapLoad', async () => {
  8. hasPermissions = await MapUtil.checkPermissions(this.mapController);
  9. if (!hasPermissions) {
  10. this.requestPermissions();
  11. }
  12. if (hasPermissions) {
  13. let requestInfo: geoLocationManager.CurrentLocationRequest = {
  14. 'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX,
  15. 'scenario': geoLocationManager.LocationRequestScenario.UNSET,
  16. 'maxAccuracy': 0
  17. };
  18. let locationChange = async (): Promise<void> => {
  19. };
  20. geoLocationManager.on('locationChange', requestInfo, locationChange);
  21. geoLocationManager.getCurrentLocation(requestInfo).then(async (result) => {
  22. let mapPosition: mapCommon.LatLng =
  23. await map.convertCoordinate(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02, result);
  24. AppStorage.setOrCreate('longitude', mapPosition.longitude);
  25. AppStorage.setOrCreate('latitude', mapPosition.latitude);
  26. let cameraPosition: mapCommon.CameraPosition = {
  27. target: mapPosition,
  28. zoom: 15,
  29. tilt: 0,
  30. bearing: 0
  31. };
  32. let cameraUpdate = map.newCameraPosition(cameraPosition);
  33. mapController?.animateCamera(cameraUpdate, 1000);
  34. })
  35. }
  36. });
  37. this.mapController.on('mapClick', async (position) => {
  38. this.mapController?.clear();
  39. this.marker?.remove();
  40. let requestInfo: geoLocationManager.CurrentLocationRequest = {
  41. 'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX,
  42. 'scenario': geoLocationManager.LocationRequestScenario.UNSET,
  43. 'maxAccuracy': 0
  44. };
  45. let locationChange = async (location: geoLocationManager.Location): Promise<void> => {
  46. let wgs84Position: mapCommon.LatLng = {
  47. latitude: location.latitude,
  48. longitude: location.longitude
  49. };
  50. let gcj02Posion: mapCommon.LatLng =
  51. await map.convertCoordinate(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02,
  52. wgs84Position);
  53. this.myPosition = gcj02Posion
  54. };
  55. geoLocationManager.on('locationChange', requestInfo, locationChange);
  56. // Add a walking marker.
  57. this.marker = await MapUtil.addMarker(position, this.mapController);
  58. const walkingRoutes = await MapUtil.walkingRoutes(position, this.myPosition);
  59. await MapUtil.paintRoute(walkingRoutes!, this.mapPolyline, this.mapController);
  60. });
  61. }
  62. };
  63. }

3. Request the location permission and enable the My Location feature.

Collapse
Word wrap
Dark theme
Copy code
  1. requestPermissions(): void {
  2. let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  3. atManager.requestPermissionsFromUser(this.getUIContext().getHostContext() as common.UIAbilityContext,
  4. ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'])
  5. .then(() => {
  6. this.mapController?.setMyLocationEnabled(true);
  7. this.mapController?.setMyLocationControlsEnabled(true);
  8. this.mapController?.setCompassControlsEnabled(false);
  9. this.mapController?.setMyLocationStyle({ displayType: mapCommon.MyLocationDisplayType.FOLLOW });
  10. geoLocationManager.getCurrentLocation().then(async (result) => {
  11. let mapPosition: mapCommon.LatLng =
  12. await map.convertCoordinate(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02, result);
  13. AppStorage.setOrCreate('longitude', mapPosition.longitude);
  14. AppStorage.setOrCreate('latitude', mapPosition.latitude);
  15. let cameraPosition: mapCommon.CameraPosition = {
  16. target: mapPosition,
  17. zoom: 15,
  18. tilt: 0,
  19. bearing: 0
  20. };
  21. let cameraUpdate = map.newCameraPosition(cameraPosition);
  22. this.mapController?.animateCamera(cameraUpdate, 1000);
  23. })
  24. })
  25. .catch((err: BusinessError) => {
  26. Logger.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
  27. })
  28. }

4. Set a tap event listener.

Collapse
Word wrap
Dark theme
Copy code
  1. this.mapController.on('mapClick', async (position) => {
  2. this.mapController?.clear();
  3. this.marker?.remove();
  4. if (!this.myPosition) {
  5. Logger.error('Current position is not available');
  6. return;
  7. }
  8. this.marker = await MapUtil.addMarker(position, this.mapController);
  9. const walkingRoutes = await MapUtil.walkingRoutes(position, this.myPosition);
  10. await MapUtil.paintRoute(walkingRoutes!, this.mapPolyline, this.mapController);
  11. });

5. Start route planning.

Collapse
Word wrap
Dark theme
Copy code
  1. public static async walkingRoutes(position: mapCommon.LatLng, myPosition?: mapCommon.LatLng) {
  2. let params: navi.RouteParams = {
  3. origins: [myPosition!],
  4. destination: position,
  5. language: 'zh_CN'
  6. };
  7. try {
  8. const result = await navi.getWalkingRoutes(params);
  9. Logger.info('naviDemo', 'getWalkingRoutes success result =' + JSON.stringify(result));
  10. return result;
  11. } catch (err) {
  12. Logger.error('naviDemo', 'getWalkingRoutes fail err =' + JSON.stringify(err));
  13. }
  14. return undefined;
  15. }

6. Draw a route.

Collapse
Word wrap
Dark theme
Copy code
  1. public static async paintRoute(routeResult: navi.RouteResult, mapPolyline?: map.MapPolyline,
  2. mapController?: map.MapComponentController) {
  3. mapPolyline?.remove();
  4. let polylineOption: mapCommon.MapPolylineOptions = {
  5. points: routeResult.routes[0].overviewPolyline!,
  6. clickable: true,
  7. startCap: mapCommon.CapStyle.BUTT,
  8. endCap: mapCommon.CapStyle.BUTT,
  9. geodesic: false,
  10. jointType: mapCommon.JointType.BEVEL,
  11. visible: true,
  12. width: 20,
  13. zIndex: 10,
  14. gradient: false,
  15. color: 0xFF2970FF
  16. }
  17. try {
  18. mapPolyline = await mapController?.addPolyline(polylineOption);
  19. } catch (error) {
  20. Logger.error('naviDemo', `addPolyline error: ${JSON.stringify(error)}`);
  21. }
  22. }

Status Display in Live View

Final Effect

After a user taps the unlock button, the live view is displayed to show the cycling status. Once the bike is returned or payment is made, the live view updates in real time. Widgets can be displayed on the lock screen or in the notification panel, and a capsule can be displayed in the status bar. Tapping the capsule expands the floating widget for an intuitive view.

Sequence Diagram

Key Points

1. Live View Kit allows apps to display real-time status changes of orders or services on the screen.

2. Enable Live View Kit in AppGallery Connect by referring to Applying for the Formal Permission on Live View Kit.

3. In this scenario, only the local live view is used. The local update or end of the live view depends on your app's process. If necessary, you can use Push Kit to remotely update or end the live view.

Core Code

1. Import Live View Kit.

Collapse
Word wrap
Dark theme
Copy code
  1. import { liveViewManager } from '@kit.LiveViewKit';

2. Create a live view.

Collapse
Word wrap
Dark theme
Copy code
  1. public async startLiveView(context: LiveViewContext,
  2. liveViewEnvironment?: LiveViewEnvironment): Promise<liveViewManager.LiveViewResult | undefined> {
  3. // build liveView
  4. this.liveViewData = await LiveViewController.buildDefaultView(context);
  5. let env = liveViewEnvironment;
  6. if (!env) {
  7. env = {
  8. id: 0,
  9. event: 'RENT'
  10. };
  11. }
  12. this.liveNotification = LiveNotification.from(context, env);
  13. return await this.liveNotification.create(this.liveViewData);
  14. }

3. Update and end the live view.

Collapse
Word wrap
Dark theme
Copy code
  1. public async updateLiveView(status: number,
  2. context: LiveViewContext): Promise<liveViewManager.LiveViewResult | undefined> {
  3. // update liveView
  4. const liveViewData = this.liveViewData!;
  5. switch (status) {
  6. case CyclingStatus.WAITING_PAYMENT:
  7. liveViewData.primary.title = CyclingConstants.WAITING_PAYMENT_TITLE;
  8. liveViewData.primary.content = [
  9. {
  10. text: CyclingConstants.WAITING_PAYMENT_CONTENT,
  11. textColor: CyclingConstants.CONTENT_COLOR
  12. }
  13. ];
  14. liveViewData.primary.clickAction = await LiveViewController.buildWantAgent(context.want);
  15. liveViewData.primary.layoutData = new TextLayoutBuilder()
  16. .setTitle(CyclingConstants.WAITING_PAYMENT_LAYOUT_TITLE)
  17. .setContent(CyclingConstants.WAITING_PAYMENT_LAYOUT_CONTENT)
  18. .setDescPic('bike_page.png');
  19. liveViewData.capsule = new TextCapsuleBuilder()
  20. .setIcon('white_bike.png')
  21. .setBackgroundColor(CyclingConstants.CAPSULE_COLOR)
  22. .setTitle(CyclingConstants.WAITING_PAYMENT_LAYOUT_TITLE)
  23. break;
  24. case CyclingStatus.PAYMENT_COMPLETED:
  25. liveViewData.primary.title = CyclingConstants.WAITING_PAYMENT_TITLE;
  26. liveViewData.primary.clickAction = await LiveViewController.buildWantAgent(context.want);
  27. liveViewData.primary.content = [
  28. {
  29. text: CyclingConstants.WAITING_PAYMENT_PAY,
  30. textColor: CyclingConstants.CONTENT_COLOR
  31. },
  32. {
  33. text: CyclingConstants.WAITING_PAYMENT_PAY_SUCCESS,
  34. textColor: CyclingConstants.CONTENT_COLOR
  35. }
  36. ];
  37. liveViewData.primary.layoutData = new TextLayoutBuilder()
  38. .setTitle(CyclingConstants.WAITING_PAYMENT_PAY_END)
  39. .setContent(CyclingConstants.WAITING_PAYMENT_LAYOUT_CONTENT)
  40. .setDescPic('bike_page.png');
  41. liveViewData.capsule = new TextCapsuleBuilder()
  42. .setIcon('white_bike.png')
  43. .setBackgroundColor(CyclingConstants.CAPSULE_COLOR)
  44. .setTitle(CyclingConstants.PAYMENT_COMPLETED_CAPSULE_TITLE)
  45. return await this.liveNotification!.stop(liveViewData);
  46. default:
  47. break;
  48. }
  49. return await this.liveNotification!.update(liveViewData);
  50. }

4. Customize an immersive live view.

Collapse
Word wrap
Dark theme
Copy code
  1. export default class LiveViewLockScreenExtAbility extends LiveViewLockScreenExtensionAbility {
  2. onCreate() {
  3. hilog.info(0x0000, 'LiveViewLockScreenTag', 'LiveViewLockScreenExtAbility onCreate begin.');
  4. }
  5. onForeground() {
  6. hilog.info(0x0000, 'LiveViewLockScreenTag', 'LiveViewLockScreenExtAbility onForeground begin.');
  7. }
  8. onBackground() {
  9. hilog.info(0x0000, 'LiveViewLockScreenTag', 'LiveViewLockScreenExtAbility onBackground begin.');
  10. }
  11. onDestroy() {
  12. hilog.info(0x0000, 'LiveViewLockScreenTag', 'LiveViewLockScreenExtAbility onDestroy begin.');
  13. }
  14. onSessionCreate(_want: Want, session: UIExtensionContentSession) {
  15. hilog.info(0x0000, 'LiveViewLockScreenTag', 'LiveViewLockScreenExtAbility onSessionCreate begin.');
  16. try {
  17. session.loadContent('pages/LiveViewLockScreenPage');
  18. } catch (error) {
  19. hilog.error(0x0000, 'LiveViewLockScreenTag', `onSessionCreate error: ${JSON.stringify(error)}.`)
  20. }
  21. }
  22. onSessionDestroy(_session: UIExtensionContentSession) {
  23. }
  24. }

5. Set immersive live view parameters in LiveViewDataBuilder.

Collapse
Word wrap
Dark theme
Copy code
  1. this.primary = {
  2. title: '',
  3. content: [
  4. {
  5. text: '',
  6. textColor: ''
  7. }
  8. ],
  9. keepTime: CyclingConstants.KEEP_TIME,
  10. clickAction: undefined,
  11. layoutData: undefined,
  12. liveViewLockScreenPicture: 'icBike.png',
  13. liveViewLockScreenAbilityName: 'LiveViewLockScreenExtAbility',
  14. liveViewLockScreenAbilityParameters: parameters
  15. };

6. Configure an ExtensionAbility in module.json5.

Collapse
Word wrap
Dark theme
Copy code
  1. "extensionAbilities": [
  2. {
  3. "name": "LiveViewLockScreenExtAbility",
  4. "type": "liveViewLockScreen",
  5. "srcEntry": "./ets/entryability/LiveViewLockScreenExtAbility.ets",
  6. "exported": true
  7. }
  8. ],

Sample Code

Search in Best Practices
Enter a keyword.