【问题标题】:How to perform a text search over JSON data in Flutter?如何在 Flutter 中对 JSON 数据执行文本搜索?
【发布时间】:2020-09-29 14:23:20
【问题描述】:

我正在开发一个应用程序,该应用程序需要从 Firebase 存储中获取一些 JSON 数据,然后用户可以对其执行搜索。但是,我的搜索查询无法正常工作,并且在我多次执行搜索时出现一个错误。

我首先将数据上传到 Firebase 存储,然后再获取它。搜索有时只起作用。我无法弄清楚代码有什么问题。

这是完整的代码

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:convert';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Contacts Demo',
      debugShowCheckedModeBanner: false,
      home: StorageUpload(),
    );
  }
}

class StorageUpload extends StatefulWidget {
  @override
  StorageUploadState createState() => new StorageUploadState();
}

class StorageUploadState extends State<StorageUpload> {
  var rows = [];
  String query = '';
  TextEditingController tc;

  @override
  void initState() {
    super.initState();
    tc = TextEditingController();
    rows = [
      {
        'contact_name': 'Test User 1',
        'contact_phone': '066 560 4900',
      },
      {
        'contact_name': 'Test User 2',
        'contact_phone': '066 560 7865',
      },
      {
        'contact_name': 'Test User 3',
        'contact_phone': '906 500 4334',
      }
    ];
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(
          "Delivero Contacts",
          style: new TextStyle(
            color: Colors.white,
          ),
        ),
      ),
      body: Container(
        color: Colors.white,
        padding: EdgeInsets.all(10),
        child: Stack(
          children: [
            Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: TextField(
                    controller: tc,
                    decoration: InputDecoration(hintText: 'Search...'),
                    onChanged: (v) {
                      setState(() {
                        query = v;
                      });
                    },
                  ),
                ),
                Container(
                  color: Colors.white,
                  child: ListView.builder(
                    shrinkWrap: true,
                    itemCount: rows.length,
                    itemBuilder: (con, ind) {
                      return query.isEmpty
                          ? ListTile(
                              title: Text(rows[ind]['contact_name']),
                              subtitle: Text(rows[ind]['contact_phone']),
                              onTap: () {
                                setState(() {
                                  tc.text = rows[ind]['contact_name'];
                                  query = rows[ind]['contact_name'];
                                });
                              },
                            )
                          : rows[ind]['contact_name']
                                      .toString()
                                      .toLowerCase()
                                      .contains(query.toLowerCase()) ||
                                  rows[ind]['contact_phone']
                                      .toString()
                                      .toLowerCase()
                                      .contains(query.toLowerCase())
                              ? ListTile(
                                  title: Text(rows[ind]['contact_name']),
                                  subtitle: Text(rows[ind]['contact_phone']),
                                  onTap: () {
                                    setState(() {
                                      tc.text = rows[ind]['contact_name'];
                                      query = rows[ind]['contact_name'];
                                    });
                                  },
                                )
                              : null;
                    },
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

这是我遇到的错误


════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performLayout():
'package:flutter/src/rendering/sliver_multi_box_adaptor.dart': Failed assertion: line 662 pos 16: 'indexOf(child) == index': is not true.


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=BUG.md

The relevant error-causing widget was
    ListView 
lib/main.dart:104
When the exception was thrown, this was the stack
#2      RenderSliverMultiBoxAdaptor.debugAssertChildListIsNonEmptyAndContiguous.<anonymous closure> 
package:flutter/…/rendering/sliver_multi_box_adaptor.dart:662
#3      RenderSliverMultiBoxAdaptor.debugAssertChildListIsNonEmptyAndContiguous 
package:flutter/…/rendering/sliver_multi_box_adaptor.dart:666
#4      RenderSliverList.performLayout 
package:flutter/…/rendering/sliver_list.dart:282
#5      RenderObject.layout 
package:flutter/…/rendering/object.dart:1767
#6      RenderSliverEdgeInsetsPadding.performLayout 
package:flutter/…/rendering/sliver_padding.dart:135
...
The following RenderObject was being processed when the exception was fired: RenderSliverList#5c228 relayoutBoundary=up17 NEEDS-LAYOUT NEEDS-COMPOSITING-BITS-UPDATE
RenderObject: RenderSliverList#5c228 relayoutBoundary=up17 NEEDS-LAYOUT NEEDS-COMPOSITING-BITS-UPDATE
    needs compositing
    parentData: paintOffset=Offset(0.0, 0.0) (can use size)
    constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 0.0, remainingPaintExtent: Infinity, crossAxisExtent: 372.7, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: Infinity, remainingCacheExtent: Infinity, cacheOrigin: 0.0)
    geometry: SliverGeometry(scrollExtent: 224.0, paintExtent: 224.0, maxPaintExtent: 224.0, cacheExtent: 224.0)
        scrollExtent: 224.0
        paintExtent: 224.0
        maxPaintExtent: 224.0
        cacheExtent: 224.0
    currently live children: 0 to 3
    child with index 0: RenderIndexedSemantics#ea1e2 relayoutBoundary=up18
        needs compositing
        parentData: index=0; layoutOffset=0.0 (can use size)
        constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
        semantic boundary
        size: Size(372.7, 56.0)
        index: 0
        child: RenderRepaintBoundary#17c6b relayoutBoundary=up19
            needs compositing
            parentData: <none> (can use size)
            constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
            layer: OffsetLayer#34eb7
                engine layer: OffsetEngineLayer#a8a79
                offset: Offset(0.0, 0.0)
            size: Size(372.7, 56.0)
            metrics: 0.0% useful (1 bad vs 0 good)
            diagnosis: insufficient data to draw conclusion (less than five repaints)
            child: RenderSemanticsAnnotations#2ecd5 relayoutBoundary=up20
                parentData: <none> (can use size)
                constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
                size: Size(372.7, 56.0)
                child: RenderMouseRegion#9a9a8 relayoutBoundary=up21
                    parentData: <none> (can use size)
                    constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
                    size: Size(372.7, 56.0)
                    listeners: enter, exit
                    cursor: SystemMouseCursor(click)
    child with index 3: RenderIndexedSemantics#96641 relayoutBoundary=up18
        needs compositing
        parentData: index=3; layoutOffset=168.0 (can use size)
        constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
        semantic boundary
        size: Size(372.7, 56.0)
        index: 3
        child: RenderRepaintBoundary#13951 relayoutBoundary=up19
            needs compositing
            parentData: <none> (can use size)
            constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
            layer: OffsetLayer#768be
                engine layer: OffsetEngineLayer#51b91
                offset: Offset(0.0, 168.0)
            size: Size(372.7, 56.0)
            metrics: 0.0% useful (1 bad vs 0 good)
            diagnosis: insufficient data to draw conclusion (less than five repaints)
            child: RenderSemanticsAnnotations#77b2c relayoutBoundary=up20
                parentData: <none> (can use size)
                constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
                size: Size(372.7, 56.0)
                child: RenderMouseRegion#9ae66 relayoutBoundary=up21
                    parentData: <none> (can use size)
                    constraints: BoxConstraints(w=372.7, 0.0<=h<=Infinity)
                    size: Size(372.7, 56.0)
                    listeners: enter, exit
                    cursor: SystemMouseCursor(click)
════════════════════════════════════════════════════════════════════════════════

我是 Flutter 菜鸟。如何从从 firebase 存储中获取的数据中对不同的 JSON 字段执行全文搜索?任何帮助都会很棒。感谢您的宝贵时间!

【问题讨论】:

  • 这似乎更像是一个 GUI 问题。尝试将搜索部分外推到更简单的 Flutter 应用程序或 DartPad 上的仅 Dart 应用程序中,以检查每个部分是否正常工作。
  • @camillo777 我尝试了这种方法,但仍然遇到与以前相同的错误。
  • 你好@Bhawna,请发布更简单的代码,以便更容易发现错误。
  • 您好@camillo777,感谢您的回复。我已经简化了代码,希望对您有所帮助。
  • 你好@Bhawna,我为你添加了一个带有工作代码的答案。

标签: json flutter firebase-storage


【解决方案1】:
import 'dart:async';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Contacts Demo',
      debugShowCheckedModeBanner: false,
      home: StorageUpload(),
    );
  }
}

class StorageUpload extends StatefulWidget {
  @override
  StorageUploadState createState() => new StorageUploadState();
}

class StorageUploadState extends State<StorageUpload> {
  List<Map<String,dynamic>> original=[],duplicate=[];
  String query = '';
  TextEditingController tc;
  StreamController<List<Map<String,dynamic>>> controller = StreamController<List<Map<String,dynamic>>>();

