Flutter网络请求的进度指示:提升用户体验
Flutter网络请求概述
在Flutter应用开发中,网络请求是获取数据、与后端服务器交互的重要手段。常见的网络请求场景包括获取用户信息、加载列表数据、提交表单等。Flutter提供了多种网络请求的方式,如使用http
包、dio
包等。
http
包是Flutter官方提供的网络请求库,它基于HttpClient实现,支持基本的HTTP请求方法,如GET、POST、PUT、DELETE等。以下是使用http
包发送GET请求的简单示例:
import 'package:http/http.dart' as http;
Future<void> fetchData() async {
final response = await http.get(Uri.parse('https://example.com/api/data'));
if (response.statusCode == 200) {
print(response.body);
} else {
print('Request failed with status: ${response.statusCode}.');
}
}
dio
包则是一个强大的、基于http
封装的网络请求库,它提供了更丰富的功能,如拦截器、请求取消、上传下载进度监听等。以下是使用dio
发送GET请求的示例:
import 'package:dio/dio.dart';
Future<void> fetchData() async {
try {
Dio dio = Dio();
Response response = await dio.get('https://example.com/api/data');
print(response.data);
} catch (e) {
print(e);
}
}
网络请求中的用户体验问题
在网络请求过程中,用户可能会遇到各种情况,导致体验不佳。例如,当网络请求时间较长时,用户可能会以为应用程序出现了卡顿或崩溃,从而关闭应用。这是因为没有给用户提供明确的反馈,让用户知道请求正在进行中。
另外,在一些大文件的下载或上传场景中,如果没有进度指示,用户无法预估剩余时间,会感到焦虑。比如在下载一个大型应用更新包时,用户不知道还需要等待多久,就可能放弃下载。
实现网络请求进度指示的重要性
实现网络请求进度指示可以极大地提升用户体验。通过向用户展示请求的进度,用户可以清楚地了解到请求的状态,知道应用正在正常工作,而不是出现故障。在文件上传或下载场景中,进度指示可以让用户预估剩余时间,合理安排自己的等待时间。这不仅能减少用户的焦虑感,还能提高用户对应用的信任度和满意度。
使用dio实现网络请求进度指示
下载进度指示
dio
包提供了方便的方法来监听下载进度。以下是一个示例,展示如何下载文件并显示下载进度:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class DownloadPage extends StatefulWidget {
@override
_DownloadPageState createState() => _DownloadPageState();
}
class _DownloadPageState extends State<DownloadPage> {
double _progress = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Download Example'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LinearProgressIndicator(
value: _progress,
),
ElevatedButton(
onPressed: () async {
Dio dio = Dio();
try {
await dio.download(
'https://example.com/file.zip',
'path/to/save/file.zip',
onReceiveProgress: (received, total) {
setState(() {
_progress = received / total;
});
},
);
print('Download completed');
} catch (e) {
print(e);
}
},
child: Text('Download File'),
),
],
),
);
}
}
在上述代码中,dio.download
方法接收三个参数:下载链接、保存路径和一个onReceiveProgress
回调函数。在回调函数中,received
表示已经接收的字节数,total
表示文件的总字节数。通过将received
除以total
,可以得到下载进度,并通过setState
更新UI显示。
上传进度指示
上传文件时同样可以监听进度。以下是一个上传文件的示例:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
class UploadPage extends StatefulWidget {
@override
_UploadPageState createState() => _UploadPageState();
}
class _UploadPageState extends State<UploadPage> {
double _progress = 0;
Future<File> getFile() async {
final directory = await getTemporaryDirectory();
final file = File('${directory.path}/example.txt');
await file.writeAsString('Some content to upload');
return file;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Upload Example'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LinearProgressIndicator(
value: _progress,
),
ElevatedButton(
onPressed: () async {
File file = await getFile();
Dio dio = Dio();
try {
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(file.path),
});
await dio.post(
'https://example.com/api/upload',
data: formData,
onSendProgress: (sent, total) {
setState(() {
_progress = sent / total;
});
},
);
print('Upload completed');
} catch (e) {
print(e);
}
},
child: Text('Upload File'),
),
],
),
);
}
}
在这个示例中,首先通过getFile
方法创建一个临时文件。然后在onPressed
回调中,将文件包装成MultipartFile
并添加到FormData
中。dio.post
方法的onSendProgress
回调用于监听上传进度,sent
表示已经发送的字节数,total
表示文件的总字节数,同样通过setState
更新UI显示上传进度。
使用http包实现网络请求进度指示(拓展思路)
虽然http
包本身没有直接提供进度监听的方法,但我们可以通过一些拓展思路来实现。一种方法是通过继承HttpClient
并覆盖相关方法来实现进度监听。以下是一个简单的示例:
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
class ProgressHttpClient extends HttpClient {
StreamController<double> _progressController = StreamController<double>();
Stream<double> get progressStream => _progressController.stream;
@override
Future<HttpClientRequest> putUrl(Uri url) async {
final request = await super.putUrl(url);
request.addListener((List<int> data) {
// 这里可以根据已发送的数据计算上传进度
// 假设知道总数据大小为totalSize
int totalSize = 1024; // 示例值,实际需根据具体情况获取
double progress = data.length / totalSize;
_progressController.add(progress);
});
return request;
}
@override
Future<HttpClientResponse> getUrl(Uri url) async {
final request = await super.getUrl(url);
final response = await request.close();
Completer<int> lengthCompleter = Completer<int>();
int totalLength = 0;
response.listen((List<int> data) {
totalLength += data.length;
// 这里可以根据已接收的数据计算下载进度
// 假设知道总数据大小为totalSize
int totalSize = 1024; // 示例值,实际需根据具体情况获取
double progress = totalLength / totalSize;
_progressController.add(progress);
}, onDone: () {
lengthCompleter.complete(totalLength);
});
await lengthCompleter.future;
return response;
}
}
使用时可以这样:
Future<void> fetchDataWithProgress() async {
ProgressHttpClient client = ProgressHttpClient();
client.progressStream.listen((progress) {
print('Progress: $progress');
});
final response = await client.getUrl(Uri.parse('https://example.com/api/data'));
if (response.statusCode == 200) {
print(await response.transform(utf8.decoder).join());
} else {
print('Request failed with status: ${response.statusCode}.');
}
}
在上述代码中,ProgressHttpClient
继承自HttpClient
,重写了putUrl
和getUrl
方法。在putUrl
方法中,通过监听请求数据的发送来计算上传进度;在getUrl
方法中,通过监听响应数据的接收来计算下载进度。通过StreamController
将进度数据发送出去,外部通过监听progressStream
来获取进度。
处理复杂网络请求场景下的进度指示
并发请求进度指示
在实际应用中,可能会同时发起多个网络请求,需要综合展示这些请求的进度。例如,一个应用可能同时从多个API获取不同的数据,如用户信息、商品列表等。
可以通过创建一个类来管理多个请求的进度。以下是一个简单的示例:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class MultiRequestProgress {
Map<String, double> progressMap = {};
double totalProgress = 0;
void updateProgress(String requestKey, double progress) {
progressMap[requestKey] = progress;
double sum = 0;
progressMap.forEach((key, value) {
sum += value;
});
totalProgress = sum / progressMap.length;
}
}
class MultiRequestPage extends StatefulWidget {
@override
_MultiRequestPageState createState() => _MultiRequestPageState();
}
class _MultiRequestPageState extends State<MultiRequestPage> {
MultiRequestProgress _progressManager = MultiRequestProgress();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Multi - Request Example'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LinearProgressIndicator(
value: _progressManager.totalProgress,
),
ElevatedButton(
onPressed: () async {
Dio dio = Dio();
List<Future> requests = [
dio.get('https://example.com/api/user'),
dio.get('https://example.com/api/products')
];
int index = 0;
requests.forEach((request) {
request.then((response) {
setState(() {
_progressManager.updateProgress('request$index', 1);
});
}).catchError((e) {
setState(() {
_progressManager.updateProgress('request$index', 0);
});
});
index++;
});
await Future.wait(requests);
},
child: Text('Start Multi - Requests'),
),
],
),
);
}
}
在上述代码中,MultiRequestProgress
类用于管理多个请求的进度。updateProgress
方法根据每个请求的完成情况更新总进度。在MultiRequestPage
中,同时发起两个请求,并在请求完成或失败时更新进度管理器中的进度,从而实时展示多个请求的综合进度。
链式请求进度指示
链式请求是指一个请求的结果作为下一个请求的输入。例如,先获取用户的认证令牌,然后使用该令牌获取用户的详细信息。
可以通过在每个请求中传递进度信息来实现链式请求的进度指示。以下是一个示例:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class ChainRequestPage extends StatefulWidget {
@override
_ChainRequestPageState createState() => _ChainRequestPageState();
}
class _ChainRequestPageState extends State<ChainRequestPage> {
double _progress = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chain - Request Example'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LinearProgressIndicator(
value: _progress,
),
ElevatedButton(
onPressed: () async {
Dio dio = Dio();
try {
// 第一个请求获取令牌
Response tokenResponse = await dio.get('https://example.com/api/token');
setState(() {
_progress = 0.5;
});
String token = tokenResponse.data['token'];
// 第二个请求使用令牌获取用户信息
Response userResponse = await dio.get(
'https://example.com/api/user',
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
setState(() {
_progress = 1;
});
print(userResponse.data);
} catch (e) {
print(e);
}
},
child: Text('Start Chain - Requests'),
),
],
),
);
}
}
在这个示例中,第一个请求获取令牌完成后,将进度更新为0.5。第二个请求使用获取到的令牌获取用户信息,完成后将进度更新为1,从而清晰地展示链式请求的进度。
优化网络请求进度指示的用户体验
进度动画效果
除了简单地显示进度条,还可以添加一些动画效果来提升用户体验。例如,在进度条的填充过程中添加一些过渡动画。
可以使用AnimatedLinearProgressIndicator
来实现:
import 'package:flutter/material.dart';
class AnimatedProgressPage extends StatefulWidget {
@override
_AnimatedProgressPageState createState() => _AnimatedProgressPageState();
}
class _AnimatedProgressPageState extends State<AnimatedProgressPage> {
double _progress = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animated Progress Example'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedLinearProgressIndicator(
value: _progress,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
// 可以添加动画曲线
curve: Curves.easeInOut,
),
ElevatedButton(
onPressed: () {
setState(() {
_progress = 0.5;
});
},
child: Text('Update Progress'),
),
],
),
);
}
}
在上述代码中,AnimatedLinearProgressIndicator
提供了平滑的进度变化动画。curve
属性可以设置动画曲线,如Curves.easeInOut
使进度变化更加自然。
错误处理与进度重置
在网络请求过程中,如果发生错误,不仅要向用户显示错误信息,还需要合理处理进度指示。一般来说,需要将进度重置为0,以便重新发起请求时进度指示能正确显示。
以下是在dio
下载示例中添加错误处理和进度重置的代码:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class DownloadPageWithErrorHandling extends StatefulWidget {
@override
_DownloadPageWithErrorHandlingState createState() => _DownloadPageWithErrorHandlingState();
}
class _DownloadPageWithErrorHandlingState extends State<DownloadPageWithErrorHandling> {
double _progress = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Download Example with Error Handling'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LinearProgressIndicator(
value: _progress,
),
ElevatedButton(
onPressed: () async {
Dio dio = Dio();
try {
await dio.download(
'https://example.com/file.zip',
'path/to/save/file.zip',
onReceiveProgress: (received, total) {
setState(() {
_progress = received / total;
});
},
);
print('Download completed');
} catch (e) {
setState(() {
_progress = 0;
});
print('Download error: $e');
}
},
child: Text('Download File'),
),
],
),
);
}
}
在上述代码中,当下载过程中发生错误时,通过setState
将_progress
重置为0,并打印错误信息,这样用户可以清楚地知道下载失败,并可以重新尝试下载。
预估剩余时间显示
在下载或上传大文件时,显示预估剩余时间可以让用户更好地了解等待情况。可以根据已完成的进度和过去一段时间内的下载/上传速度来预估剩余时间。
以下是在下载示例中添加预估剩余时间显示的代码:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'dart:async';
class DownloadPageWithETA extends StatefulWidget {
@override
_DownloadPageWithETAState createState() => _DownloadPageWithETAState();
}
class _DownloadPageWithETAState extends State<DownloadPageWithETA> {
double _progress = 0;
String _eta = 'Calculating...';
Stopwatch _stopwatch = Stopwatch();
@override
void initState() {
super.initState();
_stopwatch.start();
}
@override
void dispose() {
_stopwatch.stop();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Download Example with ETA'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LinearProgressIndicator(
value: _progress,
),
Text('Estimated Time Remaining: $_eta'),
ElevatedButton(
onPressed: () async {
Dio dio = Dio();
try {
await dio.download(
'https://example.com/file.zip',
'path/to/save/file.zip',
onReceiveProgress: (received, total) {
setState(() {
_progress = received / total;
double speed = received / _stopwatch.elapsed.inSeconds;
if (speed > 0) {
int remainingBytes = total - received;
int etaSeconds = (remainingBytes / speed).round();
_eta = '${Duration(seconds: etaSeconds).toString().split('.')[0]}';
}
});
},
);
print('Download completed');
} catch (e) {
setState(() {
_progress = 0;
_eta = 'Calculating...';
});
print('Download error: $e');
}
},
child: Text('Download File'),
),
],
),
);
}
}
在上述代码中,通过Stopwatch
记录下载开始的时间。在onReceiveProgress
回调中,根据已接收的字节数和已花费的时间计算下载速度,进而预估剩余时间并显示给用户。当下载发生错误时,重置进度和预估剩余时间。
通过以上方法,可以全面地提升Flutter应用中网络请求的进度指示,为用户提供更好的体验。无论是简单的单请求,还是复杂的并发、链式请求场景,都能有效地向用户展示请求状态,减少用户的焦虑,提高应用的可用性和用户满意度。同时,通过优化进度指示的动画效果、错误处理和预估剩余时间显示等方面,进一步提升用户体验,使应用更加完善。在实际开发中,应根据具体的业务需求和场景,灵活选择和组合这些方法,打造出流畅、友好的网络交互体验。