Flutter 将获取的数据附加到未来构建器中的列表视图

Flutter appending fetched data to a listview inside a future builder

我想问一下何时使用 FutureBuilderListView 中显示从远程服务器获取的数据。我使用 ScrollController 检查是否到达了 ListView 的底部。一切正常,直到我尝试加载新数据并将它们附加到现有的 ListView 我获取数据将它们添加到我的 ArraysetState((){}) 我更新列表 FutureBuilder 这显然是错误的方法,因为整个 FutureBuilderListView 都被重建了。但是,这些更改确实显示所有新项目都按预期出现在列表中,但是它不会显着降低性能,因为 ListView 不会使图块处于活动状态,但它对性能的影响很小,但主要问题是由于 ListView 已重建,我作为用户被抛到此列表的开头,这是因为 ListView 已重建。现在我想要实现的是 ListView 不会在我每次获取新数据时都得到重建。这是整个StateFulWidget

的代码
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';

import '../widgets/rss_card.dart';
import '../extensions/colors.dart';
import '../extensions/rss.dart';
import '../main.dart';
import '../models/rss.dart';

class RssListView extends StatefulWidget {
  final String? channel;

  const RssListView.fromChannel(this.channel, {Key? key}) : super(key: key);

  @override
  State<RssListView> createState() => _RssListViewState();
}

class _RssListViewState extends State<RssListView>
    with AutomaticKeepAliveClientMixin {
  late RssListModel _rssListModel;
  double _offset = 0.0;
  final double _limit = 5.0;
  Future<List<RssItemModel>?>? _rssFuture;
  final ScrollController _scrollController = ScrollController();

  Map<String, Object> _args({double? newOffset}) => {
        'offset': newOffset ?? _offset,
        'limit': _limit,
      };

  Future<bool> isConnected() async {
    var conn = await Connectivity().checkConnectivity();
    return (conn == ConnectivityResult.mobile ||
            conn == ConnectivityResult.wifi ||
            conn == ConnectivityResult.ethernet)
        ? true
        : false;
  }

  Future<void> _pullRefresh() async {
    _rssListModel.refresh(_args(
      newOffset: 0,
    ));
    List<RssItemModel>? refreshedRssItems = await _rssListModel.fetchData();
    setState(() {
      _rssFuture = Future.value(refreshedRssItems);
    });
  }

  Future<List<RssItemModel>?> get initialize async {
    await _rssListModel.initializationDone;
    return _rssListModel.Items;
  }

  void _loadMore() async {
    List<RssItemModel>? moreItems = await _rssListModel
        .loadMoreWithArgs(_args(newOffset: _offset += _limit));
    setState(() {
      _rssFuture = Future.value(moreItems);
    });
  }

  void _showSnackBarWithDelay({int? milliseconds}) {
    Future.delayed(
      Duration(milliseconds: milliseconds ?? 200),
      () {
        ScaffoldMessenger.of(context).showSnackBar(getDefaultSnackBar(
          message: 'No Internet Connection',
        ));
      },
    );
  }

  void _addScrollControllerListener() {
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          (_scrollController.position.maxScrollExtent)) _loadMore();
    });
  }

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    _rssListModel = RssListModel.fromChannel(widget.channel, _args());

    isConnected().then((internet) {
      if (!internet) {
        _showSnackBarWithDelay();
      } else {
        _addScrollControllerListener();
        setState(() {
          _rssFuture = initialize;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
      padding: const EdgeInsets.symmetric(
        vertical: 8,
        horizontal: 16,
      ),
      color: Colors.white,
      child: FutureBuilder<List<RssItemModel?>?>(
        future: _rssFuture,
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
            case ConnectionState.active:
              break;
            case ConnectionState.waiting:
              return getLoadingWidget();
            case ConnectionState.done:
              {
                if (!snapshot.hasData || snapshot.data!.isEmpty)
                  return _noDataView('No data to display');
                if (snapshot.hasError)
                  return _noDataView("There was an error while fetching data");

                return _refreshIndicator(snapshot);
              }
          }
          return _noDataView('Unable to fetch data from server');
        },
      ),
    );
  }

  /// Returns a `RefreshIndicator` wrapping our `ListView`
  Widget _refreshIndicator(AsyncSnapshot snapshot) => RefreshIndicator(
        backgroundColor: const Color.fromARGB(255, 255, 255, 255),
        triggerMode: RefreshIndicatorTriggerMode.anywhere,
        color: MyColors.Red,
        onRefresh: _pullRefresh,
        child: _listView(snapshot),
      );

  /// Returns a `ListView` builder from an `AsyncSnapshot`
  Widget _listView(AsyncSnapshot snapshot) => ListView.builder(
        controller: _scrollController,
        clipBehavior: Clip.none,
        itemCount: snapshot.data!.length,
        physics: const BouncingScrollPhysics(),
        itemBuilder: (context, index) => RssCard(snapshot.data![index]),
      );

  /// Returns a `Widget` informing of "No Data Fetched"
  Widget _noDataView(String message) => Center(
        child: Text(
          message,
          style: const TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.w800,
          ),
        ),
      );
}

您需要的是保持某些 Listenable 的状态,例如 ValueNotifier 并使用 ValueListenableBuilder 来构建您的 ListView。我把这个演示放在一起向你展示我的意思:

import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: const HomePage(),
  ));
}

@immutable
class Person {
  final String id;
  Person() : id = const Uuid().v4();
}

class DataController extends ValueNotifier<Iterable<Person>> {
  DataController() : super([]) {
    addMoreValues();
  }

  void addMoreValues() {
    value = value.followedBy(
      Iterable.generate(
        30,
        (_) => Person(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late final ScrollController _controller;
  final _generator = DataController();

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
    _controller.addListener(() {
      if (_controller.position.atEdge && _controller.position.pixels != 0.0) {
        _generator.addMoreValues();
      }
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      body: ValueListenableBuilder(
        valueListenable: _generator,
        builder: (context, value, child) {
          final persons = value as Iterable<Person>;
          return ListView.builder(
            controller: _controller,
            itemCount: persons.length,
            itemBuilder: (context, index) {
              final person = persons.elementAt(index);
              return ListTile(
                title: Text(person.id),
              );
            },
          );
        },
      ),
    );
  }
}