【问题标题】:JSON - Flutter - Loading json list only onceJSON - Flutter - 仅加载一次 json 列表
【发布时间】:2021-05-16 06:45:54
【问题描述】:

我编写了一个代码,它将从 FoodData API 获取数据-https://fdc.nal.usda.gov/api-guide.html 并将其显示在列表中。我还创建了一个 TextField,以便用户可以搜索特定的菜肴。 一切正常,但我遇到了一个小问题 -

列表每隔几秒刷新一次,因此过滤器列表会显示几秒钟,然后列表会进入其初始外观。

我想让列表只加载一次,并且过滤器功能仍然可以正常工作。

这是我的代码-

import 'dart:async';
import 'dart:convert';

import 'package:fit_app/fitness_app_theme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<List<FoodGen>> fetchPhotos(http.Client client) async {
  final response =
      await client.get('https://api.nal.usda.gov/fdc/v1/foods/list?dataType=Foundation,SR%20Legacy&pageSize=200&api_key=APIKEY');

  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}

// A function that converts a response body into a List<Photo>.
List<FoodGen> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<FoodGen>((json) => FoodGen.fromJson(json)).toList();
}

class FoodGen {
  int fdcId;
  String description;
  String dataType;
  String publicationDate;
  String ndbNumber;
  List<FoodNutrients> foodNutrients;

  FoodGen(
      {this.fdcId,
      this.description,
      this.dataType,
      this.publicationDate,
      this.ndbNumber,
      this.foodNutrients});

