How to receive data back to flutter when calling a method using js.context.callMethod()?
Solution 1
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;
});
}
Solution 2
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);
}
Abhishek
Updated on December 22, 2022Comments
-
Abhishek over 1 year
I am building a chrome extension using Flutter Web, which is just a simple
ListView
on the home screen and basic CRUD operations. I am usingdart:js
package to call methods from a JS file which performs some operations on the Firebase Realtime Database.Adding a new entry to the database is working through the
add()
method call. Read operation is also working in the JS file just fine.My main question is how I am supposed to read the database info as JSON, parse it and display it as
ListView
in Flutter.
Here goes
main.dart
andAddAttendee.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); } }, ), ); } }
Here is the JS code -
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"); }); }
Database structure in Firebase RT DB -
Database structure when parsed -
It's just so frustrating debugging this code because I can't
print
the outputs of themain.dart
and if any error happens and an exception is thrown then it is displayed in form of transcompiledmain.dart.js
file which is impossible to read.I haven't been able to get the data back from the
read()
method in the JS file tomain.dart
. How to do that?Some links for reference -
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/