<返回目录     Powered by claud/xia兄

第9课: 网络请求

http包、Dio、JSON解析

http包基础

# pubspec.yaml
dependencies:
  http: ^1.1.0

import 'package:http/http.dart' as http;
import 'dart:convert';

// GET请求
Future fetchData() async {
  final response = await http.get(
    Uri.parse('https://api.example.com/users'),
  );

  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    print(data);
  } else {
    throw Exception('请求失败');
  }
}

// POST请求
Future createUser() async {
  final response = await http.post(
    Uri.parse('https://api.example.com/users'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'name': '张三',
      'email': 'zhang@example.com',
    }),
  );

  if (response.statusCode == 201) {
    print('创建成功');
  }
}

Dio - 强大的HTTP客户端

# pubspec.yaml
dependencies:
  dio: ^5.3.0

import 'package:dio/dio.dart';

class ApiService {
  final Dio _dio = Dio(
    BaseOptions(
      baseUrl: 'https://api.example.com',
      connectTimeout: Duration(seconds: 5),
      receiveTimeout: Duration(seconds: 3),
      headers: {
        'Content-Type': 'application/json',
      },
    ),
  );

  // GET请求
  Future> getUsers() async {
    try {
      final response = await _dio.get('/users');
      return (response.data as List)
          .map((json) => User.fromJson(json))
          .toList();
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }

  // POST请求
  Future createUser(User user) async {
    try {
      final response = await _dio.post(
        '/users',
        data: user.toJson(),
      );
      return User.fromJson(response.data);
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }

  // PUT请求
  Future updateUser(int id, User user) async {
    final response = await _dio.put(
      '/users/$id',
      data: user.toJson(),
    );
    return User.fromJson(response.data);
  }

  // DELETE请求
  Future deleteUser(int id) async {
    await _dio.delete('/users/$id');
  }

  String _handleError(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
        return '连接超时';
      case DioExceptionType.sendTimeout:
        return '发送超时';
      case DioExceptionType.receiveTimeout:
        return '接收超时';
      case DioExceptionType.badResponse:
        return '服务器错误: ${e.response?.statusCode}';
      case DioExceptionType.cancel:
        return '请求取消';
      default:
        return '网络错误';
    }
  }
}

JSON解析

// 用户模型
class User {
  final int id;
  final String name;
  final String email;
  final int age;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.age,
  });

  // 从JSON创建对象
  factory User.fromJson(Map json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
      age: json['age'],
    );
  }

  // 转换为JSON
  Map toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'age': age,
    };
  }
}

// 使用示例
void main() {
  // JSON字符串转对象
  String jsonString = '{"id": 1, "name": "张三", "email": "zhang@example.com", "age": 25}';
  Map jsonMap = jsonDecode(jsonString);
  User user = User.fromJson(jsonMap);

  // 对象转JSON字符串
  String json = jsonEncode(user.toJson());
  print(json);
}

FutureBuilder实现异步UI

class UserListPage extends StatelessWidget {
  final ApiService _apiService = ApiService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('用户列表')),
      body: FutureBuilder>(
        future: _apiService.getUsers(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }

          if (snapshot.hasError) {
            return Center(
              child: Text('错误: ${snapshot.error}'),
            );
          }

          if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return Center(child: Text('暂无数据'));
          }

          final users = snapshot.data!;
          return ListView.builder(
            itemCount: users.length,
            itemBuilder: (context, index) {
              final user = users[index];
              return ListTile(
                title: Text(user.name),
                subtitle: Text(user.email),
                trailing: Text('${user.age}岁'),
              );
            },
          );
        },
      ),
    );
  }
}

拦截器

class ApiService {
  late Dio _dio;

  ApiService() {
    _dio = Dio(BaseOptions(
      baseUrl: 'https://api.example.com',
    ));

    // 添加拦截器
    _dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) {
          // 添加token
          options.headers['Authorization'] = 'Bearer token123';
          print('请求: ${options.method} ${options.path}');
          return handler.next(options);
        },
        onResponse: (response, handler) {
          print('响应: ${response.statusCode}');
          return handler.next(response);
        },
        onError: (error, handler) {
          print('错误: ${error.message}');
          return handler.next(error);
        },
      ),
    );
  }
}

