Hands-on Development of Smart Connected Car Applications Based on HarmonyOS Next



This content originally appeared on DEV Community and was authored by linzhongxue

Hands-on Development of Smart Connected Car Applications Based on HarmonyOS Next

Opening: When HarmonyOS Meets Smart Vehicles

In the era of ubiquitous connectivity, automobiles have long transcended their role as mere transportation tools. As a developer with years of experience in vehicle connectivity, I’ve had the privilege to participate in multiple HarmonyOS in-car application projects. Today, through a complete in-car music application case study, I’ll demonstrate how to rapidly build professional-grade automotive applications using AppGallery Connect.

1. Fundamental Preparations for In-Car Application Development

1.1 Special Configuration of Development Environment

In-car application development requires special attention to the following configurations:

  1. Install the “Automotive” extension package in DevEco Studio
  2. Apply for in-car development permissions (requires enterprise developer account)
  3. Select automotive resolution (typically 1920×720) when configuring the emulator
// In-car environment detection tool
function checkAutomotiveEnvironment() {
    try {
        const systemInfo = device.getSystemInfoSync();
        if (!systemInfo.isAutomotive) {
            console.warn("Not in automotive environment, some APIs may be restricted");
            return false;
        }
        console.log("Automotive system version:", systemInfo.osVersion);
        return true;
    } catch (e) {
        console.error("Environment detection exception:", e);
        return false;
    }
}

1.2 In-Car UI Design Specifications

In-car applications must follow special interaction principles:

  • Button size no smaller than 60px
  • Text size no smaller than 24px
  • Operation hierarchy no more than 3 levels
  • Full-screen popups are prohibited

2. Developing an In-Car Music Player

2.1 Audio Service Integration

// Audio service wrapper class
class AudioService {
    private audioSession: audio.AudioSession;
    private player: audio.AudioPlayer;

    constructor() {
        this.initAudioSession();
    }

    private initAudioSession() {
        // Create dedicated automotive audio session
        this.audioSession = audio.createAudioSession({
            usage: audio.StreamUsage.MUSIC,
            device: audio.CommunicationDevice.CAR_AUDIO
        });

        // Configure audio focus policy
        this.audioSession.setInterruptionMode(
            audio.InterruptionMode.SHAREABLE
        );
    }

    // Initialize player
    async initPlayer(source: string) {
        this.player = await this.audioSession.createPlayer({
            source: {
                uri: source
            },
            loop: false
        });

        // Set automotive audio properties
        await this.player.setAudioProperties({
            speed: 1.0,
            pitch: 1.0,
            fadeTime: 500
        });
    }

    // Playback control
    async play() {
        try {
            await this.player.play();
            console.log("Playback started");
        } catch (error) {
            console.error("Playback failed:", error);
        }
    }

    // Pause playback
    async pause() {
        await this.player.pause();
    }

    // Switch audio source
    async switchSource(newSource: string) {
        await this.player.reset();
        await this.player.setSource({ uri: newSource });
        await this.play();
    }
}

2.2 Music List Component

// Dedicated in-car music list component
@Component
struct MusicList {
    @State songs: Song[] = [];
    @State currentIndex: number = -1;

    // Column width adapts to in-car display
    @StorageProp('displayMode') displayMode: string = 'single';

    build() {
        Grid() {
            ForEach(this.songs, (song, index) => {
                GridItem() {
                    Column() {
                        Image(song.cover)
                            .width(120)
                            .height(120)
                            .borderRadius(8)

                        Text(song.name)
                            .fontSize(24)
                            .margin({ top: 8 })

                        Text(song.artist)
                            .fontSize(18)
                            .opacity(0.8)
                    }
                    .padding(12)
                    .backgroundColor(
                        this.currentIndex === index ? 
                        '#333333' : 'transparent'
                    )
                    .onClick(() => {
                        this.playSelected(index);
                    })
                }
                .colSpan(this.displayMode === 'single' ? 2 : 1)
            })
        }
        .columnsTemplate(
            this.displayMode === 'single' ? 
            '1fr' : '1fr 1fr'
        )
        .onAppear(() => {
            this.loadMusicList();
        })
    }

