【问题标题】:how to build dynamic list from http server in flutter?如何在flutter中从http服务器构建动态列表?
【发布时间】:2018-07-17 10:06:58
【问题描述】:

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,
          );
  }

【问题讨论】:

    标签: dart flutter


    【解决方案1】:

    要实现这一点,您可以将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 方法以及图块的呈现方式),但我认为它会给您提供主要思路。

    【讨论】:

    • 是的,ScrollController 是个好主意。我运行你的代码示例,它显示了一个没有滚动的列表。
    • 哦,是的,您可能需要为此添加更多初始项目:List&lt;int&gt; items = List.generate(20, (i) =&gt; i);
    【解决方案2】:

    您正在寻找的是“无限滚动”。那里有 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 调用。它不是现有的类,而是您将实现的存储库的抽象表示。此外,当您调用_cache.putIfAbsent(index, () =&gt; MyWidget())时,您将指定您希望出现在列表中的Widget

    【讨论】:

      猜你喜欢
      • 2018-05-29
      • 2022-08-19
      • 1970-01-01
      • 2020-12-05
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 2020-04-17
      • 2014-08-26
      相关资源
      最近更新 更多