开源鸿蒙跨平台:Flutter 个人主页开发
/ 内容类型枚举:控制页面展示不同的内容summary, // 默认:个人信息摘要主页followedUsers, // 关注用户列表页// 全局变量// 网络请求实例// 当前登录用户信息// 用户信息加载状态String?_userError;// 用户信息加载错误信息// 关注用户数量// 个人仓库数量// 默认展示摘要页// 关注用户列表// 关注用户加载状态枚举是页面内容切换的核心开关所
一、页面核心定位 & 功能总览
✅ 页面核心
本页面 ProfilePage 是项目的个人中心页面,也是用户数据的聚合展示页面,核心承载「个人信息 + 收藏仓库 + 关注用户」三大核心模块,所有数据都是当前登录用户的专属私有数据。
✅ 核心功能清单
- 加载并展示「当前登录用户」的完整个人信息(用户名、头像、简介、仓库数 / 粉丝数 / 关注数统计);
- 展示本地收藏仓库、关注用户、个人仓库的数量统计卡片;
- 点击统计卡片可跳转至「收藏仓库列表 / 关注用户列表」,加载并展示完整列表数据;
- 列表页面支持返回个人中心主页、空数据友好提示、加载状态展示;
- 支持页面数据一键刷新,重新加载用户信息和统计数据;
- 所有收藏 / 关注数据基于本地持久化存储,个人信息基于接口请求获取。
用户打开ProfilePage
↓
加载当前用户信息(API)
↓
加载统计数据(并行):
├─ 从SharedPreferences获取收藏仓库数量
├─ 从SharedPreferences获取关注用户数量
├─ 从API获取用户仓库数量
↓
显示摘要页面
↓
用户点击卡片:
├─ 点击"收藏仓库" → 从SharedPreferences获取仓库名称列表 → 通过API补充详细信息 → 显示列表
├─ 点击"关注用户" → 从SharedPreferences获取用户名列表 → 通过API补充详细信息 → 显示列表
├─ 点击"我的仓库"
↓
用户点击返回按钮 → 回到摘要页面
二、数据来源
2.1 「我的」数据分类 & 对应获取方式
✔ 第一类:当前登录用户的个人核心信息 - 来源:【服务端 API 接口请求】
- 包含数据:用户名 (
login)、头像 (avatarUrl)、昵称 (name)、个人简介 (bio)、公开仓库数 (publicRepos)等; - 获取方式:调用
api_client.dart中的getCurrentUser()方法; - 核心原理:通过配置的
personalToken个人令牌,请求$baseUrl/user接口,接口返回当前登录用户的完整信息,通过GitCodeUser.fromJson()解析为实体类对象,供页面展示。
✔ 第二类:我的收藏仓库数据 - 来源:【本地持久化存储 + 服务端补全详情】
- 包含数据:所有手动收藏的仓库列表;
- 获取方式:
- 调用
PersistenceStorage.getStarredRepositories(),从本地鸿蒙版 shared_preferences 中,获取「收藏的仓库全名列表」(仓库唯一标识:owner/repoName); - 拿到仓库全名后,分批调用接口
searchRepositories,根据仓库全名获取仓库的完整详情数据,组装成仓库列表;
- 调用
- 核心原理:APP 重启后数据不丢失,详情数据需要从服务端获取,保证数据最新。
✔ 第三类:我的关注用户数据 - 来源:【本地持久化存储 + 服务端补全详情】
- 包含数据:所有手动关注的用户列表;
- 获取方式:
- 调用
PersistenceStorage.getFollowedUsers(),从本地鸿蒙版 shared_preferences 中,获取「关注的用户名列表」(用户唯一标识:login); - 拿到用户名后,分批调用接口
getUserDetails,根据用户名获取用户的完整详情数据,组装成用户列表;
- 调用
- 核心原理:和收藏仓库逻辑完全一致,详情数据从服务端获取。
✔ 第四类:我的个人仓库数据 - 来源:【服务端 API 接口请求】
- 包含数据:当前登录用户创建的所有公开仓库;
- 获取方式:调用
api_client.dart中的getUserRepositories方法,传入当前用户的login,请求接口获取仓库列表;
关于本项目中数据调用形式的差异化设计,核心原因在于 API 接口的功能限制:目前 AtomGit 开放 API 暂未提供直接关联「当前登录用户关注关系、仓库收藏状态」的查询接口,无法通过单次请求获取用户的关注列表、收藏列表等关联数据。
因此,项目采用 本地+ 服务端 的混合方案实现相关功能:本地通过鸿蒙适配版 shared_preferences 存储关注的用户名、收藏的仓库标识(fullName),确保 APP 重启后数据不丢失;后续需展示完整详情时,再通过存储的标识分批请求服务端接口,补全仓库 / 用户的详细信息。
若后续 AtomGit 开放 API 支持直接查询个人关注列表、收藏列表的完整数据(无需本地存储标识),可无缝切换为纯服务端数据调用方案,以 API 返回的真实数据为准,现有页面逻辑无需大幅重构,仅需调整数据加载的源头即可。
2.2 代码中 getCurrentUser() 接口核心解析(用户信息的核心来源)
下方的这个方法,是获取当前登录用户信息的核心接口,也是整个页面的「数据入口」:
Future<GitCodeUser> getCurrentUser() async {
final uri = Uri.parse('$baseUrl/user').replace(
queryParameters: {
'access_token': ApiConfig.personalToken, // 核心:个人令牌,身份认证凭证
},
);
try {
final response = await http.get(uri).timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
final jsonData = jsonDecode(response.body);
return GitCodeUser.fromJson(jsonData); // 解析为用户实体类
} else if (response.statusCode == 401) {
throw Exception('Token无效或已过期,请检查配置'); // Token认证失败
} else {
throw Exception('获取用户信息失败,状态码: ${response.statusCode}');
}
} catch (e) {
rethrow;
}
}
核心要点:
- 身份认证:通过
personalToken令牌请求接口,接口会根据令牌识别「当前登录的用户」,返回该用户的专属数据,这是「我的数据」的核心认证方式; - 数据解析:接口返回的 JSON 数据,通过
GitCodeUser.fromJson()方法解析为实体类,页面中直接调用实体类的属性(如_currentUser!.login),类型安全,不会出错; - 异常处理:包含超时、网络错误、Token 失效等异常捕获,保证页面不会崩溃。
三、所有核心功能 分模块详细解析(按代码执行顺序,逐功能拆解)
3.1 初始化相关(页面启动的基础准备)
✔ 全局变量定义 & 枚举类作用
// 内容类型枚举:控制页面展示不同的内容
enum ContentType {
summary, // 默认:个人信息摘要主页
starredRepos, // 收藏仓库列表页
followedUsers, // 关注用户列表页
}
// 全局变量
final GitCodeApiClient _apiClient = GitCodeApiClient(); // 网络请求实例
GitCodeUser? _currentUser; // 当前登录用户信息
bool _loadingUser = true; // 用户信息加载状态
String? _userError; // 用户信息加载错误信息
int _starredCount = 0; // 收藏仓库数量
int _followedCount = 0; // 关注用户数量
int _repoCount = 0; // 个人仓库数量
ContentType _currentContentType = ContentType.summary; // 默认展示摘要页
List<GitCodeRepository> _starredRepos = []; // 收藏仓库列表
List<GitCodeUser> _followedUsers = []; // 关注用户列表
bool _loadingStarredRepos = false; // 收藏仓库加载状态
bool _loadingFollowedUsers = false;// 关注用户加载状态
核心作用:
ContentType枚举是页面内容切换的核心开关:通过修改枚举值,页面会根据枚举值渲染不同的内容(主页 / 收藏列表 / 关注列表);- 所有状态变量私有化,通过
setState()更新状态,驱动页面刷新,符合 Flutter 的「数据驱动 UI」思想; - 所有加载状态独立管理,避免页面出现「全局加载」的卡顿体验。
3.2 核心数据加载方法(页面的核心逻辑,所有数据的源头)
✔ 初始化加载所有数据 _loadInitialData()
Future<void> _loadInitialData() async {
await _loadCurrentUser(); // 先加载用户信息
await _loadAllStats(); // 再加载统计数据
}
✔ 加载当前用户信息 _loadCurrentUser()
Future<void> _loadCurrentUser() async {
setState(() {_loadingUser = true;_userError = null;});
try {
final user = await _apiClient.getCurrentUser(); // 调用核心接口
setState(() {_currentUser = user;_loadingUser = false;});
} catch (e) {
setState(() {_userError = e.toString();_loadingUser = false;});
}
}
✔ 加载所有统计数据 _loadAllStats()
Future<void> _loadAllStats() async {
try {
await Future.wait([ // 并行加载,提升加载效率
_loadStarredRepositoriesCount(),
_loadFollowedUsersCount(),
_loadUserRepositoriesCount(),
]);
} catch (e) {print('加载统计数据失败: $e');}
}
✔ 三个统计数据加载方法(收藏数 / 关注数 / 仓库数)
// 收藏仓库数量:本地存储中取列表长度
Future<void> _loadStarredRepositoriesCount() async {
final starred = await PersistenceStorage.getStarredRepositories();
setState(() {_starredCount = starred.length;});
}
// 关注用户数量:本地存储中取列表长度
Future<void> _loadFollowedUsersCount() async {
final followed = await PersistenceStorage.getFollowedUsers();
setState(() {_followedCount = followed.length;});
}
// 个人仓库数量:从用户信息中直接获取(接口返回的字段)
Future<void> _loadUserRepositoriesCount() async {
if (_currentUser == null) return;
setState(() {_repoCount = _currentUser?.publicRepos ?? 0;});
}
3.3 收藏仓库 / 关注用户 列表加载核心功能(核心业务逻辑)
Future<void> _loadStarredRepositories() async {
setState(() {
_currentContentType = ContentType.starredRepos; // 切换页面内容为收藏列表
_loadingStarredRepos = true;
_starredRepos.clear();
});
try {
final repoFullNames = await PersistenceStorage.getStarredRepositories(); // 本地取收藏标识
if (repoFullNames.isEmpty) {setState(() {_loadingStarredRepos = false;});return;}
final List<GitCodeRepository> loadedRepos = [];
const concurrentLimit = 3; // 分批并发请求,避免接口限流
for (var i = 0; i < repoFullNames.length; i += concurrentLimit) {
final batch = repoFullNames.sublist(i, i + concurrentLimit > repoFullNames.length ? repoFullNames.length : i + concurrentLimit);
final batchResults = await Future.wait(batch.map((fullName) => _fetchRepositoryByFullName(fullName)));
// 过滤空数据+去重,保证列表数据干净
for (final result in batchResults) {
if (result != null && !loadedRepos.any((repo) => repo.fullName == result.fullName)) {
loadedRepos.add(result);
}
}
}
setState(() {_starredRepos = loadedRepos;_loadingStarredRepos = false;});
} catch (e) {
print('加载收藏仓库失败: $e');
setState(() {_loadingStarredRepos = false;});
}
}



欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)