  FoodGen.fromJson(Map<String, dynamic> json) {
    fdcId = json['fdcId'];
    description = json['description'];
    dataType = json['dataType'];
    publicationDate = json['publicationDate'];
    ndbNumber = json['ndbNumber'];
    if (json['foodNutrients'] != null) {
      foodNutrients = new List<FoodNutrients>();
      json['foodNutrients'].forEach((v) {
        foodNutrients.add(new FoodNutrients.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['fdcId'] = this.fdcId;
    data['description'] = this.description;
    data['dataType'] = this.dataType;
    data['publicationDate'] = this.publicationDate;
    data['ndbNumber'] = this.ndbNumber;
    if (this.foodNutrients != null) {
      data['foodNutrients'] =
          this.foodNutrients.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

class FoodNutrients {
  String number;
  String name;
  dynamic amount;
  String unitName;
  String derivationCode;
  String derivationDescription;

  FoodNutrients(
      {this.number,
      this.name,
      this.amount,
      this.unitName,
      this.derivationCode,
      this.derivationDescription});

  FoodNutrients.fromJson(Map<String, dynamic> json) {
    number = json['number'];
    name = json['name'];
    amount = json['amount'];
    unitName = json['unitName'];
    derivationCode = json['derivationCode'];
    derivationDescription = json['derivationDescription'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['number'] = this.number;
    data['name'] = this.name;
    data['amount'] = this.amount;
    data['unitName'] = this.unitName;
    data['derivationCode'] = this.derivationCode;
    data['derivationDescription'] = this.derivationDescription;
    return data;
  }
}

class FoodPage extends StatefulWidget {
  FoodPage({Key key}) : super(key: key);

  @override
  _FoodPageState createState() => _FoodPageState();
}

class _FoodPageState extends State<FoodPage> {
  TextEditingController editingController = TextEditingController();
  List<FoodGen> dishes;
  List<FoodGen> dishesFilter;
  List<FoodGen> duplicateItems;
  List<FoodGen> dummySearchList = List<FoodGen>();

  @override
  void initState() { 
    super.initState();
  }
  void filterSearchResults(String query) {
    if(dishes == null)return;
    dummySearchList.addAll(dishes);
    if(query.isNotEmpty) {
    List<FoodGen> dummyListData = List<FoodGen>();
      dummySearchList.forEach((item) {
            String queryLowercase = query.toLowerCase(); 
            if(item.description.toLowerCase().startsWith('$queryLowercase')){
              dummyListData.add(item);
        }
        }
      );
      setState(() {
        dishes.clear();
        dishes.addAll(dummyListData);
      });
      return;
    } else {
      setState(() {
        dishes.clear();
        dishes.addAll(duplicateItems);
      });
    }

  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      backgroundColor: FitnessAppTheme.darkBackground,
      body: SafeArea(
        top: true,
        child:Container(
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
                child: TextField(
                style: TextStyle(color: Colors.white),

                onChanged: (value) {
                    filterSearchResults(value);
                },
                controller: editingController,
                decoration: InputDecoration(
                    hintStyle: TextStyle(color:FitnessAppTheme.white),
                    labelStyle: TextStyle(color:FitnessAppTheme.white), 
                    labelText: "Search",
                    hintText: "Search",
                    prefixIcon: Icon(Icons.search),
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.all(Radius.circular(25.0)))),
              ),
              ),

            Expanded(
              child: FutureBuilder<List<FoodGen>>(
                future: fetchPhotos(http.Client()),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                  dishes = snapshot.data;
                  return snapshot.hasData
                      ? ListView.builder(
 
                        itemCount: dishes.length,
                        itemBuilder: (context, index) {
                          return Card(
                            child: ListTile(title: Text(dishes[index].description))
                          );   
                        },
                      )
                      : Center(child: CircularProgressIndicator());
                  }
                },
              ),
            ),
          ],
        ),
      ),
      ),
    );
  }
}

非常感谢您在这里的帮助(:

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    只需将fetchPhotos 放在类变量中并在FutureBuilder 中使用它:

    var photosRequest;
    
    @override
    void initState() {
      photosRequest = fetchPhotos(client);
    }
    

    FutureBuilder:

    builder: photosRequest.then((photos) => filterSearchResults(photos)),
    

    如果您从变量中调用Future 并且变量不能更改FutureBuilder 不能再次调用它并使用之前获取的数据。

    【讨论】:

    • 我想你误解了我的意思。我想用 initState void 中的 JSON 数据初始化菜肴列表一次,在 FutureBuilder 中使用它并使用过滤器函数对其进行过滤。我怎样才能做到这一点? @fartem
    • 您可以将过滤器参数传递给fetchPhotos,并在每次更改过滤器参数时过滤项目。
    • 你能告诉我怎么做吗?我不确定我是否理解@fartem
    • 抛出了另一个异常:类型 'Future' 不是类型 '(BuildContext, AsyncSnapshot>) => Widget' @fartem 的子类型
    • 您应该从filterSearchResults 返回List&lt;FoodGen&gt;,并且只使用一个列表来处理所有和排序的项目。默认情况下,您有一个加载的数据存储在列表中,过滤后,您需要更改原始列表并再次从中显示数据。
    【解决方案2】:

    您可以使用 Provider Package 和 Listen: false ,这将调用一次,

    这是这个包的例子希望它对你有帮助。

    将此添加到您的包的 pubspec.yaml 文件中:

    dependencies:
      provider: ^4.3.3
    
    
    
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    /// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].
    
    void main() {
      runApp(
        /// Providers are above [MyApp] instead of inside it, so that tests
        /// can use [MyApp] while mocking the providers
        MultiProvider(
          providers: [
            ChangeNotifierProvider(create: (_) => Counter()),
          ],
          child: const MyApp(),
        ),
      );
    }
    
    /// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
    // ignore: prefer_mixin
    class Counter with ChangeNotifier, DiagnosticableTreeMixin {
      int _count = 0;
    
      int get count => _count;
    
      void increment() {
        _count++;
        notifyListeners();
      }
    
      /// Makes `Counter` readable inside the devtools by listing all of its properties
      @override
      void debugFillProperties(DiagnosticPropertiesBuilder properties) {
        super.debugFillProperties(properties);
        properties.add(IntProperty('count', count));
      }
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Example'),
          ),
          body: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: const <Widget>[
                Text('You have pushed the button this many times:'),
    
                /// Extracted as a separate widget for performance optimization.
                /// As a separate widget, it will rebuild independently from [MyHomePage].
                ///
                /// This is totally optional (and rarely needed).
                /// Similarly, we could also use [Consumer] or [Selector].
                Count(),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            key: const Key('increment_floatingActionButton'),
    
            /// Calls `context.read` instead of `context.watch` so that it does not rebuild
            /// when [Counter] changes.
            onPressed: () => context.read<Counter>().increment(),
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    
    class Count extends StatelessWidget {
      const Count({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Text(
    
            /// Calls `context.watch` to make [Count] rebuild when [Counter] changes.
            '${context.watch<Counter>().count}',
            key: const Key('counterState'),
            style: Theme.of(context).textTheme.headline4);
      }
    }
    

    【讨论】:

    • 你能在我的脚本中实现它吗? @Tasnuva oshin
    猜你喜欢
    • 1970-01-01
    • 2019-07-28
    • 2021-10-15
    • 2023-01-17
    • 2023-03-18
    • 1970-01-01
    • 2018-10-02
    • 2016-10-31
    • 2021-07-02
    相关资源
    最近更新 更多