flutter - 每次获取数据时,我的 FutureBuilder 都会触发两次
问题描述
我试图做一个简单的分页,但我的 GridView 的每一页都构建了两次。然后我试图记录我的代码中发生的事情。我认为我的问题是由在 fetch 方法中设置状态引起的,但我不确定,我不知道如何避免这种情况。这是代码:
import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:developer' as developer;
import 'package:test_webant/data/photoEntity.dart';
import 'package:http/http.dart' as http;
import 'package:test_webant/data/SingleImage.dart';
final galleryUrl = "http://gallery.dev.webant.ru/api/photos?popular=true&page=";
int page = 1;
bool isLoading = false;
class PopularGalleryGrid extends StatefulWidget{
@override
State<StatefulWidget> createState(){
return _PopularGalleryGridState();
}
}
class _PopularGalleryGridState extends State<PopularGalleryGrid>{
List<Photo> data = new List<Photo>();
ScrollController _scrollController = new ScrollController();
Future<List<Photo>> _fetchPhotos() async {
if (!isLoading) {
developer.log('fetch photos');
final response = await http.get(
galleryUrl + page.toString() + "&limit=10");
Map<String, dynamic> decodedJson = json.decode(response.body);
List photos = decodedJson['data'] as List;
return (photos.map((photo) => Photo.fromJson(photo))).toList();
}
setState(() {
isLoading = false;
});
}
@override
void initState(){
data.clear();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
developer.log('scroll controller');
setState(() {
isLoading = true;
});
page++;
_fetchPhotos();
}
});
super.initState();
}
@override
void dispose(){
super.dispose();
_scrollController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Photo>>(
future: _fetchPhotos(),
builder:(context, snapshot){
developer.log('builder');
if (snapshot.hasData){
List<Photo> tempList = snapshot.data;
data.addAll(tempList);
return _photoGridView(data);
}
else if (snapshot.hasError) {
return Text("${snapshot.hasError}");
}
return CircularProgressIndicator();
},
)
);
}
GridView _photoGridView(data){
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.0,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
),
itemCount: data.length,
itemBuilder: (context, index){
return Card(
elevation: 4,
child: GestureDetector(
onTap: () => _navigateToImage(context, data[index].id),
child:
Image.network(('http://gallery.dev.webant.ru/media/' + data[index].image)),
),
);
},
controller: _scrollController,
);
}
void _navigateToImage(BuildContext context, int id) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SingleImage(imageId: id),
),
);
}
}
这就是我在日志中得到的:
[log] fetch photos
[log] builder
[log] builder
I/ple.test_weban(15020): ProcessProfilingInfo new_methods=539 is saved saved_to_disk=1 resolve_classes_delay=8000
[log] scroll controller
[log] fetch photos
[log] builder
[log] builder
解决方案
通过调用setState
,您正在重建FutureBuilder
.
FutureBuilder
在构建时执行未来并等待其解决。
只需这样做(删除isLoading
逻辑):
Future<List<Photo>> _fetchPhotos() async {
developer.log('fetch photos');
final response = await http.get(
galleryUrl + page.toString() + "&limit=10");
Map<String, dynamic> decodedJson = json.decode(response.body);
List photos = decodedJson['data'] as List;
return (photos.map((photo) => Photo.fromJson(photo))).toList();
}
isLoading
在这里没用,因为如果你不在setState
里面调用,你FutureBuilder
只会被构建一次,并且_fetchPhotos
也会被调用一次。
编辑:使用流
这是您可以对流进行的测试。请注意,由于我没有您的项目,因此无法测试代码,因此您可能希望将其用作指南。
import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:developer' as developer;
import 'package:test_webant/data/photoEntity.dart';
import 'package:http/http.dart' as http;
import 'package:test_webant/data/SingleImage.dart';
final galleryUrl = "http://gallery.dev.webant.ru/api/photos?popular=true&page=";
int page = 1;
class PopularGalleryGrid extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _PopularGalleryGridState();
}
}
class _PopularGalleryGridState extends State<PopularGalleryGrid> {
List<Photo> data = new List<Photo>();
ScrollController _scrollController = new ScrollController();
StreamController<List<Photo>> _photosStreamController =
StreamController<List<Photo>>.broadcast();
Future<List<Photo>> _fetchPhotos() async {
developer.log('fetch photos');
try {
final response =
await http.get(galleryUrl + page.toString() + "&limit=10");
Map<String, dynamic> decodedJson = json.decode(response.body);
List photos = decodedJson['data'] as List;
List<Photo> result =
(photos.map((photo) => Photo.fromJson(photo))).toList();
data.addAll(result);
_photosStreamController.sink.add(data);
} catch (e) {
_photosStreamController.sink.addError(e);
}
}
@override
void initState() {
data.clear();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
developer.log('scroll controller');
page++;
_fetchPhotos();
}
});
_photosStreamController.onListen = _fetchPhotos;
super.initState();
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
_photosStreamController?.close();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<Photo>>(
stream: _photosStreamController.stream,
builder: (context, snapshot) {
developer.log('builder');
if (snapshot.hasData || data.isNotEmpty) {
return _photoGridView(snapshot.data ?? data);
} else if (snapshot.hasError) {
return Text("${snapshot.hasError}");
}
return CircularProgressIndicator();
},
));
}
GridView _photoGridView(data) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.0,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
),
itemCount: data.length,
itemBuilder: (context, index) {
return Card(
elevation: 4,
child: GestureDetector(
onTap: () => _navigateToImage(context, data[index].id),
child: Image.network(
('http://gallery.dev.webant.ru/media/' + data[index].image)),
),
);
},
controller: _scrollController,
);
}
void _navigateToImage(BuildContext context, int id) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SingleImage(imageId: id),
),
);
}
}
推荐阅读
- idris - 为什么 Idris 将值名称与随后定义的类型参数名称混为一谈?
- c# - 如何按名称查找歌曲并将其从播放列表中删除
- spring - AOP 配置中的问题似乎无效
- batch-file - 用于 nasm 编译器的可点击批处理文件
- reactjs - 反应中的onelogin OIDC令牌刷新
- css - 如何移除锚点 ::after 的悬停效果?
- php - 如何在自定义 php 项目中运行 composer 包
- sharepoint - SharePoint Online 删除网站集
- apache - Apache NiFi 1.6.0 升级后错误
- php - 循环遍历数据库结果似乎只执行一次