标题:深入 Flutter 开发:构建一个带网络请求的天气应用(含完整代码)

引言

在移动开发领域,Flutter 凭借其高性能、跨平台能力和出色的 UI 表现力,已成为 Google 主推的现代应用开发框架。它使用 Dart 语言,通过自绘引擎 Skia 实现原生级渲染,支持一套代码运行在 iOS、Android、Web 和桌面端。

本文将带你从零开始,使用 Flutter 构建一个真实的天气查询应用,涵盖 UI 布局、HTTP 请求、JSON 解析、状态管理与错误处理,并附有完整可运行代码。


一、环境准备

确保已安装:

  • Flutter SDK(v3.19+)
  • Dart
  • 编辑器(推荐 VS Code 或 Android Studio)
  • 模拟器或真机调试设备

创建项目:

flutter create flutter_weather_app
cd flutter_weather_app

二、添加依赖

编辑 pubspec.yaml 文件,添加网络请求和图标支持:

dependencies:
  flutter:
    sdk: flutter
  http: ^1.0.0        # 用于发送 HTTP 请求
  intl: ^0.19.0       # 格式化日期等(可选)

保存后运行:

flutter pub get

三、获取免费天气 API

我们使用 OpenWeatherMap 的免费 API:

  1. 注册账号并获取 API Key
  2. 使用如下接口:
    https://api.openweathermap.org/data/2.5/weather?q=城市名&appid=你的APIKey&units=metric&lang=zh_cn
    

示例响应(JSON):

{
  "name": "Beijing",
  "main": {
    "temp": 22.5,
    "humidity": 60
  },
  "weather": [
    {
      "description": "晴朗",
      "icon": "01d"
    }
  ]
}

四、项目结构

lib/
├── main.dart
├── models/
│   └── weather.dart
├── services/
│   └── weather_service.dart
└── screens/
    └── weather_screen.dart

五、完整代码实现

1. 模型类:models/weather.dart
// lib/models/weather.dart
class Weather {
  final String cityName;
  final double temperature;
  final String description;
  final String iconCode;

  Weather({
    required this.cityName,
    required this.temperature,
    required this.description,
    required this.iconCode,
  });

  // 工厂构造函数:从 JSON 创建对象
  factory Weather.fromJson(Map<String, dynamic> json) {
    return Weather(
      cityName: json['name'],
      temperature: json['main']['temp'].toDouble(),
      description: json['weather'][0]['description'],
      iconCode: json['weather'][0]['icon'],
    );
  }
}

2. 网络服务:services/weather_service.dart
// lib/services/weather_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/weather.dart';

class WeatherService {
  final String apiKey;

  WeatherService(this.apiKey);

  Future<Weather> fetchWeather(String city) async {
    final url = Uri.parse(
        'https://api.openweathermap.org/data/2.5/weather?q=$city&appid=$apiKey&units=metric&lang=zh_cn');

    final response = await http.get(url);

    if (response.statusCode == 200) {
      return Weather.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to load weather data');
    }
  }
}

3. 主界面:screens/weather_screen.dart
// lib/screens/weather_screen.dart
import 'package:flutter/material.dart';
import '../models/weather.dart';
import '../services/weather_service.dart';

class WeatherScreen extends StatefulWidget {
  const WeatherScreen({super.key});

  
  State<WeatherScreen> createState() => _WeatherScreenState();
}

class _WeatherScreenState extends State<WeatherScreen> {
  final TextEditingController _controller = TextEditingController();
  Weather? _weather;
  bool _loading = false;
  String? _error;

  // 替换为你的 OpenWeatherMap API Key
  final WeatherService service = WeatherService("YOUR_API_KEY_HERE");

  void _searchWeather() async {
    final city = _controller.text.trim();
    if (city.isEmpty) return;

    setState(() {
      _loading = true;
      _error = null;
    });

    try {
      final weather = await service.fetchWeather(city);
      setState(() {
        _weather = weather;
        _loading = false;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
        _loading = false;
      });
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("🌦️ 天气预报"),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 输入框 + 搜索按钮
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      labelText: '输入城市名称',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _searchWeather(),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: _searchWeather,
                  child: const Icon(Icons.search),
                ),
              ],
            ),

            const SizedBox(height: 20),

            // 加载状态
            if (_loading)
              const CircularProgressIndicator()
            else if (_error != null)
              Text(
                '错误: $_error',
                style: TextStyle(color: Colors.red[700]),
              )
            else if (_weather != null)
              _buildWeatherCard(_weather!)
            else
              const Text("请输入城市查询天气")
          ],
        ),
      ),
    );
  }

  // 显示天气信息卡片
  Widget _buildWeatherCard(Weather weather) {
    return Card(
      elevation: 4,
      child: Container(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text(
              weather.cityName,
              style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Image.network(
              'https://openweathermap.org/img/wn/${weather.iconCode}@2x.png',
              width: 100,
              height: 100,
            ),
            Text(
              '${weather.temperature.toStringAsFixed(1)}°C',
              style: const TextStyle(fontSize: 40, color: Colors.orange),
            ),
            Text(
              weather.description,
              style: const TextStyle(fontSize: 18, color: Colors.grey),
            ),
          ],
        ),
      ),
    );
  }

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

4. 入口文件:main.dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'screens/weather_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 天气应用',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const WeatherScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

六、运行应用

flutter run

在输入框中输入城市如 “Shanghai” 或 “纽约”,点击搜索,即可看到实时天气信息。


七、核心知识点总结

技术点 说明
StatefulWidget 用于需要动态更新的界面(如加载、数据变化)
http.get() 发送网络请求获取天气数据
Futureasync/await 异步处理网络调用
setState() 触发 UI 重绘
TextFormField 用户输入处理
JSON 解析 使用 factory 构造函数映射 API 响应
错误处理 try-catch 捕获异常并友好提示

八、优化建议(进阶方向)

  • ✅ 使用 Provider / Riverpod 进行全局状态管理
  • ✅ 添加地理位置定位自动获取城市
  • ✅ 支持天气图标本地缓存
  • ✅ 增加未来几天预报(调用 5-day forecast API)
  • ✅ 支持主题切换(深色/浅色模式)

九、结语

通过这个实战项目,你已经掌握了 Flutter 应用开发的核心流程:
UI 构建 → 网络请求 → 数据解析 → 状态更新 → 用户交互

Flutter 不仅让跨平台开发变得简单高效,更以接近原生的性能和丰富的组件生态,成为现代移动开发的理想选择。

🎯 下一步:将本项目部署到 Web 平台(flutter build web),让你的天气应用在浏览器中也能运行!


📌 提示:请将 YOUR_API_KEY_HERE 替换为你在 OpenWeatherMap 获取的真实密钥。

GitHub 示例地址(参考):github.com/yourname/flutter-weather


本文代码已在 Flutter 3.22 上测试通过,支持 Android、iOS 与 Web 平台。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