【2025】Flutter跨平台开发鸿蒙GitCode口袋工具

一、前置准备

1、创建Fluuer工程

假设之前已经配置好了关于 flutter 和鸿蒙的环境(没有配置好的可以看我其他文章),在代码保存目录下输入 powershell 进入命令行:

打开 PowerShell输入以下创建Flutter项目命令:

注意项目名称不要有大写字母!

bash 复制代码
flutter create --platforms ohos <projectName>

2、熟悉Flutter工程目录

创建完项目之后使用VSCode打开 Flutter 工程目录(可以不是VSCode,任意一个都可以):

  • 通常会在【lib】文件夹下编写Flutter工程的代码逻辑;
  • 在pubspec.yaml 文件中进行 flutter 工程有关的配置;
  • 而 ohos 文件夹是关于鸿蒙工程项目的文件夹。

3、检查鸿蒙工程

使用 DevEco Studio打开 ohos 鸿蒙工程文件夹,并且打开鸿蒙虚拟机:

由于我们采用的是 Flutter 工程跨平台开发鸿蒙项目,所以我们只需要在 Flutter 工程中编写代码逻辑,然后在Flutter工程中完成编译之后;直接在DevEco Studio中启动该Flutter工程下的 ohos 鸿蒙项目工程文件,就可以直接在虚拟机中展示我们的结果了

二、实现代码逻辑

1、查看API

因为我们要实现的是关于 GitCode 的一个简便口袋工具,所以我们首先要查看一下 GitCode 提供的API接口,以方便我们更好的梳理代码逻辑:

GitCode提供的API:https://docs.gitcode.com/docs/apis/get-api-v-5-search-users/

因为我们的 flutter 工程是使用的 Dart 语言,所以我们只需要关注 Dart语言的开发示例即可。然后再看一下GitCode提供的接口:

bash 复制代码
# 搜索用户APi
https://api.gitcode.com/api/v5/search/users
# 搜索仓库APi

https://api.gitcode.com/api/v5/search/repositories

2、前置准备

再来看一下请求参数:

可以发现无论是搜索仓库还是搜索用户,都需要有用户授权码,那么我们需要在 GitCode上创建一个用户授权码:

通过以上步骤我们就获得了用户授权码。

由于我们需要使用到网络请求,所以我们还需要在我们的Flutter项目中导入 Dio 来实现网络请求:

首先我们点击【Ctrl + Shift + ~】来打开终端,再输入以下命令:

bash 复制代码
flutter pub add dio

导入 Dio 库,并在pubspec.yaml 配置文件中查看Dio库是否导入完成。

3、代码实现

首先我们在【lib】文件夹下创建两个文件夹:

  • 【config】,用于存储配置信息,例如我们刚才申请的用户授权码等隐私信息,不能直接赋值,应该下载配置文件中进行调用。
  • 【utils】,工具类,用于存放一些可能会使用到的工具类,其中将网络请求封装成一个工具类后来使用。

首先是config文件:

Dart 复制代码
class AppConfig {
  AppConfig._();
  static const token = '<自己的用户授权码>';
}

接下来是核心的网络请求工具类,具体实现注释已经加上:

Dart 复制代码
import 'package:dio/dio.dart';

/// GitCode API 客户端封装
/// 本文件集中封装了调用 GitCode API 所需的 HTTP 客户端、数据模型
/// 以及通用的解析逻辑,便于在 UI 层直接复用,避免重复拼装请求。

/// GitCode API 客户端类
/// 
/// 该类是与 GitCode API 交互的核心类,负责:
/// - 统一管理 HTTP 请求配置
/// - 封装所有 API 端点调用
/// - 处理错误和异常情况
/// - 提供便捷的数据访问方法
class GitCodeApiClient {
  /// 创建 GitCode API 客户端实例
  /// 
  /// [dio] - 可选的自定义 Dio 实例,用于特殊配置或测试
  ///         如果未提供,将使用默认配置的 Dio 实例
  GitCodeApiClient({Dio? dio})
      : _dio = dio ?? _createDefaultDio();

