【开源鸿蒙跨平台开发先锋训练营】Flutter 我的选项卡中设定配置信息
通过应用界面来设定网络访问信息,来解决直接写在代码中的问题。同时将配置信息存在手机上,以供下次打开应用是使用。
目的:
通过应用界面来设定网络访问信息,来解决直接写在代码中的问题。同时将配置信息存在手机上,以供下次打开应用是使用。
推荐方案了解
方案概览(简短)
简单方案:
shared_preferences存全部字段 + 用Provider或ValueNotifier暴露运行时设置。优点:实现最简单;缺点:Token 明文存储,安全性差。推荐混合方案:
shared_preferences存projectID/domain/perPage,flutter_secure_storage存token;全局用ChangeNotifier/Provider或Riverpod管理。优点:平衡易用与安全。复杂/结构化方案:
hive(可选加密)或drift(sqlite)用于复杂或大量配置,支持类型、模型和批量查询。优点:性能和结构好;缺点:稍复杂。只在内存 + 导入/导出: 不自动持久化,只提供导入/导出配置文件(适合安全需求极高,但每次需手动输入或导入的场景)。
如何在应用中结构化(推荐做法)
模型: 建一个
Settings数据类(包含projectID、domain、token、perPage等),并实现load()/save()方法。全局状态: 使用
ChangeNotifier+Provider(或StateNotifier+ Riverpod)在runApp之前加载并注入,其他页面通过Provider.of/ Consumer 获取和修改。敏感数据:
token使用flutter_secure_storage保存;非敏感的用shared_preferences以便快速读取。启动流程:
在
main()调用WidgetsFlutterBinding.ensureInitialized()。异步加载
Settings(从shared_preferences/secure_storage)。将加载好的
Settings注入到Provider,然后runApp(...)。
AI 修改要点
1. 我将修改 main.dart 添加
Settings、在main()中加载并注入Provider,并更新ProfilePage为可编辑并保存设置;同时更新 pubspec.yaml 添加依赖。现在应用补丁。2. 现在我将应用补丁:1) 更新 pubspec.yaml 添加依赖;2) 修改 main.dart 添加
Settings、在main()加载并注入Provider,并更新MirrorPage与ProfilePage。3. 我将修改
_MirrorPageState:延迟在首帧后加载数据,并用Provider中的Settings替代原来的常量。4. 已完成实现:我在 main.dart 中添加了
Settings(使用shared_preferences+flutter_secure_storage),在main()启动前加载并通过Provider注入;更新了MirrorPage以使用运行时设置并延迟首帧后加载;将ProfilePage改为可编辑并能保存/重置配置。也已更新 pubspec.yaml 添加依赖。
验证运行
发现在安卓平台没有问题(右边截屏),在鸿蒙模拟器上首页显示失败(左边截屏),如下:


