首页 > 解决方案 > 每次获取数据时,我的 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

标签: flutter

解决方案


通过调用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),
      ),
    );
  }
}

推荐阅读