    private async loadMusicList() {
        try {
            const response = await fetch(
                'https://api.example.com/car-music'
            );
            this.songs = await response.json();
        } catch (error) {
            console.error("Failed to fetch music list:", error);
        }
    }

    private playSelected(index: number) {
        this.currentIndex = index;
        const audioService = AudioService.getInstance();
        audioService.switchSource(this.songs[index].url);
    }
}

3. Vehicle Data Interaction Module

3.1 Vehicle Status Subscription

// Vehicle data service
class VehicleDataService {
    private static instance: VehicleDataService;
    private vehicleApi: any;

    private constructor() {
        this.initVehicleApi();
    }

    public static getInstance(): VehicleDataService {
        if (!VehicleDataService.instance) {
            VehicleDataService.instance = new VehicleDataService();
        }
        return VehicleDataService.instance;
    }

    private initVehicleApi() {
        try {
            this.vehicleApi = require('@ohos.vehicle');
        } catch (error) {
            console.error("Vehicle API unavailable:", error);
        }
    }

    // Get current speed
    getCurrentSpeed(): Promise<number> {
        return new Promise((resolve) => {
            if (!this.vehicleApi) {
                resolve(0);
                return;
            }

            this.vehicleApi.getData(
                'speed',
                (err, data) => {
                    if (!err) {
                        resolve(data.value);
                    } else {
                        resolve(0);
                    }
                }
            );
        });
    }

    // Subscribe to vehicle data changes
    subscribe(dataType: string, callback: Function) {
        if (!this.vehicleApi) return;

        this.vehicleApi.subscribe(
            dataType,
            (err, data) => {
                if (!err) {
                    callback(data.value);
                }
            }
        );
    }
}

3.2 Driving Mode Adaptation

// Driving mode awareness component
@Component
struct DrivingModeAware {
    @State drivingMode: string = 'normal';
    private vehicleService = VehicleDataService.getInstance();

    build() {
        Column() {
            // Display different UI based on driving mode
            if (this.drivingMode === 'sport') {
                SportModeView()
            } else if (this.drivingMode === 'night') {
                NightModeView()
            } else {
                NormalModeView()
            }
        }
        .onAppear(() => {
            this.setupDrivingModeListener();
        })
    }

    private setupDrivingModeListener() {
        this.vehicleService.subscribe(
            'driving_mode',
            (mode: string) => {
                this.drivingMode = mode;
            }
        );
    }
}

4. Cloud Synchronization and User Preferences

4.1 User Configuration Synchronization

// User preference management
class UserPreference {
    private cloudDB: cloud.CloudDBZone;

    constructor() {
        this.initCloudDB();
    }

    private async initCloudDB() {
        const config = {
            name: 'CarUserPrefs',
            persistenceEnabled: true,
            encryptionKey: 'user_specific_key'
        };
        this.cloudDB = await cloud.CloudDBZoneManager
            .getInstance()
            .openCloudDBZone(config);
    }

    // Save preference settings
    async savePreference(key: string, value: any) {
        const pref = {
            userId: getCurrentUserId(),
            prefKey: key,
            prefValue: JSON.stringify(value),
            updateTime: new Date().getTime()
        };

        await this.cloudDB.executeUpsert([pref]);
    }

    // Retrieve preference settings
    async getPreference(key: string): Promise<any> {
        const query = cloud.CloudDBZoneQuery
            .where('UserPreference')
            .equalTo('userId', getCurrentUserId())
            .equalTo('prefKey', key)
            .limit(1);

        const snapshot = await this.cloudDB.executeQuery(query);
        if (snapshot.hasNext()) {
            const record = snapshot.next();
            return JSON.parse(record.prefValue);
        }
        return null;
    }
}

