【问题标题】:How to receive data back to flutter when calling a method using js.context.callMethod()?使用js.context.callMethod()调用方法时如何接收数据返回flutter?
【发布时间】:2020-10-17 20:07:08
【问题描述】:

我正在使用 Flutter Web 构建一个 chrome 扩展,它只是在主屏幕上简单的ListView 和基本的 CRUD 操作。我正在使用dart:js 包从 JS 文件中调用方法,该文件在 Firebase 实时数据库上执行一些操作。

通过add() 方法调用正在向数据库添加新条目。读取操作在 JS 文件中也可以正常工作。

我的主要问题是我应该如何将数据库信息读取为 JSON,对其进行解析并在 Flutter 中将其显示为 ListView


这里是main.dartAddAttendee.dart -

import 'dart:js' as js;
import 'dart:convert';

import 'package:flutter/material.dart';
import 'AddAttendee.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Google Flutter Meet Attendance',
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  List<Map> data;

  getAttendees() async {
    var obj = await js.context.callMethod('read');
    List<Map> users = (jsonDecode(obj) as List<dynamic>).cast<Map>();
    setState(() {
      data = users;
    });
  }

  @override
  void initState() {
    getAttendees();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Current Attendees"),
      ),
      body: data != null
          ? ListView.builder(
              padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
              itemCount: data.length,
              itemBuilder: (context, index) {
                return ListTile(
                  leading: Icon(Icons.person),
                  title: Text(data.toString()[index]), // I don't know how to read correctly
                );
              },
            )
          : Center(child: CircularProgressIndicator()),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add to Attendance',
        child: Icon(Icons.person_add),
        onPressed: () => Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => AddAttendee(),
          ),
        ),
      ),
    );
  }
}
import 'dart:js' as js;

import 'package:flutter/material.dart';

class AddAttendee extends StatefulWidget {
  @override
  _AddAttendeeState createState() => _AddAttendeeState();
}

class _AddAttendeeState extends State<AddAttendee> {
  final _formKey = GlobalKey<FormState>();
  final TextEditingController _textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Add to Attendance List"),
      ),
      body: Form(
        key: _formKey,
        child: SingleChildScrollView(
          padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
          child: TextFormField(
            autocorrect: false,
            autofocus: true,
            controller: _textController,
            onFieldSubmitted: (value) => _textController.text = value,
            decoration: InputDecoration(labelText: "Name"),
            keyboardType: TextInputType.text,
            textInputAction: TextInputAction.next,
            textCapitalization: TextCapitalization.sentences,
            validator: (value) {
              if (value.isEmpty) return 'This field is mandatory';
              return null;
            },
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: "Done",
        child: Icon(Icons.done),
        onPressed: () {
          if (_formKey.currentState.validate()) {
            js.context.callMethod('add', [_textController.text]);
            Navigator.pop(context);
          }
        },
      ),
    );
  }
}

这是JS代码-

const dbRef = firebase.database().ref();

var userList = [];

function read() {
  dbRef.once("value", function (snapshot) {
    userList = [];
    snapshot.forEach(function (childSnapshot) {
      var key = childSnapshot.key;
      var user = childSnapshot.val();
      userList.push({ key, user });
    });
  });
  return userList;
}

function add(user) {
  let newUser = { name: user };

  dbRef.push(newUser, function () {
    console.log("Attendance Record Updated - New Attendee Added");
  });
}

Firebase RT DB 中的数据库结构 -

解析时的数据库结构-


调试这段代码真是令人沮丧,因为我不能printmain.dart 的输出,如果发生任何错误并引发异常,那么它会以转编译的main.dart.js 文件的形式显示,这是不可能的阅读。

我一直无法从 JS 文件中的read() 方法取回数据到main.dart。该怎么做?

一些参考链接-

https://twitter.com/rodydavis/status/1197564669633978368 https://www.reddit.com/r/FlutterDev/comments/dyd8j5/create_chrome_extension_running_flutter/ https://github.com/rodydavis/dart_pad_ext/

【问题讨论】:

  • 当前输出是多少?我从js.context.callMethod 检索输出没有遇到问题。

标签: javascript flutter dart flutter-web


【解决方案1】:

当调用callMethod 时,它返回的正是javascript 函数返回的内容。它不会像您的代码当前所暗示的那样返回 json 编码的 String。因此没有必要对其进行解码。 var obj几乎可以被视为List&lt;Map&lt;String, dynamic&gt;&gt; 对象。这意味着您可以在 dart 代码中使用 js.context.callMethod("read")[0]['key'] 访问列表的第一个对象和 key 属性。您可以删除代码中的所有 json 解码和强制转换,并将其替换为使用这种访​​问返回数据的方法的函数,您自己将其转换为实际的 List&lt;Map&lt;String, dynamic&gt;&gt;

