How to receive data back to flutter when calling a method using js.context.callMethod()?
When calling callMethod
it returns exactly what the javascript function returns. It doesn't return a json encoded String
like your code currently implies. Therefore there is no need to decode it. var obj
can nearly be treated as a List<Map<String, dynamic>>
object. This means you can access say the first object of the list and the key
property with js.context.callMethod("read")[0]['key']
in your dart code. You can remove all the json decoding and casting in your code and replace it with a function that uses this method of accessing the returned data to convert it to an actual List<Map<String, dynamic>>
yourself.
Example getAttendees
modification:
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; });}
If you for some reason find the need to use json, you would need to encode on the javascript side, though I don't see a reason to do this as it just adds complexity.
When displaying the data in the build
function data.toString()[index]
will first convert the javascript output to a String
like this: "[{key: key}, {key: key}, ...]"
and then find the index
of the String
, not the List
object, which is likely what you intend. For this you use the index first, data[index]
and then add the field you're looking for data[index]['key']
as data is a List
of Map
s.
Unrelated directly to the problem you're having, but some general flutter advice, is to use a FutureBuilder
instead of your current method of showing data when the async
function completes. Your method nearly does what a FutureBuilder
would, just a bit less efficiently and in a less readable way.
EDIT:To handle read
as a Promise
:
First you have to add js: ^0.6.2
to your pubspec.yaml
dependencies.
Then this must be added somewhere in your code:
@JS()library script.js;import 'package:js/js.dart';import 'dart:js_util';@JS()external dynamic read();
js.JsArray obj = js.context.callMethod("read");
Should be modified to just be await promiseToFuture(read())
. The whole getAttendees
should be modified to:
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; });}
Since Christopher Moore gave a generalized solution to my problem, I will post an exact implementation I used.
Database Structure -
JS Code -
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 Interop -
@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; }}
Getting the Map
from interop -
List<Map> data;Future<void> getAttendees() async { await FBOps().getList().then((value) => data = value); //print(data);}