从 API 中获取多个页面并添加到流接收器
Fetching multiple pages from an API and adding to stream sink
我正在获取此 API https://rickandmortyapi.com/api/character 并将数据放入 Stream 中,这样我就可以在包含每个字符的卡片的 Gridview 上无限滚动。
使用 FutureBuilder
获取第一页是可行的,但尝试使用 StreamBuilder
不会更新任何内容,就好像它没有收到任何数据一样。
这是 Provider.dart
class CharacterProvider {
final _url = 'rickandmortyapi.com';
final _characterStream = StreamController<List<Character>>.broadcast();
List<Character> _characters = [];
int currentPage = 1;
Function(List<Character>) get characterSink => _characterStream.sink.add;
Stream<List<Character>> get characterStream => _characterStream.stream;
void dispose() {
_characterStream?.close();
}
Future<Map<String, dynamic>> fetchData(
String path, Map<String, dynamic> header) async {
print(header);
final response = await http.get(
Uri.https(_url, 'api/$path', header),
);
if (response.statusCode == 200) {
final results = jsonDecode(response.body);
return results;
} else {
throw Exception('Fallo al cargar personajes');
}
}
Future<List<Character>> fetchCharacters() async {
final path = 'character';
final header = {
'page': currentPage.toString(),
};
final data = await fetchData(path, header);
final characterFetched = Characters.fromJsonList(data['results']);
_characters.addAll(characterFetched.character);
characterSink(_characters);
if (currentPage < data['info']['pages']) {
currentPage++;
}
return characterFetched.character;
}
}
小部件中 StreamBuilder
的流已订阅 characterStream
但它始终为空。
class _CharacterCardsState extends State<CharacterCards> {
final _scrollController = ScrollController();
Future<List<Character>> _characters;
int cards;
bool loading;
@override
void initState() {
super.initState();
print('Cards: init');
_characters = initFetch();
loading = true;
cards = 6;
_scrollController.addListener(updateCards);
}
Future<List<Character>> initFetch() async {
final fetch = await CharacterProvider().fetchCharacters();
return fetch;
}
@override
Widget build(BuildContext context) {
CharacterProvider().fetchCharacters();
print('Cards: build');
return GridView.builder(
itemCount: cards,
controller: _scrollController,
itemBuilder: (context, index) {
return StreamBuilder(
stream: CharacterProvider().characterStream,
builder: (BuildContext context,
AsyncSnapshot<List<Character>> snapshot) {
if (snapshot.hasData) {
loading = false;
final character = snapshot.data;
return GestureDetector(
onTap: () {
cardView(context, character, index);
},
child: ofCard(character, index),
);
} else {
return ofLoading(widget.size);
}
},
);
});
}
在调试时,添加到接收器的值是非空的。数据正在正确获取,但 sink.add() 似乎无法正常工作。
我相信你正在尝试使用提供程序包(这就是为什么你命名你的 class CharacterProvider()
我认为),无论哪种方式,问题是你没有保存那个 class,每次调用 CharacterProvider().someMethod
时都会重新创建它们,因此 initFetch CharacterProvider().fetchCharacters()
和流 CharacterProvider().characterStream
不相关
就像您的 scrollController 一样,您应该创建一个 final characterProvider = CharacterProvider()
并在所有需要它的方法中调用它
PS:不要在构建中调用 future CharacterProvider().fetchCharacters();
,这是一个反模式
试试这个。
class _CharacterCardsState extends State<CharacterCards> {
final _scrollController = ScrollController();
Future<List<Character>> _characters;
int cards;
bool loading;
@override
void initState() {
super.initState();
_characters = CharacterProvider();
_characters.fetchCharacters();
loading = true;
cards = 6;
_scrollController.addListener(updateCards);
}
@override
void dispose(){
_characters.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: cards,
controller: _scrollController,
itemBuilder: (context, index) {
return StreamBuilder(
stream: _characters.characterStream,
builder: (BuildContext context,
AsyncSnapshot<List<Character>> snapshot) {
if (snapshot.hasData) {
setState(()=>loading=false);
final character = snapshot.data;
return GestureDetector(
onTap: () {
cardView(context, character, index);
},
child: ofCard(character, index),
);
} else {
return ofLoading(widget.size);
}
},
);
});
}
我不知道你为什么要将 streambuilder 放在 gridview 中,但逻辑上上面的代码应该可以工作。
我正在获取此 API https://rickandmortyapi.com/api/character 并将数据放入 Stream 中,这样我就可以在包含每个字符的卡片的 Gridview 上无限滚动。
使用 FutureBuilder
获取第一页是可行的,但尝试使用 StreamBuilder
不会更新任何内容,就好像它没有收到任何数据一样。
这是 Provider.dart
class CharacterProvider {
final _url = 'rickandmortyapi.com';
final _characterStream = StreamController<List<Character>>.broadcast();
List<Character> _characters = [];
int currentPage = 1;
Function(List<Character>) get characterSink => _characterStream.sink.add;
Stream<List<Character>> get characterStream => _characterStream.stream;
void dispose() {
_characterStream?.close();
}
Future<Map<String, dynamic>> fetchData(
String path, Map<String, dynamic> header) async {
print(header);
final response = await http.get(
Uri.https(_url, 'api/$path', header),
);
if (response.statusCode == 200) {
final results = jsonDecode(response.body);
return results;
} else {
throw Exception('Fallo al cargar personajes');
}
}
Future<List<Character>> fetchCharacters() async {
final path = 'character';
final header = {
'page': currentPage.toString(),
};
final data = await fetchData(path, header);
final characterFetched = Characters.fromJsonList(data['results']);
_characters.addAll(characterFetched.character);
characterSink(_characters);
if (currentPage < data['info']['pages']) {
currentPage++;
}
return characterFetched.character;
}
}
小部件中 StreamBuilder
的流已订阅 characterStream
但它始终为空。
class _CharacterCardsState extends State<CharacterCards> {
final _scrollController = ScrollController();
Future<List<Character>> _characters;
int cards;
bool loading;
@override
void initState() {
super.initState();
print('Cards: init');
_characters = initFetch();
loading = true;
cards = 6;
_scrollController.addListener(updateCards);
}
Future<List<Character>> initFetch() async {
final fetch = await CharacterProvider().fetchCharacters();
return fetch;
}
@override
Widget build(BuildContext context) {
CharacterProvider().fetchCharacters();
print('Cards: build');
return GridView.builder(
itemCount: cards,
controller: _scrollController,
itemBuilder: (context, index) {
return StreamBuilder(
stream: CharacterProvider().characterStream,
builder: (BuildContext context,
AsyncSnapshot<List<Character>> snapshot) {
if (snapshot.hasData) {
loading = false;
final character = snapshot.data;
return GestureDetector(
onTap: () {
cardView(context, character, index);
},
child: ofCard(character, index),
);
} else {
return ofLoading(widget.size);
}
},
);
});
}
在调试时,添加到接收器的值是非空的。数据正在正确获取,但 sink.add() 似乎无法正常工作。
我相信你正在尝试使用提供程序包(这就是为什么你命名你的 class CharacterProvider()
我认为),无论哪种方式,问题是你没有保存那个 class,每次调用 CharacterProvider().someMethod
时都会重新创建它们,因此 initFetch CharacterProvider().fetchCharacters()
和流 CharacterProvider().characterStream
不相关
就像您的 scrollController 一样,您应该创建一个 final characterProvider = CharacterProvider()
并在所有需要它的方法中调用它
PS:不要在构建中调用 future CharacterProvider().fetchCharacters();
,这是一个反模式
试试这个。
class _CharacterCardsState extends State<CharacterCards> {
final _scrollController = ScrollController();
Future<List<Character>> _characters;
int cards;
bool loading;
@override
void initState() {
super.initState();
_characters = CharacterProvider();
_characters.fetchCharacters();
loading = true;
cards = 6;
_scrollController.addListener(updateCards);
}
@override
void dispose(){
_characters.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: cards,
controller: _scrollController,
itemBuilder: (context, index) {
return StreamBuilder(
stream: _characters.characterStream,
builder: (BuildContext context,
AsyncSnapshot<List<Character>> snapshot) {
if (snapshot.hasData) {
setState(()=>loading=false);
final character = snapshot.data;
return GestureDetector(
onTap: () {
cardView(context, character, index);
},
child: ofCard(character, index),
);
} else {
return ofLoading(widget.size);
}
},
);
});
}
我不知道你为什么要将 streambuilder 放在 gridview 中,但逻辑上上面的代码应该可以工作。