news 2026/9/14 19:57:08

Flutter跨平台天气应用开发:和风天气API与鸿蒙适配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter跨平台天气应用开发:和风天气API与鸿蒙适配

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(Map<String, dynamic> json) => _$WeatherResponseFromJson(json); Map<String, 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(Map<String, dynamic> json) => _$NowFromJson(json); Map<String, 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") Future<WeatherResponse> 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 build

4. 天气卡片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/"); Future<WeatherResponse> 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 State<WeatherCardContainer> createState() => _WeatherCardContainerState(); } class _WeatherCardContainerState extends State<WeatherCardContainer> { late final WeatherRepository _repository; WeatherResponse? _weather; String? _error; @override void initState() { super.initState(); _repository = WeatherRepository( apiKey: const String.fromEnvironment("WEATHER_API_KEY"), ); _loadWeather(); } Future<void> _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应用的性能要求较高,建议:

  1. 使用const构造函数尽可能多的地方
  2. 对网络图片使用cached_network_image插件
  3. 避免在build方法中做耗时操作
  4. 使用ListView.builder而不是Column+List处理长列表

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 Map<String, WeatherResponse> _cache = {}; final Duration _cacheDuration; CachedWeatherRepository({ required WeatherRepository delegate, Duration cacheDuration = const Duration(minutes: 30), }) : _delegate = delegate, _cacheDuration = cacheDuration; Future<WeatherResponse> 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-define=WEATHER_API_KEY=your_api_key flutter run --dart-define=WEATHER_API_KEY=your_api_key

10.2 鸿蒙应用打包

虽然Flutter应用可以直接在鸿蒙设备上运行,但正式发布需要:

  1. 按照鸿蒙应用规范配置应用信息
  2. 添加鸿蒙特有的权限声明
  3. 使用鸿蒙的签名工具对应用进行签名
  4. 提交到华为应用市场审核

提示:鸿蒙系统对应用权限管理较严格,确保只申请必要的权限

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 19:55:32

仓颉+Harness实战:从零跑通AI微服务编排

1. 项目概述&#xff1a;为什么一个“Harness实战”值得专门写篇踩坑记录&#xff1f; 最近在用仓颉语言做模型能力编排时&#xff0c;真正把 deepseek harness 跑通、调通、跑稳&#xff0c;前后花了将近三周——不是因为代码写不出来&#xff0c;而是因为整个链路里埋了太多“…

作者头像 李华
网站建设 2026/9/14 19:55:30

生物医药创新生态:跨国药企与本土力量的协同研发

1. 项目背景解析&#xff1a;生物医药创新生态的协同进化拜耳Co.Lab共创平台的这次入驻事件&#xff0c;本质上是跨国药企与本土创新力量在研发模式上的深度重构。作为全球生命科学领域的百年巨头&#xff0c;拜耳近年来通过Co.Lab这种开放式创新平台&#xff0c;正在将传统的&…

作者头像 李华
网站建设 2026/9/14 19:55:26

Python+AI实战教程:从爬虫到自动化报表的工程化路径

1. 这套PythonAI教程到底值不值得花600集时间学&#xff1f;——一个带过37个转行学员的老手真实拆解我带过37个零基础转行做数据分析和自动化开发的学员&#xff0c;平均年龄28.6岁&#xff0c;其中21个是文科背景、5个是传统制造业从业者、还有3个是教培行业转型的老师。他们…

作者头像 李华
网站建设 2026/9/14 19:54:23

从ArcFace到工程落地:猪脸识别技术解析与实现

简介&#xff1a;京东JDD大赛猪脸识别项目以商品猪个体身份识别为赛题&#xff0c;涵盖数据预处理、模型训练、测试与可视化全流程&#xff0c;适合计算机、数学、电子信息等专业学生用于课程设计、期末大作业或毕业设计参考。压缩包共61个文件&#xff0c;其中24个Python脚本构…

作者头像 李华
网站建设 2026/9/14 19:52:54

上帝视角拍摄全攻略:从设备选型到后期处理的实战指南

你有没有过这种经历——站在天桥上往下看&#xff0c;脚下的车流和人潮突然变成一幅会动的画&#xff0c;你明明没有参与其中&#xff0c;却好像把一切都收在眼底。这种从现实里抽离出去、俯视全局的感觉&#xff0c;正是"上帝视角"最迷人的地方&#xff0c;英文里常…

作者头像 李华
网站建设 2026/9/14 19:52:53

企业级Agent落地指南:从超级个体到超级团队的工程化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华