
鸿蒙跨设备GitHub仓库搜索应用设计与实现 原创
鸿蒙跨设备GitHub仓库搜索应用设计与实现
一、系统架构设计
基于HarmonyOS的网络能力和分布式技术,我们设计了一个跨设备同步的GitHub仓库搜索应用,能够搜索GitHub仓库并在多设备间同步搜索结果。
!https://example.com/github-search-arch.png
系统包含三个核心模块:
API调用模块 - 使用@ohos.net.http调用GitHub API
分布式同步模块 - 通过@ohos.distributedData实现多设备搜索历史同步
UI组件模块 - 提供搜索界面和结果展示
二、核心代码实现
GitHub API服务(ArkTS)
// GitHubService.ets
import http from ‘@ohos.net.http’;
import { distributedData } from ‘@ohos.distributedData’;
const GITHUB_API_URL = ‘https://api.github.com/search/repositories’;
const SEARCH_HISTORY_KEY = ‘github_search_history’;
class GitHubService {
private static instance: GitHubService = null;
private httpRequest: http.HttpRequest = http.createHttp();
private dataManager: distributedData.DataManager;
private constructor() {
this.initDataManager();
public static getInstance(): GitHubService {
if (!GitHubService.instance) {
GitHubService.instance = new GitHubService();
return GitHubService.instance;
private initDataManager() {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.githubsearch',
area: distributedData.Area.GLOBAL
});
this.dataManager.registerDataListener(SEARCH_HISTORY_KEY, (data) => {
this.handleSyncData(data);
});
public async searchRepositories(query: string): Promise<Repository[]> {
try {
const response = await this.httpRequest.request(
{GITHUB_API_URL}?q={encodeURIComponent(query)},
method: http.RequestMethod.GET,
header: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'HarmonyOS-GitHub-Search'
}
);
if (response.responseCode === http.ResponseCode.OK) {
const result = JSON.parse(response.result.toString());
this.saveSearchHistory(query, result.items);
return result.items;
else {
console.error('搜索失败:', response.responseCode, response.result);
return [];
} catch (err) {
console.error('搜索请求异常:', JSON.stringify(err));
return [];
}
private async saveSearchHistory(query: string, repos: Repository[]) {
const historyItem: SearchHistory = {
id: Date.now().toString(),
query: query,
repos: repos.slice(0, 5), // 只保存前5个结果
timestamp: Date.now(),
deviceId: this.getDeviceId()
};
// 本地存储
const localHistory = await this.getLocalSearchHistory();
localHistory.unshift(historyItem);
await this.setLocalSearchHistory(localHistory.slice(0, 10)); // 最多保存10条
// 同步到其他设备
this.dataManager.syncData(SEARCH_HISTORY_KEY, {
type: 'addHistory',
history: historyItem
});
private async getLocalSearchHistory(): Promise<SearchHistory[]> {
// 实际实现需要使用Preferences或数据库
return [];
private async setLocalSearchHistory(history: SearchHistory[]): Promise<void> {
// 实际实现需要使用Preferences或数据库
private handleSyncData(data: any): void {
if (data?.type === 'addHistory' && data.history) {
this.addRemoteHistory(data.history);
}
private async addRemoteHistory(history: SearchHistory): Promise<void> {
if (history.deviceId === this.getDeviceId()) return;
const localHistory = await this.getLocalSearchHistory();
if (!localHistory.some(h => h.id === history.id)) {
localHistory.unshift(history);
await this.setLocalSearchHistory(localHistory.slice(0, 10));
}
public async getSearchHistory(): Promise<SearchHistory[]> {
const localHistory = await this.getLocalSearchHistory();
return localHistory.sort((a, b) => b.timestamp - a.timestamp);
public async clearSearchHistory(): Promise<void> {
await this.setLocalSearchHistory([]);
// 同步清除命令
this.dataManager.syncData(SEARCH_HISTORY_KEY, {
type: 'clearHistory',
deviceId: this.getDeviceId()
});
private getDeviceId(): string {
// 实际实现需要获取设备ID
return 'local_device';
}
interface Repository {
id: number;
name: string;
full_name: string;
html_url: string;
description: string;
stargazers_count: number;
forks_count: number;
language: string;
owner: {
login: string;
avatar_url: string;
};
interface SearchHistory {
id: string;
query: string;
repos: Repository[];
timestamp: number;
deviceId: string;
export const githubService = GitHubService.getInstance();
搜索主界面(ArkUI)
// GitHubSearchApp.ets
import { githubService } from ‘./GitHubService’;
@Entry
@Component
struct GitHubSearchApp {
@State searchQuery: string = ‘’;
@State searchResults: Repository[] = [];
@State isLoading: boolean = false;
@State searchHistory: SearchHistory[] = [];
@State showHistory: boolean = false;
aboutToAppear() {
this.loadSearchHistory();
build() {
Column() {
// 搜索栏
this.buildSearchBar()
// 加载状态
if (this.isLoading) {
LoadingProgress()
.margin(20)
// 历史记录面板
if (this.showHistory && this.searchHistory.length > 0) {
this.buildHistoryPanel()
// 搜索结果
if (!this.showHistory && this.searchResults.length > 0) {
this.buildResultsList()
else if (!this.showHistory && !this.isLoading) {
Text('输入关键词搜索GitHub仓库')
.fontSize(16)
.margin(20)
}
.padding(10)
@Builder
buildSearchBar() {
Row() {
TextInput({ placeholder: ‘搜索GitHub仓库’, text: this.searchQuery })
.onChange((value: string) => {
this.searchQuery = value;
})
.onSubmit(() => {
this.doSearch();
})
.layoutWeight(1)
if (this.searchQuery) {
Button('搜索')
.onClick(() => {
this.doSearch();
})
.margin({ left: 10 })
else {
Button('历史')
.onClick(() => {
this.toggleHistory();
})
.margin({ left: 10 })
}
@Builder
buildHistoryPanel() {
Column() {
Text(‘搜索历史’)
.fontSize(18)
.margin({ bottom: 10 })
List({ space: 10 }) {
ForEach(this.searchHistory, (history) => {
ListItem() {
this.buildHistoryItem(history)
})
.layoutWeight(1)
Button('清除历史')
.margin({ top: 10 })
.onClick(() => {
this.clearHistory();
})
.height(‘60%’)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding(10)
@Builder
buildHistoryItem(history: SearchHistory) {
Column() {
Row() {
Text(history.query)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text(this.formatTime(history.timestamp))
.fontSize(12)
if (history.repos.length > 0) {
ForEach(history.repos.slice(0, 3), (repo) => {
Text({repo.name} (⭐{repo.stargazers_count}))
.fontSize(14)
.margin({ top: 4 })
})
}
.width('100%')
.padding(10)
.backgroundColor('#FFFFFF')
.borderRadius(4)
.onClick(() => {
this.searchQuery = history.query;
this.searchResults = history.repos;
this.showHistory = false;
})
@Builder
buildResultsList() {
List({ space: 10 }) {
ForEach(this.searchResults, (repo) => {
ListItem() {
this.buildRepoItem(repo)
})
.layoutWeight(1)
@Builder
buildRepoItem(repo: Repository) {
Row() {
// 仓库头像
Image(repo.owner.avatar_url)
.width(40)
.height(40)
.borderRadius(20)
.margin({ right: 10 })
// 仓库信息
Column() {
Text(repo.full_name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
if (repo.description) {
Text(repo.description)
.fontSize(14)
.margin({ top: 4 })
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row() {
if (repo.language) {
Text(repo.language)
.fontSize(12)
.backgroundColor('#EEEEEE')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
Text(⭐ ${repo.stargazers_count})
.fontSize(12)
.margin({ left: 8 })
Text(🍴 ${repo.forks_count})
.fontSize(12)
.margin({ left: 4 })
.margin({ top: 8 })
.layoutWeight(1)
.width(‘100%’)
.padding(10)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onClick(() => {
this.openRepoInBrowser(repo.html_url);
})
private async doSearch() {
if (!this.searchQuery.trim()) return;
this.isLoading = true;
this.showHistory = false;
try {
this.searchResults = await githubService.searchRepositories(this.searchQuery);
finally {
this.isLoading = false;
}
private toggleHistory() {
this.showHistory = !this.showHistory;
if (this.showHistory) {
this.loadSearchHistory();
}
private async loadSearchHistory() {
this.searchHistory = await githubService.getSearchHistory();
private async clearHistory() {
await githubService.clearSearchHistory();
this.searchHistory = [];
private openRepoInBrowser(url: string) {
// 实际实现需要使用系统能力打开浏览器
console.log('打开仓库:', url);
private formatTime(timestamp: number): string {
const date = new Date(timestamp);
return {date.getFullYear()}-{date.getMonth() + 1}-${date.getDate()};
}
分布式同步增强(ArkTS)
// EnhancedSyncService.ets
import deviceManager from ‘@ohos.distributedDeviceManager’;
class EnhancedSyncService {
private static instance: EnhancedSyncService = null;
private deviceManager: deviceManager.DeviceManager;
private constructor() {
this.initDeviceManager();
public static getInstance(): EnhancedSyncService {
if (!EnhancedSyncService.instance) {
EnhancedSyncService.instance = new EnhancedSyncService();
return EnhancedSyncService.instance;
private async initDeviceManager() {
try {
this.deviceManager = await deviceManager.createDeviceManager('com.example.githubsearch');
this.deviceManager.on('deviceOnline', (device) => {
this.syncWithDevice(device.deviceId);
});
catch (err) {
console.error('初始化DeviceManager失败:', JSON.stringify(err));
}
public async syncAllDevices() {
const devices = this.deviceManager.getTrustedDeviceListSync();
devices.forEach(device => {
this.syncWithDevice(device.deviceId);
});
private async syncWithDevice(deviceId: string) {
const history = await githubService.getSearchHistory();
distributedData.sync(SEARCH_HISTORY_KEY, {
type: 'fullSync',
history: history,
timestamp: Date.now(),
deviceId: this.getLocalDeviceId()
}, {
targetDevice: deviceId
});
private getLocalDeviceId(): string {
// 实际实现需要获取设备ID
return 'local_device';
}
export const enhancedSyncService = EnhancedSyncService.getInstance();
三、项目配置
权限配置
// module.json5
“module”: {
"requestPermissions": [
“name”: “ohos.permission.INTERNET”,
"reason": "访问GitHub API"
},
“name”: “ohos.permission.DISTRIBUTED_DATASYNC”,
"reason": "跨设备同步搜索历史"
],
"abilities": [
“name”: “MainAbility”,
"type": "page",
"visible": true
],
"distributedNotification": {
"scenarios": [
“name”: “github_search”,
"value": "search_history"
]
}
资源文件
<!-- resources/base/element/string.json -->
“string”: [
“name”: “app_name”,
"value": "GitHub搜索"
},
“name”: “search_hint”,
"value": "搜索GitHub仓库"
},
“name”: “search_button”,
"value": "搜索"
},
“name”: “history_button”,
"value": "历史"
},
“name”: “history_title”,
"value": "搜索历史"
},
“name”: “clear_history”,
"value": "清除历史"
},
“name”: “default_hint”,
"value": "输入关键词搜索GitHub仓库"
]
四、功能扩展
添加收藏功能
// 在GitHubService中添加收藏功能
class GitHubService {
private favoriteRepos: Repository[] = [];
public async addFavorite(repo: Repository): Promise<void> {
if (!this.favoriteRepos.some(r => r.id === repo.id)) {
this.favoriteRepos.push(repo);
await this.saveFavorites();
this.syncFavorite(repo, ‘add’);
}
public async removeFavorite(repoId: number): Promise<void> {
this.favoriteRepos = this.favoriteRepos.filter(r => r.id !== repoId);
await this.saveFavorites();
this.syncFavorite({ id: repoId } as Repository, ‘remove’);
public async getFavorites(): Promise<Repository[]> {
return [...this.favoriteRepos];
private async saveFavorites(): Promise<void> {
// 实际实现需要持久化存储
private syncFavorite(repo: Repository, action: ‘add’ | ‘remove’): void {
distributedData.sync('favorite_repos', {
type: action,
repo: repo,
timestamp: Date.now(),
deviceId: this.getDeviceId()
});
// …其他方法
添加趋势仓库查看
// 在GitHubService中添加趋势仓库功能
class GitHubService {
public async getTrendingRepos(): Promise<Repository[]> {
try {
const response = await this.httpRequest.request(
‘https://api.github.com/search/repositories?q=stars:>1000&sort=stars&order=desc’,
method: http.RequestMethod.GET,
header: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'HarmonyOS-GitHub-Search'
}
);
if (response.responseCode === http.ResponseCode.OK) {
const result = JSON.parse(response.result.toString());
return result.items;
else {
console.error('获取趋势仓库失败:', response.responseCode, response.result);
return [];
} catch (err) {
console.error('获取趋势请求异常:', JSON.stringify(err));
return [];
}
添加用户搜索功能
// 在GitHubService中添加用户搜索功能
class GitHubService {
public async searchUsers(query: string): Promise<User[]> {
try {
const response = await this.httpRequest.request(
https://api.github.com/search/users?q=${encodeURIComponent(query)},
method: http.RequestMethod.GET,
header: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'HarmonyOS-GitHub-Search'
}
);
if (response.responseCode === http.ResponseCode.OK) {
const result = JSON.parse(response.result.toString());
return result.items;
else {
console.error('搜索用户失败:', response.responseCode, response.result);
return [];
} catch (err) {
console.error('搜索用户请求异常:', JSON.stringify(err));
return [];
}
interface User {
login: string;
id: number;
avatar_url: string;
html_url: string;
五、总结
通过这个GitHub仓库搜索应用的实现,我们学习了:
使用HarmonyOS网络能力调用RESTful API
处理异步网络请求和响应
使用分布式能力同步搜索历史
构建响应式的用户界面
实现本地历史记录管理
这个应用可以进一步扩展为功能更完善的GitHub客户端,如:
添加OAuth认证支持更多API调用
实现仓库收藏和分类管理
添加用户信息查看功能
支持查看仓库Issues和Pull Requests
添加代码片段搜索功能
分布式同步能力可以确保用户在不同设备上都能访问到最新的搜索历史,实现无缝的开发体验。
