如何在Flutter中从http服务器构建动态列表?

时间:2018-07-17 10:06:58

标签: dart flutter

Flutter新手,从这里学到了很多东西。

在通常情况下,人们会从Web服务器请求资源,并且服务器返回对象数组:

[ { "key": "value"}, {"key": "value}...]

我们可以轻松地使用FutureBuilder进行处理。

但是我有一台具有海量数据的服务器,必须以这种方式获取资源:

  1. 使用api查询记录计数,例如“ / resources / counts”
  2. 使用api查询一些记录,说“ / resource?offset = 101&limit = 20”以获得20条记录。
  3. 当用户向下滚动菜单时,将触发“ / resource?offset = 121&limit = 20”以获取另外20条记录。

所以我有一个带有某种修复计数的列表,但是必须从服务器动态加载资源。怎么做?

一些代码。

 @override
  Widget build(BuildContext context) {
    // _max == -1, request in progress.
    return _max == -1
        ? new CircularProgressIndicator()
        : ListView.builder(
            padding: const EdgeInsets.all(16.0),
            itemBuilder: (context, i) {
              return new FutureBuilder(
                future: _getFollowingContracts(),
                builder: (context, snapshot) {
                  if (i.isOdd) return new Divider();
                  switch (snapshot.connectionState) {
                    case ConnectionState.none:
                    case ConnectionState.waiting:
                    case ConnectionState.active:
                      return new Text('loading...');
                    case ConnectionState.done:
                      if (i.isOdd)
                        return new Divider(
                          height: 2.0,
                        );
                      final int index = i ~/ 2;
                      // when user scroll down here we got exception
                      // because only 20 records is available. 
                      // how to get another 20 records?
                      return _buildRow(_contracts[index]);
                  }
                },
              );
            },
            itemCount: _max * 2,
          );
  }

2 个答案:

答案 0 :(得分:2)

要实现此目的,您可以将ScrollController附加到ListView上并收听其更改。在此侦听器中,当您处于滚动末尾时,将获取更多数据并更新您的应用状态。

示例:

以下代码摘自这篇经充分说明的博客文章:https://marcinszalek.pl/flutter/infinite-dynamic-listview/

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(primarySwatch: Colors.blue),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<int> items = List.generate(10, (i) => i);
  ScrollController _scrollController = new ScrollController();
  bool isPerformingRequest = false;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        _getMoreData();
      }
    });
  }

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

  _getMoreData() async {
    if (!isPerformingRequest) {
      setState(() => isPerformingRequest = true);
      List<int> newEntries = await fakeRequest(
          items.length, items.length + 10); //returns empty list
      if (newEntries.isEmpty) {
        double edge = 50.0;
        double offsetFromBottom = _scrollController.position.maxScrollExtent -
            _scrollController.position.pixels;
        if (offsetFromBottom < edge) {
          _scrollController.animateTo(
              _scrollController.offset - (edge - offsetFromBottom),
              duration: new Duration(milliseconds: 500),
              curve: Curves.easeOut);
        }
      }
      setState(() {
        items.addAll(newEntries);
        isPerformingRequest = false;
      });
    }
  }

  Widget _buildProgressIndicator() {
    return new Padding(
      padding: const EdgeInsets.all(8.0),
      child: new Center(
        child: new Opacity(
          opacity: isPerformingRequest ? 1.0 : 0.0,
          child: new CircularProgressIndicator(),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: AppBar(
        title: Text("Infinite ListView"),
      ),
      body: ListView.builder(
        itemCount: items.length + 1,
        itemBuilder: (context, index) {
          if (index == items.length) {
            return _buildProgressIndicator();
          } else {
            return ListTile(title: new Text("Number $index"));
          }
        },
        controller: _scrollController,
      ),
    );
  }
}

/// from - inclusive, to - exclusive
Future<List<int>> fakeRequest(int from, int to) async {
  return Future.delayed(Duration(seconds: 2), () {
    return List.generate(to - from, (i) => i + from);
  });
}

您将不得不使此代码适应您的情况(主要是通过更改fakeRequest方法以及如何渲染图块),但是我认为它将为您提供主要思想。

答案 1 :(得分:1)

您要寻找的是“无限滚动”。有Flutter教程对此进行了详细解释,在这里,我发现一种常见的模式效果很好。

import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';


class Feed extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new _FeedState();
}

class _FeedState extends State<Feed> {
  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
  final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
      new GlobalKey<RefreshIndicatorState>();

  bool _initialized;
  bool _readyToFetchNextPage;
  ScrollController _scrollController;
  Repository _repository;

  int _page;
  Map<int, Widget> _cache;

  @override
  void initState() {
    _initialized = false;
    _repository = Repository();
    _readyToFetchNextPage = true;
    _initialize();
    super.initState();
  }


  Future<Null> _initialize() async {
    _initialized = false;
    _readyToFetchNextPage = false;
    final Completer<Null> completer = new Completer<Null>();
    // Reset cache
    _cache = new Map();
    // Reset page
    _page = 1;
    // Fetch initial data
    List results = repository.getPage(_page);
    // Add results to the cache
    _update(_cache.length, results);
    completer.complete();
    // Let the refresh indicator know the method has completed
    _readyToFetchNextPage = true;
    // SetState must be called to dismiss the CircularProgressIndicator
    setState(() { _initialized = true; });
    return completer.future;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: new RefreshIndicator(
        key: _refreshIndicatorKey,
        onRefresh: _initialize,
        child: CustomScrollView(
          slivers: <Widget>[
            AppBar(),
            (_initialized) ? new SliverList(
              delegate: new SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                return _getItem(index);
              }),
            ) : new SliverFillRemaining(
              child: new Center(
                child: new CircularProgressIndicator(),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _getItem(int index) {
    if (_cache.containsKey(index)) {
      return _cache[index];
    } else {
      if (_readyToFetchNextPage) {
        _readyToFetchNextPage = false;
        _page += 1;
        _repository.getPage(_page).then((results) {
          setState(() {
            _update(_cache.length, results);
          });
        });
      }
      return null;
    }
  }

  void _update(int offset, List results) {
    for (int i = 0; i < results.length; i += 1) {
      int index = i + offset;
      _cache.putIfAbsent(
          index,
          () => new ListTile(
                key: new Key('post-$index'),
                title: Text(results[i]),
              ));
    }
    _readyToFetchNextPage = true;
  }
}

其工作原理如下。

  1. 您有一个Repository,它会根据页面编号来调用API
      

    例如:repository.getPage(1)=> GET http://api.com?page=1 =>列表

  2. 您收集结果并通过_update运行它们
  3. _update将所有这些帖子添加到缓存(地图)。
  4. ScrollView使用一个名为getItem(int index)的函数,该函数必须为给定的索引返回一个Widget
  5. 我的getItem函数在缓存中查找该项目,如果找到该项目,则将其返回。
  6. 如果找不到,我告诉Repository获取下一页并更新缓存,依此类推...

您当然应该考虑使用此模式,因为它将允许您按需获取新页面。

重要”这不是直接的实现。我在此代码示例中使用的Repository旨在向您显示在哪里进行API调用。它不是现有的类,而是要实现的存储库的抽象表示。此外,您将指定调用Widget

时要显示在列表中的_cache.putIfAbsent(index, () => MyWidget())