  /// Dio 实例,用于执行所有 HTTP 请求
  /// 私有成员变量,外部不能直接访问,确保请求逻辑封装在类内部
  final Dio _dio;
  
  /// 创建默认配置的 Dio 实例
  /// 
  /// 该方法配置了与 GitCode API 交互所需的默认参数,包括:
  /// - 基础 URL
  /// - 超时设置
  /// - 内容类型和响应格式
  /// 
  /// 返回配置好的 Dio 实例
  /// 创建默认配置的 Dio 实例
  /// 
  /// 此静态方法负责初始化与 GitCode API 交互所需的默认 Dio 实例
  /// 配置了所有与 API 通信必要的基础参数
  /// 
  /// 返回配置好的 Dio 实例,可直接用于发起 API 请求
  static Dio _createDefaultDio() {
    return Dio(
      BaseOptions(
        // 默认基准端点指向 GitCode v5 API,所有请求在此路径下拼接
        baseUrl: 'https://api.gitcode.com/api/v5',
        // 连接和读取阶段都限定为 5 秒,能在弱网场景下快速失败,提示用户重试
        connectTimeout: const Duration(seconds: 5),
        receiveTimeout: const Duration(seconds: 5),
        // 设置内容类型为 JSON,与 GitCode API 接口要求匹配
        contentType: Headers.jsonContentType,
        // 设置响应类型为 JSON,自动解析响应体
        responseType: ResponseType.json,
      ),
    );
  }

  /// 通过登录名获取用户详细信息
  /// 
  /// 此方法调用 GitCode API 的 `/users/{username}` 端点,获取特定用户的完整信息
  /// 
  /// [username] - 用户的登录名,将被用于 API 请求
  /// [personalToken] - 可选的个人访问令牌,用于授权访问需要权限的 API 端点
  /// [allowSearchFallback] - 当用户不存在时,是否尝试通过搜索接口查找可能的登录名
  /// 
  /// 返回用户的详细信息对象 [GitCodeUser]
  /// 抛出 [GitCodeApiException] 当发生错误时
  Future<GitCodeUser> fetchUser(
    String username, {
    String? personalToken,
    bool allowSearchFallback = true,
  }) async {
    // 1. 输入验证:去除前后空白字符,避免因用户误输入空格导致请求失败
    final trimmed = username.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('用户名不能为空');
    }