  @override
  void initState() {
    super.initState();
    tc = TextEditingController();
    original = [
      {
        'contact_name': 'Test User 1',
        'contact_phone': '066 560 4900',
      },
      {
        'contact_name': 'Test User 2',
        'contact_phone': '066 560 7865',
      },
      {
        'contact_name': 'Test User 3',
        'contact_phone': '906 500 4334',
      }
    ];
    duplicate.addAll(original);
    controller.sink.add(duplicate);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(
          "Delivero Contacts",
          style: new TextStyle(
            color: Colors.white,
          ),
        ),
      ),
      body: Container(
        color: Colors.white,
        padding: EdgeInsets.all(10),
        child: Stack(
          children: [
            Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: TextField(
                    controller: tc,
                    decoration: InputDecoration(hintText: 'Search...'),
                    onChanged: (v) {
                      if(v.isEmpty){
                        duplicate = original;
                        controller.sink.add(duplicate);
                        return;
                      }

                      //If your data is a Map object you can use toString()
                      //and avoid checking both fields
                      duplicate = original.where((m)=>
                                                 m['contact_name'].toString().toLowerCase().contains(v.toLowerCase())||
                                                m['contact_phone'].toString().toLowerCase().contains(v.toLowerCase())).toList();
                      controller.sink.add(duplicate);
                    },
                  ),
                ),
                Container(
                  color: Colors.white,
                  child: StreamBuilder<Object>(
                    stream: controller.stream,
                    builder: (context, snapshot) {
                      return ListView.builder(
                        shrinkWrap: true,
                        itemCount: duplicate.length,
                        itemBuilder: (con, ind) {
                          return query.isEmpty
                              ? ListTile(
                                  title: Text(duplicate[ind]['contact_name']),
                                  subtitle: Text(duplicate[ind]['contact_phone']),
                                  onTap: () {
                                    setState(() {
                                      tc.text = duplicate[ind]['contact_name'];
                                      query = duplicate[ind]['contact_name'];
                                    });
                                  },
                                )
                              : duplicate[ind]['contact_name']
                                          .toString()
                                          .toLowerCase()
                                          .contains(query) ||
                                      duplicate[ind]['contact_phone']
                                          .toString()
                                          .toLowerCase()
                                          .contains(query)
                                  ? ListTile(
                                      title: Text(duplicate[ind]['contact_name']),
                                      subtitle: Text(duplicate[ind]['contact_phone']),
                                      onTap: () {
                                        setState(() {
                                          tc.text = duplicate[ind]['contact_name'];
                                          query = duplicate[ind]['contact_name'];
                                        });
                                      },
                                    ):null;
                        },
                      );
                    }
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
  @override
  void dispose(){
    controller?.close();
    super.dispose();
  }
}

【讨论】:

  • 非常感谢您的回答。你能告诉我我做错了什么吗?
  • 您的代码没有问题!但是这个答案很有效,因为它只更新列表视图而不重绘整个小部件树!可能是在列表视图中使用过滤器的问题!
【解决方案2】:

我稍微更改了您的代码以制作一个工作示例。 我添加了一个结果列表对象,并在 ListView 之前移动了“query.isEmpty”检查:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Contacts Demo',
      debugShowCheckedModeBanner: false,
      home: StorageUpload(),
    );
  }
}

class StorageUpload extends StatefulWidget {
  @override
  StorageUploadState createState() => new StorageUploadState();
}

class StorageUploadState extends State<StorageUpload> {
  List results = [];

