Flutter 将获取的数据附加到未来构建器中的列表视图
Flutter appending fetched data to a listview inside a future builder
我想问一下何时使用 FutureBuilder
在 ListView
中显示从远程服务器获取的数据。我使用 ScrollController
检查是否到达了 ListView
的底部。一切正常,直到我尝试加载新数据并将它们附加到现有的 ListView
我获取数据将它们添加到我的 Array
和 setState((){})
我更新列表 FutureBuilder
这显然是错误的方法,因为整个 FutureBuilder
和 ListView
都被重建了。但是,这些更改确实显示所有新项目都按预期出现在列表中,但是它不会显着降低性能,因为 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),
);
},
);
},
),
);
}
}
我想问一下何时使用 FutureBuilder
在 ListView
中显示从远程服务器获取的数据。我使用 ScrollController
检查是否到达了 ListView
的底部。一切正常,直到我尝试加载新数据并将它们附加到现有的 ListView
我获取数据将它们添加到我的 Array
和 setState((){})
我更新列表 FutureBuilder
这显然是错误的方法,因为整个 FutureBuilder
和 ListView
都被重建了。但是,这些更改确实显示所有新项目都按预期出现在列表中,但是它不会显着降低性能,因为 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),
);
},
);
},
),
);
}
}