    // 2. 发起请求并处理响应
    try {
      // 构建 GET 请求,使用 Uri.encodeComponent 确保用户名中的特殊字符被正确编码
      final response = await _dio.get<Map<String, dynamic>>(
        '/users/${Uri.encodeComponent(trimmed)}',
        options: Options(
          // 添加认证头和其他请求头
          headers: _buildHeaders(personalToken),
          // 自定义状态码验证逻辑:允许所有 <500 的状态码进入响应处理分支
          // 这样可以对不同错误状态码返回更具针对性的错误信息
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      // 3. 根据 HTTP 状态码处理不同情况
      final statusCode = response.statusCode ?? 0;
      switch (statusCode) {
        case 200: // 请求成功
          // 验证响应数据不为空
          final data = response.data;
          if (data == null) {
            throw const GitCodeApiException('接口返回为空,请稍后重试');
          }
          // 将 JSON 数据转换为 GitCodeUser 对象并返回
          return GitCodeUser.fromJson(data);
        
        case 401: // 未授权
          throw const GitCodeApiException('未授权,请检查 access token 或权限');
        
        case 404: // 用户不存在
          // 尝试通过搜索接口进行用户名回退查找
          if (allowSearchFallback && personalToken != null && personalToken.isNotEmpty) {
            // 调用内部搜索方法查找可能的用户名
            final fallbackLogin = await _searchLoginByKeyword(
              trimmed,
              personalToken: personalToken,
            );
            
            // 如果找到不同的用户名,则使用新用户名重新请求
            if (fallbackLogin != null && fallbackLogin != trimmed) {
              // 关闭二次回退,避免无限递归
              return fetchUser(
                fallbackLogin,
                personalToken: personalToken,
                allowSearchFallback: false,
              );
            }
          }
          // 如果无法找到合适的用户名,返回友好的错误信息
          throw GitCodeApiException(
            '未找到用户 $trimmed,若输入的是展示昵称,请改用登录名或携带 token 搜索。',
          );
        
        default: // 其他错误状态码
// 其他状态码处理
        if (statusCode >= 500) {
          throw GitCodeApiException(
            '服务器错误 (HTTP $statusCode),请稍后重试',
          );
        } else {
          throw GitCodeApiException(
            '查询失败 (HTTP $statusCode),请稍后重试',
          );
        }
      }
    } on DioException catch (error) {
      // 4. 捕获并处理网络请求异常
      _handleDioException(error);
    }
  }
  
  /// 处理 Dio 网络请求异常
  /// 
  /// 此私有方法负责将不同类型的 Dio 异常转换为统一的 GitCodeApiException
  /// 并提供友好的错误消息,帮助用户理解错误原因
  /// 
  /// [error] - DioException 异常对象,包含异常类型和详细信息
  /// 
  /// 抛出统一的 [GitCodeApiException] 异常,包含友好的错误消息
  Never _handleDioException(DioException error) {
    // 根据不同类型的 Dio 异常返回相应的用户友好错误信息
    switch (error.type) {
      // 超时异常
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.receiveTimeout:
      case DioExceptionType.sendTimeout:
        throw const GitCodeApiException('网络连接超时,请检查网络连接');
        
      // 连接错误
      case DioExceptionType.connectionError:
        throw const GitCodeApiException('无法连接到服务器,请检查网络连接或服务器是否可用');
        
      // 未知错误
      case DioExceptionType.unknown:
        throw const GitCodeApiException('未知错误,请稍后重试');
        
      // 服务器错误响应
      case DioExceptionType.badResponse:
        final statusCode = error.response?.statusCode ?? 0;
        if (statusCode == 401) {
          throw const GitCodeApiException('未授权,请检查 access token 或权限');
        } else {
          throw GitCodeApiException('请求失败 (HTTP $statusCode),请稍后重试');
        }
        
      // 请求被取消
      case DioExceptionType.cancel:
        throw const GitCodeApiException('请求已取消');
        
      // 证书错误
      case DioExceptionType.badCertificate:
        throw const GitCodeApiException('证书验证失败,请检查网络环境');
        
      // 默认错误处理
      default:
        throw const GitCodeApiException('请求失败,请稍后重试');
    }
  }

  /// 构建 HTTP 请求头
  /// 
  /// 此私有方法负责为所有 API 请求构建标准的 HTTP 请求头
  /// 包括可选的授权令牌,用于访问需要权限的 API 端点
  /// 
  /// [personalToken] - 可选的个人访问令牌,用于 API 授权
  /// 
  /// 返回构建好的请求头映射 [Map<String, String>]
  Map<String, String> _buildHeaders(String? personalToken) {
    // 创建基础请求头映射
    final headers = <String, String>{};
    
    // 如果提供了个人访问令牌,添加授权头
    if (personalToken != null && personalToken.isNotEmpty) {
      headers['Authorization'] = 'Bearer $personalToken';  // 使用 Bearer 方式的授权头格式
    }
    
    return headers;
  }

  /// 内部方法:搜索可能的用户登录名
  /// 
  /// 此私有方法用于尝试通过搜索接口查找与关键字匹配的真实用户登录名
  /// 主要用于在用户可能输入了显示名称而不是登录名的情况下
  /// 
  /// [keyword] - 搜索关键字,可能是用户的显示名称或部分登录名
  /// [personalToken] - 必需的个人访问令牌,用于授权搜索操作
  /// 
  /// 返回第一个匹配到的用户登录名,如果未找到则返回 null
  Future<String?> _searchLoginByKeyword(
    String keyword, {
    required String personalToken,
  }) async {
    try {
      // 发起搜索请求,限制返回最多1条结果
      final response = await _dio.get<List<dynamic>>(
        '/search/users',
        queryParameters: {
          'q': keyword,
          'per_page': 1,  // 限制返回结果数量,只需要找到一个匹配项即可
          'access_token': personalToken,
        },
        options: Options(
          headers: _buildHeaders(personalToken),
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      if ((response.statusCode ?? 0) == 200) {
        final list = response.data;
        if (list == null || list.isEmpty) {
          return null;
        }
        
        final first = list.first;
        if (first is Map<String, dynamic>) {
          // 只取第一个候选项的 login,命中率通常最高
          return first['login'] as String?;
        }
      } else if ((response.statusCode ?? 0) == 401) {
        // Token 不合法时立即抛出,提示用户调整配置
        throw const GitCodeApiException('搜索需要有效的 access token');
      }
    } on DioException {
      // 出错时返回 null,由调用者决定如何处理
      return null;
    }
    return null;
  }

  /// 搜索用户列表
  /// 
  /// 此方法调用 GitCode API 的 `/search/users` 端点,根据关键字搜索用户
  /// 支持分页查询,可以获取大量用户数据
  /// 
  /// [keyword] - 搜索关键字,用于匹配用户名、全名或其他用户属性
  /// [personalToken] - 个人访问令牌,用于授权访问和提高API限制
  /// [perPage] - 每页显示的用户数量,默认 10,范围 1-50
  /// [page] - 页码,从 1 开始计数,默认 1,范围 1-100
  /// 
  /// 返回符合条件的用户列表 [List<GitCodeSearchUser>]
  /// 抛出 [GitCodeApiException] 当发生错误时
  Future<List<GitCodeSearchUser>> searchUsers({
    required String keyword,
    required String personalToken,
    int perPage = 10,
    int page = 1,
  }) async {
    // 1. 输入验证:关键字为空时抛出异常,去除前后空格避免无效查询
    final trimmed = keyword.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('请输入搜索关键字');
    }

    // 2. 发起搜索请求
    try {
      // 构建带查询参数的 GET 请求
      final response = await _dio.get<List<dynamic>>(
        '/search/users',
        // 设置查询参数
        queryParameters: <String, dynamic>{
          'q': trimmed,
          'access_token': personalToken,
          // clamp 可以阻止业务层传入不合法的分页参数,避免后端报错
          'per_page': perPage.clamp(1, 50),
          'page': page.clamp(1, 100),
        },
        options: Options(
          // 添加认证头和其他请求头
          headers: _buildHeaders(personalToken),
          // 自定义状态码验证逻辑:允许所有 <500 的状态码进入响应处理分支
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      // 3. 根据 HTTP 状态码处理响应
      final statusCode = response.statusCode ?? 0;
      if (statusCode == 401) {
        throw const GitCodeApiException('Token 无效或权限不足,无法搜索用户');
      }
      if (statusCode != 200 || response.data == null) {
        throw GitCodeApiException('搜索用户失败 (HTTP $statusCode)');
      }

      // 4. 处理成功响应,将JSON数据转换为GitCodeSearchUser对象列表
      return response.data!
          .whereType<Map<String, dynamic>>() // 过滤确保元素类型正确
          .map(GitCodeSearchUser.fromJson)  // 转换为对象
          .toList();                        // 转换为列表
    } on DioException catch (error) {
      // 5. 捕获并处理网络请求异常
      _handleDioException(error);
    }
  }

  /// 搜索仓库
  /// 
  /// 此方法调用 GitCode API 的 `/search/repositories` 端点,根据关键字搜索仓库
  /// 支持复杂的过滤条件,包括按编程语言筛选、排序等高级功能
  /// 
  /// [keyword] - 搜索关键字,用于匹配仓库名称、描述或其他仓库属性
  /// [personalToken] - 个人访问令牌,用于授权访问和提高API限制
  /// [language] - 可选的编程语言过滤条件
  /// [sort] - 可选的排序字段,如 'stars'、'forks'、'updated' 等
  /// [order] - 可选的排序方向,支持 'desc'(降序)、'asc'(升序)
  /// [perPage] - 每页显示的仓库数量,默认 10,范围被限制在 1-50 之间
  /// [page] - 页码,从 1 开始计数,默认 1,范围被限制在 1-100 之间
  /// 
  /// 返回符合条件的仓库列表 [List<GitCodeRepository>]
  /// 抛出 [GitCodeApiException] 当发生错误时
  Future<List<GitCodeRepository>> searchRepositories({
    required String keyword,
    required String personalToken,
    String? language,
    String? sort,
    String? order,
    int perPage = 10,
    int page = 1,
  }) async {
    // 1. 输入验证:去除前后空白字符,避免因用户误输入空格导致无效搜索
    final trimmed = keyword.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('请输入搜索关键字');
    }

    // 2. 构建查询参数
    final queryParameters = <String, dynamic>{
      'q': trimmed,                          // 搜索关键字
      'access_token': personalToken,         // 访问令牌
      'per_page': perPage.clamp(1, 50),      // 限制每页数量范围,防止无效请求
      'page': page.clamp(1, 100),            // 限制页码范围,防止无效请求
      // 可选过滤参数,只有在非空时才添加到请求中
      if (language != null && language.isNotEmpty) 'language': language,
      if (sort != null && sort.isNotEmpty) 'sort': sort,
      if (order != null && order.isNotEmpty) 'order': order,
    };

    // 3. 发起搜索请求并处理响应
    try {
      final response = await _dio.get<List<dynamic>>(
        '/search/repositories',             // API 端点
        queryParameters: queryParameters,    // 查询参数
        options: Options(
          // 添加认证头和其他请求头
          headers: _buildHeaders(personalToken),
          // 自定义状态码验证逻辑:允许所有 <500 的状态码进入响应处理分支
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      // 4. 根据 HTTP 状态码处理不同情况
      final statusCode = response.statusCode ?? 0;
      if (statusCode == 401) {  // 未授权错误
        throw const GitCodeApiException('Token 无效或权限不足,无法搜索仓库');
      }
      if (statusCode != 200 || response.data == null) {  // 其他错误状态码或无数据
        throw GitCodeApiException('搜索仓库失败 (HTTP $statusCode)');
      }

      // 5. 成功响应处理:将 JSON 数据转换为 GitCodeRepository 对象列表
      return response.data!
          .whereType<Map<String, dynamic>>() // 过滤确保元素类型正确
          .map(GitCodeRepository.fromJson)   // 转换为对象
          .toList();                         // 转换为列表
    } on DioException catch (error) {
      // 6. 捕获并处理网络请求异常
      _handleDioException(error);
    }
  }
}

/// GitCode 用户信息模型
///
/// 此类表示从 GitCode API 获取的用户基本信息,
/// 包含用户的身份标识、个人资料和统计数据等关键字段
///
/// 主要用于显示用户的基本个人资料和相关统计信息
class GitCodeUser {
  /// 创建 GitCode 用户信息对象
  ///
  /// [login] - 用户的登录名,GitCode 平台上的唯一标识符
  /// [avatarUrl] - 用户头像的完整 URL
  /// [name] - 用户在个人资料中填写的展示昵称,可能为空
  /// [bio] - 个人简介或签名信息
  /// [htmlUrl] - 用户主页链接,通常指向 GitCode Web 界面
  /// [publicRepos] - 用户公开仓库的数量
  /// [followers] - 关注该用户的粉丝数量
  /// [following] - 该用户关注的人数
  /// [createdAt] - 账号创建时间,ISO 格式的时间字符串
  GitCodeUser({
    required this.login,
    required this.avatarUrl,
    this.name,
    this.bio,
    this.htmlUrl,
    this.publicRepos,
    this.followers,
    this.following,
    this.createdAt,
  });

  /// 从 JSON 数据创建用户信息对象
  ///
  /// [json] - API 返回的用户信息 JSON 数据
  /// 返回解析后的 GitCodeUser 实例
  factory GitCodeUser.fromJson(Map<String, dynamic> json) {
    return GitCodeUser(
      login: json['login'] as String? ?? '',
      avatarUrl: json['avatar_url'] as String? ?? '',
      name: json['name'] as String?,
      bio: json['bio'] as String?,
      htmlUrl: json['html_url'] as String?,
      publicRepos: _safeInt(json['public_repos']),
      followers: _safeInt(json['followers']),
      following: _safeInt(json['following']),
      createdAt: json['created_at'] as String?,
    );
  }

  /// GitCode 唯一登录名,可用于后续接口请求
  final String login;

  /// 头像的完整 URL,用于展示用户头像
  final String avatarUrl;

  /// 用户在个人资料中填写的展示昵称,可能为空
  final String? name;

  /// 个人简介或签名信息
  final String? bio;

  /// 用户主页链接,通常指向 GitCode Web
  final String? htmlUrl;

  /// 用户公开仓库的数量
  final int? publicRepos;

  /// 粉丝(followers)数量
  final int? followers;

  /// 关注(following)数量
  final int? following;

  /// 账号创建时间,ISO 时间字符串
  final String? createdAt;
  
  @override
  String toString() => 'GitCodeUser(login: $login, name: $name)';
}

/// 安全地将动态值转换为 int 类型
/// 
/// 此辅助函数用于处理 API 返回的各种可能格式的整数值,确保在解析数据模型时的类型安全
/// - [value]: 来自 API 响应的动态类型值
/// - 返回: 解析后的 int 值,若无法解析则返回 null
int? _safeInt(dynamic value) {
  // 空值直接返回 null
  if (value == null) {
    return null;
  }
  // 如果已经是 int 类型,直接返回
  if (value is int) {
    return value;
  }
  // 处理字符串形式的数字,这是 API 返回中常见的情况
  if (value is String) {
    // 有些字段可能以字符串形式返回整型,此处尝试解析
    return int.tryParse(value);
  }
  // 无法处理的类型返回 null
  return null;
}

/// GitCode API 异常类
/// 
/// 自定义异常类,用于封装 GitCode API 请求过程中发生的各种错误,
/// 提供更友好的错误信息给上层调用者
class GitCodeApiException implements Exception {
  /// 创建 API 异常
  /// 
  /// - [message]: 详细的错误描述信息,通常会展示给用户
  const GitCodeApiException(this.message);

  /// 异常消息,包含了用户可读的错误描述
  final String message;

  @override
  String toString() => 'GitCodeApiException: $message';
}

/// GitCode 搜索用户结果模型
/// 
/// 表示通过 `/search/users` 接口搜索到的用户简要信息,相比完整的 GitCodeUser
/// 只包含了搜索结果列表页展示所需的核心字段
class GitCodeSearchUser {
  /// 创建搜索用户结果
  /// 
  /// - [login]: 用户登录名,唯一标识符
  /// - [avatarUrl]: 用户头像地址
  /// - [name]: 用户显示名称(可选)
  /// - [htmlUrl]: 用户个人主页链接(可选)
  /// - [createdAt]: 用户账号创建时间(可选)
  GitCodeSearchUser({
    required this.login,
    required this.avatarUrl,
    this.name,
    this.htmlUrl,
    this.createdAt,
  });

  /// 从 JSON 数据创建搜索用户结果
  /// 
  /// 从 GitCode API 返回的 JSON 响应中解析用户搜索结果数据,
  /// 为可能为空的字段提供安全的默认值
  factory GitCodeSearchUser.fromJson(Map<String, dynamic> json) {
    return GitCodeSearchUser(
      // 登录名是必需的,但如果 API 返回为空则提供空字符串作为默认值
      login: json['login'] as String? ?? '',
      // 头像地址同样必需,确保 UI 渲染时不会出现空地址
      avatarUrl: json['avatar_url'] as String? ?? '',
      // 以下字段都是可选的,可能为空
      name: json['name'] as String?,
      htmlUrl: json['html_url'] as String?,
      createdAt: json['created_at'] as String?,
    );
  }

  /// 用户唯一登录名,在 GitCode 平台上唯一标识用户
  final String login;

  /// 用户头像的完整 URL,用于在搜索结果列表中展示用户头像
  final String avatarUrl;

  /// 用户的展示名称,可能比 login 更友好,但不是所有用户都设置了此字段
  final String? name;

  /// 用户在 GitCode 平台上的个人主页链接
  final String? htmlUrl;

  /// 用户账号创建时间,ISO 格式的时间字符串
  final String? createdAt;
  
  @override
  String toString() => 'GitCodeSearchUser(login: $login, name: $name)';
}

/// GitCode 仓库信息模型
/// 
/// 表示 GitCode 平台上的代码仓库,包含了仓库的基本信息、统计数据和所有者信息
class GitCodeRepository {
  /// 创建仓库信息
  /// 
  /// - [fullName]: 仓库全名,格式为 "所有者/仓库名"
  /// - [webUrl]: 仓库的 Web 访问地址
  /// - [description]: 仓库描述(可选)
  /// - [language]: 主要编程语言(可选)
  /// - [updatedAt]: 最近更新时间(可选)
  /// - [stars]: Star 数量(可选)
  /// - [forks]: Fork 数量(可选)
  /// - [watchers]: Watcher 数量(可选)
  /// - [ownerLogin]: 所有者登录名(可选)
  /// - [isPrivate]: 是否私有仓库(可选)
  GitCodeRepository({
    required this.fullName,
    required this.webUrl,
    this.description,
    this.language,
    this.updatedAt,
    this.stars,
    this.forks,
    this.watchers,
    this.ownerLogin,
    this.isPrivate,
  });

  /// 从 JSON 数据创建仓库信息
  /// 
  /// 从 GitCode API 返回的 JSON 响应中解析仓库数据,包含了多字段兼容处理和空值安全处理
  factory GitCodeRepository.fromJson(Map<String, dynamic> json) {
    // 先提取 owner 字段,后续用于获取所有者信息
    final owner = json['owner'];
    return GitCodeRepository(
      // 仓库全名,是必需的标识信息
      fullName: json['full_name'] as String? ?? '',
      // 仓库的 Web 访问 URL
      webUrl: json['web_url'] as String? ?? '',
      // 以下为可选字段
      description: json['description'] as String?,
      language: json['language'] as String?,
      updatedAt: json['updated_at'] as String?,
      // 兼容不同的字段名表示 stars 数量
      stars: _safeInt(json['stargazers_count']) ?? _safeInt(json['stars_count']),
      forks: _safeInt(json['forks_count']),
      watchers: _safeInt(json['watchers_count']),
      // 处理所有者信息,兼容不同的字段命名
      ownerLogin: owner is Map<String, dynamic>
          // 兼容不同字段命名(name/path),以防接口字段发生差异
          ? owner['name'] as String? ?? owner['path'] as String?
          : null,
      // 安全地解析布尔值字段
      isPrivate: _safeBool(json['private']),
    );
  }

  /// 仓库的全名,格式为 "owner/repo",是仓库的唯一标识符之一
  final String fullName;

  /// 仓库在 GitCode 平台上的 Web 访问 URL
  final String webUrl;

  /// 仓库的描述信息,通常用于概述仓库的用途和特性
  final String? description;

  /// 仓库使用的主要编程语言,如 "Dart", "Java" 等
  final String? language;

  /// 仓库最近一次更新的时间,ISO 格式的时间字符串
  final String? updatedAt;

  /// 仓库获得的 Star 数量,优先读取 `stargazers_count`,如果为空则尝试读取 `stars_count`
  final int? stars;

  /// 仓库被 Fork 的数量
  final int? forks;

  /// 仓库的 Watcher 数量
  final int? watchers;

  /// 仓库所有者的登录名或路径
  final String? ownerLogin;

  /// 标识仓库是否为私有仓库
  final bool? isPrivate;
  
  @override
  String toString() => 'GitCodeRepository(name: $fullName, stars: $stars)';
}

/// 安全地将动态值转换为 bool 类型
/// 
/// 此辅助函数用于处理 API 返回的各种可能格式的布尔值,确保在解析数据模型时的类型安全
/// - [value]: 来自 API 响应的动态类型值
/// - 返回: 解析后的 bool 值,若无法解析则返回 null
bool? _safeBool(dynamic value) {
  // 空值直接返回 null
  if (value == null) {
    return null;
  }
  // 如果已经是 bool 类型,直接返回
  if (value is bool) {
    return value;
  }
  // 处理数值形式的布尔值,常见于 API 返回的 0/1 表示
  if (value is int) {
    // GitCode 某些布尔字段会用 0/1 表示
    return value != 0;
  }
  // 处理字符串形式的布尔值
  if (value is String) {
    final lowerValue = value.toLowerCase();
    // 检查常见的真值表示
    if (value == '1' || lowerValue == 'true') {
      return true;
    }
    // 检查常见的假值表示
    if (value == '0' || lowerValue == 'false') {
      return false;
    }
  }
  // 无法处理的类型返回 null
  return null;
}

4、效果展示

当前端页面书写完毕之后,点击 main.dart 文件中的 main 函数进行启动编译flutter项目:

编译完成之后打开 DevEco Studio 启动该Flutter项目下的鸿蒙工程,即可检查效果:

完整代码链接如下,可用 git 进行拉取:

GitCode链接:https://gitcode.com/gcw_aa8Ilbos/gitCode

bash 复制代码
git clone https://gitcode.com/gcw_aa8Ilbos/gitCode.git
相关推荐
A懿轩A13 小时前
【2025版 OpenHarmony】GitCode 口袋工具 v1.0.1 更新发布:Flutter + HarmonyOS 封装导航栏进行跳转
flutter·harmonyos·openharmony·gitcode·开源鸿蒙
是Yu欸8 天前
仓颉迁移实战:将 Node.js 微服务移植到 Cangjie 的工程化评测
微服务·云原生·开源·node.js·vim·gitcode·cangjie
数式Oinone11 天前
继荣获GitCode G-Star认证后,数式Oinone入选2025年GitCode百大开源项目
低代码·开源·低代码平台·gitcode·数式oinone
GitCode官方11 天前
面壁智能入驻 GitCode:端侧 AI 开发获全新生产力引擎
人工智能·gitcode
GitCode官方12 天前
GitCode 同步发布百度 ERNIE-4.5-VL-28B-A3B-Thinking 多模态大模型
百度·gitcode
落798.16 天前
基于 GitCode 云端环境的 CANN ops-math 算子库深度测评:Ascend NPU 上的数学引擎解析
人工智能·gitcode
GitCode官方17 天前
提贡献得京东卡|GitCode & BISHENG 开源贡献征集令活动开启
开源·gitcode
GitCode官方18 天前
GitCode「开源星期六」第三期回顾:鸿蒙 AI 融合开发的新突破与实践路径
开源·gitcode
摘星编程18 天前
昇腾NPU性能调优实战:INT8+批处理优化Mistral-7B全记录
人工智能·华为·gitcode·昇腾