在鸿蒙生态中构建全栈应用时,后端服务的选择往往成为开发效率的关键制约因素。Nhost 作为一套开箱即用的后端解决方案,为开发者提供了身份认证、文件存储和 GraphQL API 等核心功能。而 nhost_sdk 则是连接鸿蒙应用与 Nhost 后端的重要桥梁。
Nhost 的核心价值在于它将多个优秀的开源项目整合为一个完整的后端平台:
nhost_sdk 对这些服务进行了 Dart 语言层面的封装,使得鸿蒙开发者可以专注于业务逻辑的实现,而不必关心底层复杂的网络通信和状态管理。
在开始集成 nhost_sdk 前,请确保你的开发环境满足以下条件:
在项目的 pubspec.yaml 中添加依赖:
yaml复制dependencies:
nhost_sdk: ^1.0.0
dio: ^4.0.0 # 网络请求库
shared_preferences: ^2.0.0 # 本地存储
运行 flutter pub get 安装依赖后,需要在鸿蒙的 module.json5 中声明必要的权限:
json复制{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "用于选择用户头像"
}
]
}
}
dart复制import 'package:nhost_sdk/nhost_sdk.dart';
final nhostClient = NhostClient(
subdomain: 'your-subdomain',
region: 'ap-southeast-1', // 根据实际区域选择
);
关键参数说明:
subdomain:在 Nhost 控制台创建项目时设置的子域名region:建议选择离目标用户群体最近的区域autoRefreshToken:默认为 true,建议保持开启以自动维护会话为了在应用重启后保持用户登录状态,我们需要配置本地存储:
dart复制import 'package:nhost_sdk/nhost_sdk.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<NhostClient> initNhostClient() async {
final prefs = await SharedPreferences.getInstance();
return NhostClient(
subdomain: 'your-subdomain',
storage: await NHostLocalStorage.init(store: prefs),
);
}
这种实现方式利用了鸿蒙兼容的 shared_preferences 插件,将会话信息安全地存储在设备本地。
dart复制Future<void> handleEmailPasswordAuth(
String email,
String password,
bool isLogin,
) async {
try {
if (isLogin) {
await nhostClient.auth.signInEmailPassword(
email: email,
password: password,
);
} else {
await nhostClient.auth.signUpEmailPassword(
email: email,
password: password,
);
}
print('当前用户: ${nhostClient.auth.currentUser?.email}');
} on AuthException catch (e) {
print('认证错误: ${e.message}');
}
}
注意事项:
Nhost 支持 Google、GitHub 等多种社交登录方式:
dart复制// 初始化时配置提供商
final nhostClient = NhostClient(
subdomain: 'your-subdomain',
authProviders: [
AuthProvider.github,
AuthProvider.google,
],
);
// 触发社交登录
void signInWithProvider(AuthProvider provider) {
final url = nhostClient.auth.getOAuthUrl(provider);
// 使用鸿蒙的 WebView 或外部浏览器打开授权页面
openWebView(url);
}
关键点:
dart复制Future<String?> uploadFile(File file) async {
if (!nhostClient.auth.isAuthenticated) {
throw Exception('请先登录');
}
final fileBytes = await file.readAsBytes();
final fileName = 'user_uploads/${DateTime.now().millisecondsSinceEpoch}';
try {
final response = await nhostClient.storage.uploadBytes(
fileName: fileName,
fileContents: fileBytes,
mimeType: lookupMimeType(file.path) ?? 'application/octet-stream',
);
return response.id;
} on StorageException catch (e) {
print('上传失败: ${e.message}');
return null;
}
}
性能优化建议:
dart复制Future<Uint8List?> downloadFile(String fileId) async {
try {
return await nhostClient.storage.downloadBytes(fileId);
} on StorageException catch (e) {
print('下载失败: ${e.message}');
return null;
}
}
// 获取文件公开URL
String getPublicUrl(String fileId) {
return nhostClient.storage.getPublicUrl(fileId);
}
安全注意事项:
鸿蒙应用的特殊生命周期需要额外处理:
dart复制class AppLifecycleObserver with WidgetsBindingObserver {
final NhostClient client;
AppLifecycleObserver(this.client);
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// 应用回到前台时刷新会话
client.auth.refreshSession().catchError((e) {
print('会话刷新失败: $e');
});
}
}
}
// 在应用初始化时注册观察者
WidgetsBinding.instance.addObserver(AppLifecycleObserver(nhostClient));
对于大文件传输等耗时操作,建议使用鸿蒙的后台任务机制:
dart复制void startBackgroundUpload(List<File> files) {
// 配置后台任务参数
final taskParams = {
'files': files.map((f) => f.path).toList(),
'authToken': nhostClient.auth.currentSession?.accessToken,
};
// 启动后台任务
BackgroundTask.start(
'file_upload_task',
inputData: taskParams,
);
}
注意事项:
dart复制class UserProfile {
final String id;
final String email;
final String? avatarUrl;
final DateTime createdAt;
UserProfile({
required this.id,
required this.email,
this.avatarUrl,
required this.createdAt,
});
factory UserProfile.fromJson(Map<String, dynamic> json) {
return UserProfile(
id: json['id'],
email: json['email'],
avatarUrl: json['avatar_url'],
createdAt: DateTime.parse(json['created_at']),
);
}
}
dart复制class ProfileService {
final NhostClient client;
ProfileService(this.client);
Future<UserProfile?> getProfile() async {
if (!client.auth.isAuthenticated) return null;
final userId = client.auth.currentUser!.id;
final response = await client.graphql.request('''
query GetProfile($userId: uuid!) {
profiles_by_pk(id: $userId) {
id
email
avatar_url
created_at
}
}
''', variables: {'userId': userId});
return UserProfile.fromJson(response.data['profiles_by_pk']);
}
Future<void> updateAvatar(String fileId) async {
await client.graphql.request('''
mutation UpdateAvatar($userId: uuid!, $fileId: uuid!) {
update_profiles_by_pk(
pk_columns: {id: $userId}
_set: {avatar_url: $fileId}
) {
id
}
}
''', variables: {
'userId': client.auth.currentUser!.id,
'fileId': fileId,
});
}
}
dart复制final optimizedClient = NhostClient(
subdomain: 'your-subdomain',
dio: Dio()
..interceptors.add(LogInterceptor()) // 请求日志
..interceptors.add(QueuedInterceptor()) // 请求队列
..interceptors.add(RetryInterceptor()), // 自动重试
);
认证失败
上传中断
GraphQL错误
dart复制// 使用鸿蒙的安全存储保存敏感信息
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
final secureStorage = FlutterSecureStorage();
await secureStorage.write(
key: 'nhost_refresh_token',
value: nhostClient.auth.currentSession?.refreshToken,
);
在Nhost控制台配置行级安全策略:
sql复制-- 示例:限制用户只能访问自己的资料
create policy profile_access_policy
on profiles
for select
using (id = auth.uid());
dart复制final subscription = nhostClient.graphql.subscribe('''
subscription GetMessages {
messages(order_by: {created_at: desc}, limit: 10) {
id
content
created_at
}
}
''').listen((result) {
print('新消息: ${result.data}');
});
dart复制Future<void> callCustomFunction() async {
final response = await nhostClient.functions.call(
'generate-report',
body: {'month': '2023-10'},
);
print('函数响应: ${response.data}');
}
在实际项目中,我发现将 Nhost 与鸿蒙的分布式能力结合,可以创造出许多独特的应用场景。比如利用鸿蒙的跨设备流转功能,配合 Nhost 的实时数据同步,实现无缝的多设备协作体验。一个典型的例子是:用户在手机上开始编辑文档,流转到平板电脑上继续工作,所有变更都通过 Nhost 实时同步到所有设备。