4.2 Multi-Device Synchronization Solution

// Cross-device synchronization controller
class SyncController {
    private agcAuth = require('@hw-agconnect/auth-ohos');
    private agcCloud = require('@hw-agconnect/cloud-ohos');

    // Initialize synchronization channel
    async initSyncChannel() {
        const user = await this.agcAuth.getInstance()
            .getCurrentUser();

        if (!user) return false;

        const config = {
            syncPolicy: 'auto',
            conflictHandler: this.resolveConflict
        };

        await this.agcCloud.getInstance()
            .enableDataSync(config);

        return true;
    }

    // Conflict resolution strategy
    private resolveConflict(localData, serverData) {
        // Use data with latest timestamp
        return localData.updateTime > serverData.updateTime ? 
            localData : serverData;
    }

    // Trigger manual synchronization
    async triggerManualSync() {
        try {
            await this.agcCloud.getInstance()
                .executeSync();
            return true;
        } catch (error) {
            console.error("Synchronization failed:", error);
            return false;
        }
    }
}

5. Performance Optimization for In-Car Applications

5.1 Memory Management Techniques

// Resource monitoring component
@Component
struct ResourceMonitor {
    @State memoryUsage: number = 0;
    @State cpuUsage: number = 0;
    private timer: number = 0;

    build() {
        Column() {
            Text(`Memory usage: ${this.memoryUsage.toFixed(1)}MB`)
            Text(`CPU usage: ${this.cpuUsage.toFixed(1)}%`)
        }
        .onAppear(() => {
            this.startMonitoring();
        })
        .onDisappear(() => {
            this.stopMonitoring();
        })
    }

    private startMonitoring() {
        this.timer = setInterval(() => {
            this.updateMetrics();
        }, 2000);
    }

    private stopMonitoring() {
        clearInterval(this.timer);
    }

    private async updateMetrics() {
        const stats = await performance.getMetrics();
        this.memoryUsage = stats.memory / 1024 / 1024;
        this.cpuUsage = stats.cpu * 100;
    }
}

5.2 Efficient Rendering Strategies

// Optimized list rendering
@Component
struct OptimizedList {
    @State data: any[] = [];
    private visibleRange: number[] = [0, 10];

    build() {
        List() {
            LazyForEach(this.data.slice(
                this.visibleRange[0], 
                this.visibleRange[1]
            ), (item) => {
                ListItem() {
                    ListItemContent(item)
                }
            })
        }
        .onScroll((offset: number) => {
            this.updateVisibleRange(offset);
        })
    }

    private updateVisibleRange(offset: number) {
        const start = Math.floor(offset / 100);
        this.visibleRange = [start, start + 10];
    }
}

6. Testing and Release Process

6.1 Specialized Testing for In-Car Applications

  1. Driver distraction testing: Ensure the app doesn’t interfere with normal driving
  2. Extreme environment testing: Operational stability under high/low temperatures
  3. Long-duration testing: 24-hour continuous operation to check for memory leaks
  4. Voice interaction testing: Compatibility with in-car voice systems

6.2 Application Release Checklist

  1. Pass Huawei’s in-car application certification
  2. Complete all required metadata information
  3. Provide screenshots in at least 3 automotive resolutions
  4. Declare supported vehicle models
  5. Prepare automotive-specific application descriptions

Conclusion: Accelerating into the HarmonyOS Automotive Ecosystem

Developing this in-car music application has given me profound appreciation for HarmonyOS’ unique advantages in the automotive field. Particularly its distributed capabilities and hardware collaboration features bring entirely new possibilities to in-car scenarios.

I recommend developers stay updated on Huawei’s intelligent automotive solutions. The automotive UI component library I’ve accumulated during this project is now open-source—search for “harmony-auto-ui” in the open-source community. Looking forward to seeing more amazing applications in the HarmonyOS automotive ecosystem!


This content originally appeared on DEV Community and was authored by linzhongxue