例如getAttendees修改:

Future<void> getAttendees() async {
  List<Map<String, dynamic>> toReturn = List();
  js.JsArray obj = js.context.callMethod("read");
  for(js.JsObject eachObj in obj) {
    //For every object in the array, convert it to a `Map` and add to toReturn
    toReturn.add({      
      'key': eachObj['key'],
      'user': eachObj['user'],
    });
  }
  setState(() {
    data = toReturn;
  });
}

如果您出于某种原因发现需要使用 json,则需要在 javascript 端进行编码,但我认为没有理由这样做,因为它只会增加复杂性。

build函数中显示数据时data.toString()[index]会先将javascript输出转换成String这样的:"[{key: key}, {key: key}, ...]"然后找到indexString em>,而不是 List 对象,这可能是您想要的。为此,您首先使用索引data[index],然后添加您要查找的字段data[index]['key'],因为数据是ListMaps。

与您遇到的问题直接无关,但一些一般性的颤振建议是使用 FutureBuilder 而不是您当前在 async 函数完成时显示数据的方法。你的方法几乎做了FutureBuilder会做的事,只是效率低了一点,可读性也差了。

编辑: 将read 处理为Promise

首先,您必须将 js: ^0.6.2 添加到您的 pubspec.yaml 依赖项中。

那么这必须添加到你的代码中的某处:

@JS()
library script.js;

import 'package:js/js.dart';
import 'dart:js_util';

@JS()
external dynamic read();

js.JsArray obj = js.context.callMethod("read"); 应该修改为 await promiseToFuture(read())。整个getAttendees应该修改为:

Future<void> getAttendees() async {
  List<Map<String, dynamic>> toReturn = List();
  dynamic obj = await promiseToFuture(read());
  for(dynamic eachObj in obj) {
    //For every object in the array, convert it to a `Map` and add to toReturn
    toReturn.add({      
      'key': eachObj.key,
      'user': eachObj.user,
    });
  }
  setState(() {
    data = toReturn;
  });
}

【讨论】:

  • 感谢您为这个问题付出的努力。我让调试工作,错误显示Expected a value of type 'List&lt;Map&lt;String, dynamic&gt;&gt;', but got one of type 'JsObject'。没有进行隐式转换,我们必须从 JsObject 显式转换它。
  • 是的,我知道这一点。我的回答已经解决了这个问题。请参阅我的示例代码。我说它可以几乎被视为该数据类型。我的代码进行了显式转换。
  • 由于 JS 方法 read()async 性质,此代码不起作用。该方法首先返回一个空数组,然后更新该数组。我一直无法找到解决方法。 (我已将 read() 方法修改为异步,否则没有任何效果)。
  • 你可以使用promiseToFuture函数,但前提是read真的是async
  • 是的,它有效!感谢分享这个很棒的方法。当我根据我的确切需要修改了一些代码时,我会将其发布为答案。
【解决方案2】:

由于 Christopher Moore 给出了我的问题的通用解决方案,我将发布我使用的确切实现。

数据库结构 -

JS 代码 -

const dbRef = firebase.database().ref();

var userList = [];

async function read() {
  await dbRef.once("value", function (snapshot) {
    userList = [];
    snapshot.forEach(function (childSnapshot) {
      var key = childSnapshot.key;
      var user = childSnapshot.val();
      userList.push({ key, user });
    });
    //console.log(userList);
  });
  return userList;
}

JS-Dart 互操作 -

@JS()
library js_interop;

import 'package:js/js.dart';
import 'package:js/js_util.dart';

@JS()
external dynamic read();

class FBOps {
  final List<Map<String, dynamic>> toReturn = [];

  Map getUserDetails(objMap) {
    final Map<String, dynamic> temp = {};

    temp['name'] = objMap.user.name;
    temp['status'] = objMap.user.status;

    return temp;
  }

  Future<List> getList() async {
    dynamic obj = await promiseToFuture(read());

    for (dynamic eachObj in obj) {
      toReturn.add({
        'key': eachObj.key,
        'user': getUserDetails(eachObj),
      });
    }

    return toReturn;
  }
}

从互操作中获取Map -

List<Map> data;

Future<void> getAttendees() async {
  await FBOps().getList().then((value) => data = value);
  //print(data);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-28
    • 2020-06-13
    • 2021-02-14
    • 1970-01-01
    • 1970-01-01
    • 2020-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多