文件上传

Future uploadFile(File file) async {
  String fileName = file.path.split('/').last;
  FormData formData = FormData.fromMap({
    'file': await MultipartFile.fromFile(
      file.path,
      filename: fileName,
    ),
    'description': '文件描述',
  });

  try {
    final response = await _dio.post(
      '/upload',
      data: formData,
      onSendProgress: (sent, total) {
        print('上传进度: ${(sent / total * 100).toStringAsFixed(0)}%');
      },
    );
    print('上传成功: ${response.data}');
  } catch (e) {
    print('上传失败: $e');
  }
}

下载文件

Future downloadFile(String url, String savePath) async {
  try {
    await _dio.download(
      url,
      savePath,
      onReceiveProgress: (received, total) {
        if (total != -1) {
          print('下载进度: ${(received / total * 100).toStringAsFixed(0)}%');
        }
      },
    );
    print('下载完成');
  } catch (e) {
    print('下载失败: $e');
  }
}

完整示例:新闻应用

// 新闻模型
class News {
  final int id;
  final String title;
  final String content;
  final String author;
  final DateTime publishTime;

  News({
    required this.id,
    required this.title,
    required this.content,
    required this.author,
    required this.publishTime,
  });

  factory News.fromJson(Map json) {
    return News(
      id: json['id'],
      title: json['title'],
      content: json['content'],
      author: json['author'],
      publishTime: DateTime.parse(json['publish_time']),
    );
  }
}

// API服务
class NewsService {
  final Dio _dio = Dio(
    BaseOptions(baseUrl: 'https://api.news.com'),
  );

  Future> getNewsList({int page = 1, int pageSize = 20}) async {
    final response = await _dio.get(
      '/news',
      queryParameters: {'page': page, 'page_size': pageSize},
    );
    return (response.data['data'] as List)
        .map((json) => News.fromJson(json))
        .toList();
  }

  Future getNewsDetail(int id) async {
    final response = await _dio.get('/news/$id');
    return News.fromJson(response.data);
  }
}

// 新闻列表页面
class NewsListPage extends StatefulWidget {
  @override
  State createState() => _NewsListPageState();
}

class _NewsListPageState extends State {
  final NewsService _newsService = NewsService();
  List _newsList = [];
  bool _isLoading = false;
  int _currentPage = 1;

  @override
  void initState() {
    super.initState();
    _loadNews();
  }

  Future _loadNews() async {
    if (_isLoading) return;

    setState(() => _isLoading = true);

    try {
      final news = await _newsService.getNewsList(page: _currentPage);
      setState(() {
        _newsList.addAll(news);
        _currentPage++;
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('加载失败: $e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('新闻列表')),
      body: RefreshIndicator(
        onRefresh: () async {
          _currentPage = 1;
          _newsList.clear();
          await _loadNews();
        },
        child: ListView.builder(
          itemCount: _newsList.length + 1,
          itemBuilder: (context, index) {
            if (index == _newsList.length) {
              return _isLoading
                  ? Center(child: CircularProgressIndicator())
                  : SizedBox();
            }

            final news = _newsList[index];
            return ListTile(
              title: Text(news.title),
              subtitle: Text('${news.author} · ${_formatTime(news.publishTime)}'),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => NewsDetailPage(newsId: news.id),
                  ),
                );
              },
            );
          },
        ),
      ),
    );
  }

  String _formatTime(DateTime time) {
    final now = DateTime.now();
    final diff = now.difference(time);

    if (diff.inDays > 0) return '${diff.inDays}天前';
    if (diff.inHours > 0) return '${diff.inHours}小时前';
    if (diff.inMinutes > 0) return '${diff.inMinutes}分钟前';
    return '刚刚';
  }
}
最佳实践

练习任务

  1. 使用http包获取天气API数据并显示
  2. 使用Dio实现用户登录功能
  3. 创建一个图片上传功能,显示上传进度
  4. 实现下拉刷新和上拉加载更多
  5. 使用拦截器实现请求重试机制