从 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 中,但逻辑上上面的代码应该可以工作。