鸿蒙运行问题分析
首先怀疑是否有新引进的插件是需要鸿蒙适配的,如下为pubspec.yaml的修改
diff --git a/pubspec.yaml b/pubspec.yaml
index 8bd8f06..b55fc16 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -37,6 +37,9 @@ dependencies:
http: ^1.6.0
pull_to_refresh: ^2.0.0
infinite_scroll_pagination: ^5.1.1
+ shared_preferences: ^2.1.1^M
+ flutter_secure_storage: ^8.0.0^M
+ provider: ^6.0.5^M
dev_dependencies:
flutter_test:
对于新追加的三个依赖,这里默认都是使用的上游版本,
- shared_preferences: https://pub.dev/packages/shared_preferences
- flutter_secure_storage:https://pub.dev/packages/flutter_secure_storage
- provider: https://pub.dev/packages/provider
默认是没有对鸿蒙的适配的。然后在鸿蒙适配的一览表中,https://gitcode.com/openharmony-tpc/flutter_packages,可以看到有对这些依赖的适配:
- shared_preferences: OpenHarmony shared_preferences_ohos
- flutter_secure_storage:OH fluttertpc_flutter_secure_storage
- provider: https://pub.dev/packages/provider
尝试如下指定还是不行
# shared_preferences: ^2.1.1
shared_preferences:
git:
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
path: "packages/shared_preferences/shared_preferences"
# flutter_secure_storage: ^8.0.0
flutter_secure_storage_ohos:
git:
url: https://gitcode.com/openharmony-sig/fluttertpc_flutter_secure_storage.git
path: flutter_secure_storage_ohos
# provider: ^6.0.5
path_provider:
git:
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
path: "packages/path_provider/path_provider"
如上指定时,Android和web编译会报如下编译错误:
PS D:\flutter\projects\pipeline\lib> flutter build apk
Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source!
Changing current working directory to: D:\flutter\projects\pipeline
Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source!
Error: Couldn't resolve the package 'flutter_secure_storage' in 'package:flutter_secure_storage/flutter_secure_storage.dart'.
Error: Couldn't resolve the package 'provider' in 'package:provider/provider.dart'.
lib/main.dart:7:8: Error: Not found: 'package:flutter_secure_storage/flutter_secure_storage.dart'
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
^
lib/main.dart:8:8: Error: Not found: 'package:provider/provider.dart'
import 'package:provider/provider.dart';
^
lib/main.dart:30:9: Error: Type 'FlutterSecureStorage' not found.
final FlutterSecureStorage _secure = FlutterSecureStorage();
^^^^^^^^^^^^^^^^^^^^
lib/main.dart:17:5: Error: Undefined name 'ChangeNotifierProvider'.
ChangeNotifierProvider.value(
^^^^^^^^^^^^^^^^^^^^^^
lib/main.dart:30:9: Error: 'FlutterSecureStorage' isn't a type.
final FlutterSecureStorage _secure = FlutterSecureStorage();
^^^^^^^^^^^^^^^^^^^^
lib/main.dart:30:40: Error: Method not found: 'FlutterSecureStorage'.
final FlutterSecureStorage _secure = FlutterSecureStorage();
^^^^^^^^^^^^^^^^^^^^
lib/main.dart:241:24: Error: The getter 'Provider' isn't defined for the type '_MirrorPageState'.
- '_MirrorPageState' is from 'package:pipeline/main.dart' ('lib/main.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'Provider'.
final settings = Provider.of<Settings>(context, listen: false);
^^^^^^^^
lib/main.dart:272:24: Error: The getter 'Provider' isn't defined for the type '_MirrorPageState'.
- '_MirrorPageState' is from 'package:pipeline/main.dart' ('lib/main.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'Provider'.
final settings = Provider.of<Settings>(context, listen: false);
^^^^^^^^
lib/main.dart:454:24: Error: The getter 'Provider' isn't defined for the type '_ProfilePageState'.
- '_ProfilePageState' is from 'package:pipeline/main.dart' ('lib/main.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'Provider'.
final settings = Provider.of<Settings>(context, listen: false);
^^^^^^^^
lib/main.dart:474:22: Error: The getter 'Provider' isn't defined for the type '_ProfilePageState'.
- '_ProfilePageState' is from 'package:pipeline/main.dart' ('lib/main.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'Provider'.
final settings = Provider.of<Settings>(context);
^^^^^^^^
Unhandled exception:
FileSystemException(uri=org-dartlang-untranslatable-uri:package%3Aflutter_secure_storage%2Fflutter_secure_storage.dart; message=StandardFileSystem only supports file:* and data:* URIs)
#0 StandardFileSystem.entityForUri (package:front_end/src/api_prototype/standard_file_system.dart:45)
#1 asFileUri (package:vm/kernel_front_end.dart:1002)
#2 writeDepfile (package:vm/kernel_front_end.dart:1165)
<asynchronous suspension>
#3 FrontendCompiler.compile (package:frontend_server/frontend_server.dart:729)
<asynchronous suspension>
#4 starter (package:frontend_server/starter.dart:102)
<asynchronous suspension>
#5 main (file:///D:/Code/gitcode/flutter3.35/engine/src/flutter/third_party/dart/pkg/frontend_server/bin/frontend_server_starter.dart:13)
<asynchronous suspension>
Target kernel_snapshot_program failed: Exception
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:compileFlutterBuildRelease'.
> Process 'command 'D:\flutter\sdks\flutter_flutter\bin\flutter.bat'' finished with non-zero exit value 1
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 19s
Running Gradle task 'assembleRelease'... 19.7s
Gradle task assembleRelease failed with exit code 1
PS D:\flutter\projects\pipeline\lib>
不知道该如何修改了,后面继续调查该如何使用适配鸿蒙的插件吧
代码修改(main.dart)
import 包
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:provider/provider.dart';
main()
初始化,加载settings, 并通过ChangeNotifierProvider将配置信息传递给runApp
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final settings = Settings();
await settings.load();
runApp(
ChangeNotifierProvider.value(
value: settings,
child: const MyApp(),
),
);
}
继承于ChangeNotifier的Settings
通过SharedPreferences和FlutterSecureStorage实现对配置信息的加载和保存
class Settings extends ChangeNotifier {
String projectID = '28894006';
String domain = 'gitlab.com';
String token = '';
int perPage = 20;
final FlutterSecureStorage _secure = FlutterSecureStorage();
Future<void> load() async {
final prefs = await SharedPreferences.getInstance();
projectID = prefs.getString('projectID') ?? projectID;
domain = prefs.getString('domain') ?? domain;
perPage = prefs.getInt('perPage') ?? perPage;
token = await _secure.read(key: 'token') ?? token;
notifyListeners();
}
Future<void> save() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('projectID', projectID);
await prefs.setString('domain', domain);
await prefs.setInt('perPage', perPage);
await _secure.write(key: 'token', value: token);
notifyListeners();
}
}
_MirrorPageState
更新为通过Settings来获取配置信息。
class _MirrorPageState extends State<MirrorPage> {
...
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadFirstPage();
});
}
...
Future<void> _loadFirstPage() async {
...
final settings = Provider.of<Settings>(context, listen: false);
final perPage = settings.perPage;
_pageSize = perPage;
final newItems = await getPipelinesPage(settings.projectID, settings.domain, settings.token, 1, perPage);
...
}
Future<void> _loadMore() async {
...
final settings = Provider.of<Settings>(context, listen: false);
final newItems = await getPipelinesPage(settings.projectID, settings.domain, settings.token, _page, _pageSize);
...
}
...
}
ProfilePage
主要使用了TextEditingController,Provider,ListView,Expanded,TextField,SizedBox,InputDecoration,ElevatedButton,OutlinedButton等类和组件实现了配置页面的显示生成和配置信息的保存及提取。
// 我的页面:编辑并保存 Settings
class ProfilePage extends StatefulWidget {
ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
late TextEditingController _projectController;
late TextEditingController _domainController;
late TextEditingController _tokenController;
late TextEditingController _perPageController;
bool _inited = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_inited) {
final settings = Provider.of<Settings>(context, listen: false);
_projectController = TextEditingController(text: settings.projectID);
_domainController = TextEditingController(text: settings.domain);
_tokenController = TextEditingController(text: settings.token);
_perPageController = TextEditingController(text: settings.perPage.toString());
_inited = true;
}
}
@override
void dispose() {
_projectController.dispose();
_domainController.dispose();
_tokenController.dispose();
_perPageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final settings = Provider.of<Settings>(context);
return Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: [
const SizedBox(height: 8),
TextField(
controller: _projectController,
decoration: const InputDecoration(labelText: 'Project ID'),
),
const SizedBox(height: 12),
TextField(
controller: _domainController,
decoration: const InputDecoration(labelText: 'Domain'),
),
const SizedBox(height: 12),
TextField(
controller: _tokenController,
decoration: const InputDecoration(labelText: 'Private Token'),
obscureText: true,
),
const SizedBox(height: 12),
TextField(
controller: _perPageController,
decoration: const InputDecoration(labelText: 'Per Page'),
keyboardType: TextInputType.number,
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () async {
settings.projectID = _projectController.text.trim();
settings.domain = _domainController.text.trim();
settings.token = _tokenController.text;
settings.perPage = int.tryParse(_perPageController.text) ?? settings.perPage;
await settings.save();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已保存')));
},
child: const Text('保存'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: () {
_projectController.text = settings.projectID;
_domainController.text = settings.domain;
_tokenController.text = settings.token;
_perPageController.text = settings.perPage.toString();
},
child: const Text('重置'),
),
),
],
),
const SizedBox(height: 20),
Text('当前配置: project=${settings.projectID}, domain=${settings.domain}, perPage=${settings.perPage}'),
],
),
);
}
}
总结
鸿蒙的适配还是有待完善。
- 适配的代码还没有贡献到上游。虽然做了很多努力,也已经做了很多适配,但是还停留在单独维护的代码库里。为开发者的使用增加了一些麻烦。
- 代码库的适配方法还不是很明确。对于初学者比较难于发现怎么去使用已经做了鸿蒙适配的代码库。可能有的适配的代码库里已经有使用说明文档关于如何使用,不过如果有一个文档能够将各种情况的使用方法都罗列在一起,应该会给初学者带来很大方便。这里可能也涉及到一些术语概念,容易给初学者造成混乱。比如:
- 有些包是在flutter_package中的, 比如 flutter_packages - AtomGit | GitCode
- 有些包是单独的库,比如fluttertpc_flutter_secure_storage - AtomGit | GitCode
- 如何做到其他平台使用上游库,鸿蒙使用适配库?
更多推荐




所有评论(0)