  var rows = [];
  String query = '';
  TextEditingController tc;

  @override
  void initState() {
    super.initState();
    tc = TextEditingController();
    rows = [
      {
        'contact_name': 'Test User 1',
        'contact_phone': '066 560 4900',
      },
      {
        'contact_name': 'Test User 2',
        'contact_phone': '066 560 7865',
      },
      {
        'contact_name': 'Test User 3',
        'contact_phone': '906 500 4334',
      }
    ];
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(
          "Delivero Contacts",
          style: new TextStyle(
            color: Colors.white,
          ),
        ),
      ),
      body: Container(
        color: Colors.white,
        padding: EdgeInsets.all(10),
        child: Stack(
          children: [
            Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: TextField(
                    controller: tc,
                    decoration: InputDecoration(hintText: 'Search...'),
                    onChanged: (v) {
                      setState(() {
                        query = v;
                        setResults(query);
                      });
                    },
                  ),
                ),
                Container(
                  color: Colors.white,
                  child: query.isEmpty
                      ? ListView.builder(
                          shrinkWrap: true,
                          itemCount: rows.length,
                          itemBuilder: (con, ind) {
                            return ListTile(
                              title: Text(rows[ind]['contact_name']),
                              subtitle: Text(rows[ind]['contact_phone']),
                              onTap: () {
                                setState(() {
                                  tc.text = rows[ind]['contact_name'];
                                  query = rows[ind]['contact_name'];
                                  setResults(query);
                                });
                              },
                            );
                          },
                        )
                      : ListView.builder(
                          shrinkWrap: true,
                          itemCount: results.length,
                          itemBuilder: (con, ind) {
                            return ListTile(
                              title: Text(results[ind]['contact_name']),
                              subtitle: Text(results[ind]['contact_phone']),
                              onTap: () {
                                setState(() {
                                  tc.text = results[ind]['contact_name'];
                                  query = results[ind]['contact_name'];
                                  setResults(query);
                                });
                              },
                            );
                          },
                        ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  void setResults(String query) {
    results = rows
        .where((elem) =>
            elem['contact_name']
                .toString()
                .toLowerCase()
                .contains(query.toLowerCase()) ||
            elem['contact_phone']
                .toString()
                .toLowerCase()
                .contains(query.toLowerCase()))
        .toList();
  }
}

【讨论】:

  • 非常感谢。有效。一个问题,我的代码出了什么问题?
  • @Bhawna 首先你要返回“null”值,在构建方法中你不能有任何 null 小部件。然后你在构建方法中进行“包含”检查,这是不干净的; build 方法应该只基于一些变量来构建 GUI 以便快速。逻辑和变量应该放在 GUI 之外,这是状态管理的基础。保持代码干净,并将状态放在 1 个外部类中。 GUI 只会采用该类并呈现它。
猜你喜欢
  • 1970-01-01
  • 2022-08-18
  • 2018-10-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-04
  • 2012-04-18
  • 2021-05-08
  • 2011-07-10
相关资源
最近更新 更多