
1. 项目背景与核心需求天气预报应用作为移动开发领域的经典练手项目看似简单却涵盖了网络请求、数据解析、UI渲染等核心开发技能。这次我们要用Flutter框架结合retrofit库对接和风天气API打造一个鸿蒙系统兼容的天气应用城市卡片组件。选择这个技术栈有几个关键考量首先Flutter的跨平台特性让我们可以一套代码同时覆盖Android、iOS和鸿蒙系统其次retrofit作为Dart语言的类型安全HTTP客户端能极大简化API请求和响应处理的复杂度最后和风天气API提供稳定可靠的天气数据服务免费套餐完全够个人开发者使用。2. 技术选型与准备工作2.1 开发环境搭建首先确保你的开发环境已经配置好Flutter SDK建议3.0以上版本和鸿蒙开发工具。在pubspec.yaml中添加以下依赖dependencies: flutter: sdk: flutter retrofit: ^3.3.1 dio: ^4.0.6 json_annotation: ^4.8.0 logger: ^1.1.0 dev_dependencies: build_runner: ^2.1.11 retrofit_generator: ^3.1.0 json_serializable: ^6.5.4运行flutter pub get安装依赖后我们需要在和风天气官网注册开发者账号获取API Key。建议选择免费版的开发版套餐每天1000次调用足够开发测试使用。2.2 项目结构设计合理的项目结构能提高代码可维护性。建议采用以下目录结构lib/ ├── api/ # API相关文件 │ ├── weather_api.dart │ └── models/ # 数据模型 ├── widgets/ # 自定义组件 │ └── weather_card.dart ├── utils/ # 工具类 │ └── constants.dart └── main.dart # 应用入口3. API接口实现3.1 定义数据模型根据和风天气API文档我们需要先定义返回数据的模型类。以获取城市天气为例JsonSerializable() class WeatherResponse { final String code; final String updateTime; final String fxLink; final Now now; WeatherResponse({ required this.code, required this.updateTime, required this.fxLink, required this.now, }); factory WeatherResponse.fromJson(MapString, dynamic json) _$WeatherResponseFromJson(json); MapString, dynamic toJson() _$WeatherResponseToJson(this); } JsonSerializable() class Now { final String obsTime; final String temp; final String feelsLike; final String icon; final String text; final String wind360; final String windDir; final String windScale; final String windSpeed; final String humidity; final String precip; final String pressure; final String vis; final String cloud; final String dew; Now({ required this.obsTime, required this.temp, required this.feelsLike, // 其他字段... }); factory Now.fromJson(MapString, dynamic json) _$NowFromJson(json); MapString, dynamic toJson() _$NowToJson(this); }3.2 使用retrofit定义API接口创建weather_api.dart文件使用retrofit定义接口import package:retrofit/retrofit.dart; import package:dio/dio.dart; part weather_api.g.dart; RestApi(baseUrl: https://devapi.qweather.com/v7/) abstract class WeatherApi { factory WeatherApi(Dio dio, {String baseUrl}) _WeatherApi; GET(weather/now) FutureWeatherResponse getWeatherNow({ Query(location) required String location, Query(key) required String key, Query(lang) String lang zh, Query(unit) String unit m, }); }运行以下命令生成代码flutter pub run build_runner build4. 天气卡片UI实现4.1 基础卡片布局在weather_card.dart中创建WeatherCard组件class WeatherCard extends StatelessWidget { final WeatherResponse weather; const WeatherCard({super.key, required this.weather}); override Widget build(BuildContext context) { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(), const SizedBox(height: 16), _buildWeatherInfo(), const SizedBox(height: 16), _buildExtraInfo(), ], ), ), ); } Widget _buildHeader() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( weather.now.text, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), Image.network( https://a.hecdn.net/img/common/icon/202106d/${weather.now.icon}.png, width: 48, height: 48, ), ], ); } // 其他构建方法... }4.2 响应式设计考虑为了让卡片在不同设备上都有良好表现我们需要添加响应式设计LayoutBuilder( builder: (context, constraints) { final isSmallScreen constraints.maxWidth 400; return Flex( direction: isSmallScreen ? Axis.vertical : Axis.horizontal, children: [ // 根据屏幕尺寸调整布局 ], ); }, )5. 数据获取与状态管理5.1 实现数据获取逻辑创建一个WeatherRepository类封装API调用class WeatherRepository { final WeatherApi _api; WeatherRepository({required String apiKey}) : _api WeatherApi(Dio(), baseUrl: https://devapi.qweather.com/v7/); FutureWeatherResponse getWeather(String location) async { try { final response await _api.getWeatherNow( location: location, key: const String.fromEnvironment(WEATHER_API_KEY), ); if (response.code ! 200) { throw Exception(API Error: ${response.code}); } return response; } on DioException catch (e) { throw Exception(Network Error: ${e.message}); } } }5.2 状态管理方案选择对于简单的天气卡片使用StatefulWidget或Provider就足够了class WeatherCardContainer extends StatefulWidget { final String location; const WeatherCardContainer({super.key, required this.location}); override StateWeatherCardContainer createState() _WeatherCardContainerState(); } class _WeatherCardContainerState extends StateWeatherCardContainer { late final WeatherRepository _repository; WeatherResponse? _weather; String? _error; override void initState() { super.initState(); _repository WeatherRepository( apiKey: const String.fromEnvironment(WEATHER_API_KEY), ); _loadWeather(); } Futurevoid _loadWeather() async { try { final weather await _repository.getWeather(widget.location); setState(() { _weather weather; _error null; }); } catch (e) { setState(() { _error e.toString(); }); } } override Widget build(BuildContext context) { if (_error ! null) { return ErrorWidget(_error!); } if (_weather null) { return const Center(child: CircularProgressIndicator()); } return WeatherCard(weather: _weather!); } }6. 鸿蒙系统适配要点6.1 平台特性处理虽然Flutter是跨平台的但鸿蒙系统还是有些特性需要注意import package:flutter/foundation.dart show defaultTargetPlatform; import package:flutter/material.dart show TargetPlatform; // 检测是否为鸿蒙系统 bool get isHarmonyOS { return defaultTargetPlatform TargetPlatform.android Platform.environment.containsKey(HARMONY_OS); }6.2 性能优化建议鸿蒙系统对Flutter应用的性能要求较高建议使用const构造函数尽可能多的地方对网络图片使用cached_network_image插件避免在build方法中做耗时操作使用ListView.builder而不是ColumnList处理长列表7. 测试与调试技巧7.1 单元测试示例为API接口编写测试void main() { late WeatherRepository repository; setUp(() { repository WeatherRepository(apiKey: test_key); }); test(getWeather returns valid data, () async { final weather await repository.getWeather(101010100); expect(weather.code, 200); expect(weather.now.temp, isNotNull); }); }7.2 调试网络请求使用dio的拦截器记录请求日志final dio Dio() ..interceptors.add(LogInterceptor( request: true, requestHeader: true, requestBody: true, responseHeader: true, responseBody: true, error: true, ));8. 项目优化方向8.1 缓存策略实现减少API调用次数实现简单的内存缓存class CachedWeatherRepository { final WeatherRepository _delegate; final MapString, WeatherResponse _cache {}; final Duration _cacheDuration; CachedWeatherRepository({ required WeatherRepository delegate, Duration cacheDuration const Duration(minutes: 30), }) : _delegate delegate, _cacheDuration cacheDuration; FutureWeatherResponse getWeather(String location) async { final cached _cache[location]; if (cached ! null DateTime.now().difference(cached.updateTime) _cacheDuration) { return cached; } final fresh await _delegate.getWeather(location); _cache[location] fresh; return fresh; } }8.2 国际化支持添加多语言支持class WeatherCard extends StatelessWidget { // ... Widget _buildTemperature(BuildContext context) { final unit Localizations.localeOf(context).languageCode en ? °F : °C; return Text( ${weather.now.temp}$unit, style: TextStyle(fontSize: 24), ); } }9. 常见问题与解决方案9.1 API返回错误代码处理和风天气常见的错误代码及处理方式错误码含义解决方案204无数据检查location参数是否正确401认证失败检查API Key是否有效404无效请求检查API地址和参数500服务器错误稍后重试9.2 网络请求超时设置为Dio配置合理的超时时间final dio Dio(BaseOptions( connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 3), ));10. 项目部署与发布10.1 环境变量配置安全地管理API Key# --dart-defineWEATHER_API_KEYyour_api_key flutter run --dart-defineWEATHER_API_KEYyour_api_key10.2 鸿蒙应用打包虽然Flutter应用可以直接在鸿蒙设备上运行但正式发布需要按照鸿蒙应用规范配置应用信息添加鸿蒙特有的权限声明使用鸿蒙的签名工具对应用进行签名提交到华为应用市场审核提示鸿蒙系统对应用权限管理较严格确保只申请必要的权限