Appearance
Flutter网络请求
在现代移动应用开发中,网络请求是必不可少的功能。Flutter提供了多种方式进行网络请求,从基础的HTTP请求到高级的网络状态管理。本章将详细介绍Flutter中的网络编程实践。
网络请求基础
添加网络依赖
首先,在pubspec.yaml中添加网络请求所需的依赖:
yaml
dependencies:
flutter:
sdk: flutter
http: ^0.13.5
基础HTTP请求
dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class BasicNetworkingDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('基础网络请求')),
body: Center(
child: ElevatedButton(
onPressed: _makeBasicRequest,
child: Text('发起GET请求'),
),
),
);
}
Future<void> _makeBasicRequest() async {
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
);
if (response.statusCode == 200) {
// 请求成功,解析JSON数据
final data = json.decode(response.body);
print('Response: $data');
} else {
// 请求失败
print('Request failed with status: ${response.statusCode}');
}
} catch (e) {
print('Error occurred: $e');
}
}
}
GET请求详解
简单GET请求
dart
class GetRequestDemo extends StatefulWidget {
@override
_GetRequestDemoState createState() => _GetRequestDemoState();
}
class _GetRequestDemoState extends State<GetRequestDemo> {
String _response = '';
bool _isLoading = false;
Future<void> _fetchData() async {
setState(() {
_isLoading = true;
_response = '';
});
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/users'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
setState(() {
_response = response.body;
_isLoading = false;
});
} else {
setState(() {
_response = 'Error: ${response.statusCode}';
_isLoading = false;
});
}
} catch (e) {
setState(() {
_response = 'Exception: $e';
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('GET请求示例')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
ElevatedButton(
onPressed: _fetchData,
child: Text('获取用户数据'),
),
SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else
Expanded(
child: SingleChildScrollView(
child: Text(_response),
),
),
],
),
),
);
}
}
带参数的GET请求
dart
class GetWithParamsDemo extends StatefulWidget {
@override
_GetWithParamsDemoState createState() => _GetWithParamsDemoState();
}
class _GetWithParamsDemoState extends State<GetWithParamsDemo> {
String _response = '';
bool _isLoading = false;
String _userId = '1';
Future<void> _fetchUserPosts() async {
setState(() {
_isLoading = true;
_response = '';
});
try {
final url = Uri.https('jsonplaceholder.typicode.com', '/posts', {
'userId': _userId,
});
final response = await http.get(url);
if (response.statusCode == 200) {
setState(() {
_response = response.body;
_isLoading = false;
});
} else {
setState(() {
_response = 'Error: ${response.statusCode}';
_isLoading = false;
});
}
} catch (e) {
setState(() {
_response = 'Exception: $e';
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('带参数GET请求')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: '用户ID',
hintText: '输入用户ID',
),
onChanged: (value) => _userId = value,
keyboardType: TextInputType.number,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _fetchUserPosts,
child: Text('获取用户文章'),
),
SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else
Expanded(
child: SingleChildScrollView(
child: Text(_response),
),
),
],
),
),
);
}
}
POST请求详解
基础POST请求
dart
class PostRequestDemo extends StatefulWidget {
@override
_PostRequestDemoState createState() => _PostRequestDemoState();
}
class _PostRequestDemoState extends State<PostRequestDemo> {
String _response = '';
bool _isLoading = false;
String _title = '';
String _body = '';
Future<void> _createPost() async {
setState(() {
_isLoading = true;
_response = '';
});
try {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': _title,
'body': _body,
'userId': '1',
}),
);
if (response.statusCode == 201) {
setState(() {
_response = response.body;
_isLoading = false;
});
} else {
setState(() {
_response = 'Error: ${response.statusCode}';
_isLoading = false;
});
}
} catch (e) {
setState(() {
_response = 'Exception: $e';
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('POST请求示例')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: '标题',
hintText: '输入文章标题',
),
onChanged: (value) => _title = value,
),
SizedBox(height: 10),
TextField(
decoration: InputDecoration(
labelText: '内容',
hintText: '输入文章内容',
),
maxLines: 4,
onChanged: (value) => _body = value,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _createPost,
child: Text('创建文章'),
),
SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else if (_response.isNotEmpty)
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(_response),
),
),
),
],
),
),
);
}
}
文件上传
dart
import 'package:http_parser/http_parser.dart';
class FileUploadDemo extends StatefulWidget {
@override
_FileUploadDemoState createState() => _FileUploadDemoState();
}
class _FileUploadDemoState extends State<FileUploadDemo> {
String _response = '';
bool _isLoading = false;
Future<void> _uploadFile() async {
setState(() {
_isLoading = true;
_response = '';
});
try {
// 注意:这只是一个模拟示例,实际文件上传需要使用image_picker等插件
var request = http.MultipartRequest(
'POST',
Uri.parse('https://httpbin.org/post'),
);
// 添加文本字段
request.fields['description'] = 'This is a test file upload';
request.fields['user_id'] = '123';
// 创建一个模拟文件
var multipartFile = http.MultipartFile.fromString(
'file',
'Sample file content',
filename: 'sample.txt',
contentType: MediaType('text', 'plain'),
);
request.files.add(multipartFile);
final streamedResponse = await request.send();
final response = await http.Response.fromStream(streamedResponse);
if (response.statusCode == 200) {
setState(() {
_response = response.body;
_isLoading = false;
});
} else {
setState(() {
_response = 'Error: ${response.statusCode}';
_isLoading = false;
});
}
} catch (e) {
setState(() {
_response = 'Exception: $e';
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('文件上传示例')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
ElevatedButton(
onPressed: _uploadFile,
child: Text('上传文件'),
),
SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else if (_response.isNotEmpty)
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Text(_response),
),
),
),
),
],
),
),
);
}
}
数据模型与序列化
创建数据模型
dart
// 用户模型
class User {
final int id;
final String name;
final String username;
final String email;
final Address address;
final String phone;
final String website;
final Company company;
User({
required this.id,
required this.name,
required this.username,
required this.email,
required this.address,
required this.phone,
required this.website,
required this.company,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
username: json['username'],
email: json['email'],
address: Address.fromJson(json['address']),
phone: json['phone'],
website: json['website'],
company: Company.fromJson(json['company']),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'username': username,
'email': email,
'address': address.toJson(),
'phone': phone,
'website': website,
'company': company.toJson(),
};
}
}
class Address {
final String street;
final String suite;
final String city;
final String zipcode;
final Geo geo;
Address({
required this.street,
required this.suite,
required this.city,
required this.zipcode,
required this.geo,
});
factory Address.fromJson(Map<String, dynamic> json) {
return Address(
street: json['street'],
suite: json['suite'],
city: json['city'],
zipcode: json['zipcode'],
geo: Geo.fromJson(json['geo']),
);
}
Map<String, dynamic> toJson() {
return {
'street': street,
'suite': suite,
'city': city,
'zipcode': zipcode,
'geo': geo.toJson(),
};
}
}
class Geo {
final String lat;
final String lng;
Geo({required this.lat, required this.lng});
factory Geo.fromJson(Map<String, dynamic> json) {
return Geo(
lat: json['lat'],
lng: json['lng'],
);
}
Map<String, dynamic> toJson() {
return {
'lat': lat,
'lng': lng,
};
}
}
class Company {
final String name;
final String catchPhrase;
final String bs;
Company({required this.name, required this.catchPhrase, required this.bs});
factory Company.fromJson(Map<String, dynamic> json) {
return Company(
name: json['name'],
catchPhrase: json['catchPhrase'],
bs: json['bs'],
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'catchPhrase': catchPhrase,
'bs': bs,
};
}
}
// 使用模型的网络请求
class ModelBasedNetworking extends StatefulWidget {
@override
_ModelBasedNetworkingState createState() => _ModelBasedNetworkingState();
}
class _ModelBasedNetworkingState extends State<ModelBasedNetworking> {
List<User> _users = [];
bool _isLoading = false;
Future<void> _fetchUsers() async {
setState(() {
_isLoading = true;
_users = [];
});
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/users'),
);
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
final users = data.map((json) => User.fromJson(json)).toList();
setState(() {
_users = users;
_isLoading = false;
});
} else {
setState(() {
_isLoading = false;
});
throw Exception('Failed to load users');
}
} catch (e) {
setState(() {
_isLoading = false;
});
print('Error: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('模型化网络请求')),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16.0),
child: ElevatedButton(
onPressed: _fetchUsers,
child: Text('获取用户数据'),
),
),
if (_isLoading)
LinearProgressIndicator()
else
Expanded(
child: ListView.builder(
itemCount: _users.length,
itemBuilder: (context, index) {
final user = _users[index];
return Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user.name,
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 8),
Text('用户名: ${user.username}'),
Text('邮箱: ${user.email}'),
Text('电话: ${user.phone}'),
Text('公司: ${user.company.name}'),
],
),
),
);
},
),
),
],
),
);
}
}
网络状态管理
创建网络状态管理器
dart
import 'package:flutter/foundation.dart';
enum NetworkStatus { loading, success, error, empty }
class NetworkManager extends ChangeNotifier {
NetworkStatus _status = NetworkStatus.empty;
String? _errorMessage;
dynamic _data;
NetworkStatus get status => _status;
String? get errorMessage => _errorMessage;
dynamic get data => _data;
Future<void> fetchData(String url) async {
_status = NetworkStatus.loading;
_errorMessage = null;
_data = null;
notifyListeners();
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
_status = NetworkStatus.success;
_data = json.decode(response.body);
} else {
_status = NetworkStatus.error;
_errorMessage = 'HTTP Error: ${response.statusCode}';
}
} catch (e) {
_status = NetworkStatus.error;
_errorMessage = e.toString();
}
notifyListeners();
}
}
class NetworkStatusDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => NetworkManager(),
child: Scaffold(
appBar: AppBar(title: Text('网络状态管理')),
body: Consumer<NetworkManager>(
builder: (context, networkManager, child) {
switch (networkManager.status) {
case NetworkStatus.loading:
return Center(child: CircularProgressIndicator());
case NetworkStatus.success:
final users = networkManager.data as List;
return RefreshIndicator(
onRefresh: () => networkManager.fetchData('https://jsonplaceholder.typicode.com/users'),
child: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return Card(
child: ListTile(
title: Text(user['name']),
subtitle: Text(user['email']),
trailing: Icon(Icons.arrow_forward_ios),
),
);
},
),
);
case NetworkStatus.error:
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red),
SizedBox(height: 16),
Text(
'错误: ${networkManager.errorMessage}',
textAlign: TextAlign.center,
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () => networkManager.fetchData('https://jsonplaceholder.typicode.com/users'),
child: Text('重试'),
),
],
),
);
case NetworkStatus.empty:
default:
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.cloud_outlined, size: 64),
SizedBox(height: 16),
Text('点击下方按钮获取数据'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () => networkManager.fetchData('https://jsonplaceholder.typicode.com/users'),
child: Text('获取数据'),
),
],
),
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<NetworkManager>(context, listen: false)
.fetchData('https://jsonplaceholder.typicode.com/users');
},
child: Icon(Icons.refresh),
),
),
);
}
}
高级网络功能
请求拦截器和配置
dart
class NetworkService {
static const String _baseUrl = 'https://jsonplaceholder.typicode.com';
final http.Client _client = http.Client();
// 默认请求头
Map<String, String> get _defaultHeaders {
return {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json',
};
}
// GET请求
Future<http.Response> get(String endpoint, {Map<String, String>? headers}) async {
final url = Uri.parse('$_baseUrl$endpoint');
final requestHeaders = {..._defaultHeaders, ...?headers};
return await _client.get(url, headers: requestHeaders);
}
// POST请求
Future<http.Response> post(String endpoint, dynamic body, {Map<String, String>? headers}) async {
final url = Uri.parse('$_baseUrl$endpoint');
final requestHeaders = {..._defaultHeaders, ...?headers};
return await _client.post(
url,
headers: requestHeaders,
body: jsonEncode(body),
);
}
// PUT请求
Future<http.Response> put(String endpoint, dynamic body, {Map<String, String>? headers}) async {
final url = Uri.parse('$_baseUrl$endpoint');
final requestHeaders = {..._defaultHeaders, ...?headers};
return await _client.put(
url,
headers: requestHeaders,
body: jsonEncode(body),
);
}
// DELETE请求
Future<http.Response> delete(String endpoint, {Map<String, String>? headers}) async {
final url = Uri.parse('$_baseUrl$endpoint');
final requestHeaders = {..._defaultHeaders, ...?headers};
return await _client.delete(url, headers: requestHeaders);
}
// 关闭客户端
void close() {
_client.close();
}
}
class NetworkServiceDemo extends StatefulWidget {
@override
_NetworkServiceDemoState createState() => _NetworkServiceDemoState();
}
class _NetworkServiceDemoState extends State<NetworkServiceDemo> {
final NetworkService _networkService = NetworkService();
String _response = '';
bool _isLoading = false;
Future<void> _testGet() async {
setState(() {
_isLoading = true;
_response = '';
});
try {
final response = await _networkService.get('/posts/1');
if (response.statusCode == 200) {
setState(() {
_response = response.body;
_isLoading = false;
});
} else {
setState(() {
_response = 'Error: ${response.statusCode}';
_isLoading = false;
});
}
} catch (e) {
setState(() {
_response = 'Exception: $e';
_isLoading = false;
});
}
}
Future<void> _testPost() async {
setState(() {
_isLoading = true;
_response = '';
});
try {
final response = await _networkService.post(
'/posts',
{
'title': 'Test Post',
'body': 'This is a test post created via NetworkService',
'userId': 1,
},
);
if (response.statusCode == 201) {
setState(() {
_response = response.body;
_isLoading = false;
});
} else {
setState(() {
_response = 'Error: ${response.statusCode}';
_isLoading = false;
});
}
} catch (e) {
setState(() {
_response = 'Exception: $e';
_isLoading = false;
});
}
}
@override
void dispose() {
_networkService.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('网络服务封装')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _testGet,
child: Text('GET请求'),
),
ElevatedButton(
onPressed: _testPost,
child: Text('POST请求'),
),
],
),
SizedBox(height: 20),
if (_isLoading)
LinearProgressIndicator()
else
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Text(_response),
),
),
),
),
],
),
),
);
}
}
网络缓存
实现简单的HTTP缓存
dart
import 'package:shared_preferences/shared_preferences.dart';
class CachedNetworkService {
final NetworkService _networkService = NetworkService();
final Map<String, CacheEntry> _cache = {};
static const Duration _cacheDuration = Duration(minutes: 5);
Future<Map<String, dynamic>?> getCachedData(String endpoint) async {
// 检查内存缓存
final cacheEntry = _cache[endpoint];
if (cacheEntry != null && !cacheEntry.isExpired) {
print('Cache hit for: $endpoint');
return cacheEntry.data;
}
// 检查本地缓存
final prefs = await SharedPreferences.getInstance();
final cachedJson = prefs.getString(endpoint);
final cachedTime = prefs.getInt('${endpoint}_time');
if (cachedJson != null && cachedTime != null) {
final cacheDateTime = DateTime.fromMillisecondsSinceEpoch(cachedTime);
if (DateTime.now().difference(cacheDateTime) < _cacheDuration) {
final data = json.decode(cachedJson) as Map<String, dynamic>;
// 更新内存缓存
_cache[endpoint] = CacheEntry(data, DateTime.now().add(_cacheDuration));
print('Local cache hit for: $endpoint');
return data;
} else {
// 清除过期缓存
await prefs.remove(endpoint);
await prefs.remove('${endpoint}_time');
}
}
// 发起网络请求
try {
final response = await _networkService.get(endpoint);
if (response.statusCode == 200) {
final data = json.decode(response.body) as Map<String, dynamic>;
// 存储到内存缓存
_cache[endpoint] = CacheEntry(data, DateTime.now().add(_cacheDuration));
// 存储到本地缓存
await prefs.setString(endpoint, json.encode(data));
await prefs.setInt('${endpoint}_time', DateTime.now().millisecondsSinceEpoch);
print('Network request and cache for: $endpoint');
return data;
}
} catch (e) {
print('Network request failed: $e');
}
return null;
}
void clearCache() {
_cache.clear();
}
}
class CacheEntry {
final Map<String, dynamic> data;
final DateTime expiryTime;
CacheEntry(this.data, this.expiryTime);
bool get isExpired => DateTime.now().isAfter(expiryTime);
}
class CachedNetworkingDemo extends StatefulWidget {
@override
_CachedNetworkingDemoState createState() => _CachedNetworkingDemoState();
}
class _CachedNetworkingDemoState extends State<CachedNetworkingDemo> {
final CachedNetworkService _cacheService = CachedNetworkService();
String _response = '';
bool _isLoading = false;
Future<void> _fetchData() async {
setState(() {
_isLoading = true;
_response = 'Loading...';
});
try {
final data = await _cacheService.getCachedData('/posts/1');
if (data != null) {
setState(() {
_response = json.encode(data, toEncodable: (dynamic obj) => obj.toString());
_isLoading = false;
});
} else {
setState(() {
_response = 'Failed to load data';
_isLoading = false;
});
}
} catch (e) {
setState(() {
_response = 'Error: $e';
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('网络缓存示例')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _fetchData,
child: Text('获取数据(带缓存)'),
),
ElevatedButton(
onPressed: () {
_cacheService.clearCache();
setState(() {
_response = '缓存已清除';
});
},
child: Text('清除缓存'),
),
],
),
SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Text(_response),
),
),
),
),
],
),
),
);
}
}
网络错误处理
全面的错误处理机制
dart
class NetworkError {
final String message;
final int? statusCode;
final Exception? exception;
NetworkError({
required this.message,
this.statusCode,
this.exception,
});
factory NetworkError.fromException(Exception e) {
if (e is http.ClientException) {
return NetworkError(
message: '网络连接错误,请检查网络设置',
exception: e,
);
} else if (e is FormatException) {
return NetworkError(
message: '数据格式错误',
exception: e,
);
} else if (e is TimeoutException) {
return NetworkError(
message: '请求超时,请稍后重试',
exception: e,
);
} else {
return NetworkError(
message: '未知错误: ${e.toString()}',
exception: e,
);
}
}
factory NetworkError.fromStatusCode(int statusCode) {
String message;
switch (statusCode) {
case 400:
message = '请求参数错误';
break;
case 401:
message = '未授权访问';
break;
case 403:
message = '禁止访问';
break;
case 404:
message = '资源未找到';
break;
case 500:
message = '服务器内部错误';
break;
case 502:
message = '网关错误';
break;
case 503:
message = '服务不可用';
break;
default:
message = 'HTTP错误 ($statusCode)';
}
return NetworkError(
message: message,
statusCode: statusCode,
);
}
}
class ErrorHandlingDemo extends StatefulWidget {
@override
_ErrorHandlingDemoState createState() => _ErrorHandlingDemoState();
}
class _ErrorHandlingDemoState extends State<ErrorHandlingDemo> {
String _response = '';
bool _isLoading = false;
NetworkError? _error;
Future<void> _fetchWithErrorHandling() async {
setState(() {
_isLoading = true;
_response = '';
_error = null;
});
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/999999'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
setState(() {
_response = response.body;
_isLoading = false;
});
} else {
final error = NetworkError.fromStatusCode(response.statusCode);
setState(() {
_error = error;
_isLoading = false;
});
}
} catch (e) {
final error = NetworkError.fromException(e as Exception);
setState(() {
_error = error;
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('错误处理示例')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
ElevatedButton(
onPressed: _fetchWithErrorHandling,
child: Text('发起可能出错的请求'),
),
SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else if (_error != null)
Card(
color: Colors.red.shade50,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Icon(Icons.error, color: Colors.red, size: 48),
SizedBox(height: 16),
Text(
'错误信息',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 8),
Text(
_error!.message,
style: TextStyle(fontSize: 16),
),
if (_error!.statusCode != null)
Text('状态码: ${_error!.statusCode}'),
SizedBox(height: 16),
ElevatedButton(
onPressed: _fetchWithErrorHandling,
child: Text('重试'),
),
],
),
),
)
else if (_response.isNotEmpty)
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(_response),
),
),
),
],
),
),
);
}
}
网络状态监测
监测设备网络状态
dart
import 'package:connectivity_plus/connectivity_plus.dart';
class NetworkStatusMonitor extends StatefulWidget {
@override
_NetworkStatusMonitorState createState() => _NetworkStatusMonitorState();
}
class _NetworkStatusMonitorState extends State<NetworkStatusMonitor> {
ConnectivityResult _connectionStatus = ConnectivityResult.none;
String _statusMessage = '未知网络状态';
StreamSubscription<ConnectivityResult>? _connectivitySubscription;
@override
void initState() {
super.initState();
_checkConnection();
_listenConnectionChanges();
}
Future<void> _checkConnection() async {
final result = await Connectivity().checkConnectivity();
setState(() {
_connectionStatus = result;
_statusMessage = _getConnectionMessage(result);
});
}
void _listenConnectionChanges() {
_connectivitySubscription = Connectivity().onConnectivityChanged.listen((result) {
setState(() {
_connectionStatus = result;
_statusMessage = _getConnectionMessage(result);
});
});
}
String _getConnectionMessage(ConnectivityResult result) {
switch (result) {
case ConnectivityResult.wifi:
return '已连接到WiFi网络';
case ConnectivityResult.mobile:
return '已连接到移动数据网络';
case ConnectivityResult.none:
return '无网络连接';
default:
return '未知网络状态';
}
}
Color _getConnectionColor(ConnectivityResult result) {
switch (result) {
case ConnectivityResult.wifi:
return Colors.green;
case ConnectivityResult.mobile:
return Colors.blue;
case ConnectivityResult.none:
return Colors.red;
default:
return Colors.grey;
}
}
@override
void dispose() {
_connectivitySubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('网络状态监测')),
body: Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
_connectionStatus == ConnectivityResult.none
? Icons.signal_wifi_off
: Icons.network_check,
size: 80,
color: _getConnectionColor(_connectionStatus),
),
SizedBox(height: 20),
Text(
_statusMessage,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: _getConnectionColor(_connectionStatus),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _checkConnection,
child: Text('重新检查网络状态'),
),
SizedBox(height: 20),
if (_connectionStatus != ConnectivityResult.none)
ElevatedButton(
onPressed: () async {
try {
final response = await http
.get(Uri.parse('https://httpbin.org/get'))
.timeout(Duration(seconds: 5));
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('网络连接正常'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('网络连接测试失败: $e'),
backgroundColor: Colors.red,
),
);
}
},
child: Text('测试网络连通性'),
),
],
),
),
),
);
}
}
网络请求最佳实践
1. 请求超时和重试机制
dart
class RobustNetworkService {
static Future<http.Response> getWithTimeout(String url, {Duration timeout = const Duration(seconds: 10)}) async {
try {
final response = await http.get(Uri.parse(url)).timeout(timeout);
return response;
} on TimeoutException {
throw Exception('请求超时');
} on Exception {
rethrow;
}
}
static Future<http.Response> getWithRetry(String url, {int maxRetries = 3, Duration delay = const Duration(seconds: 1)}) async {
http.Response? lastResponse;
Exception? lastException;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode >= 200 && response.statusCode < 300) {
return response;
}
lastResponse = response;
} on Exception catch (e) {
lastException = e;
}
if (attempt < maxRetries) {
await Future.delayed(delay);
}
}
if (lastResponse != null) {
return lastResponse;
} else if (lastException != null) {
throw lastException;
} else {
throw Exception('未知错误');
}
}
}
2. 网络请求拦截器
dart
class InterceptorNetworkService {
final http.Client _client = http.Client();
final List<RequestInterceptor> _requestInterceptors = [];
final List<ResponseInterceptor> _responseInterceptors = [];
void addRequestInterceptor(RequestInterceptor interceptor) {
_requestInterceptors.add(interceptor);
}
void addResponseInterceptor(ResponseInterceptor interceptor) {
_responseInterceptors.add(interceptor);
}
Future<http.Response> get(String url) async {
var uri = Uri.parse(url);
// 应用请求拦截器
for (var interceptor in _requestInterceptors) {
uri = await interceptor.interceptUri(uri);
}
var response = await _client.get(uri);
// 应用响应拦截器
for (var interceptor in _responseInterceptors) {
response = await interceptor.interceptResponse(response);
}
return response;
}
void close() {
_client.close();
}
}
typedef RequestInterceptorFunc = Future<Uri> Function(Uri uri);
typedef ResponseInterceptorFunc = Future<http.Response> Function(http.Response response);
class RequestInterceptor {
final RequestInterceptorFunc interceptUri;
RequestInterceptor(this.interceptUri);
}
class ResponseInterceptor {
final ResponseInterceptorFunc interceptResponse;
ResponseInterceptor(this.interceptResponse);
}
总结
Flutter网络编程涉及多个重要方面:
- 基础网络请求:使用http包进行GET、POST等基本请求
- 数据模型:创建对应API响应的数据模型并实现序列化
- 状态管理:管理网络请求的加载、成功、错误等状态
- 高级功能:实现请求拦截、缓存、错误处理等
- 网络监测:监测设备网络状态以提供更好的用户体验
- 最佳实践:实现超时、重试、拦截器等健壮性功能
通过合理运用这些网络编程技术,可以构建高效、可靠、用户体验良好的Flutter应用。