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 using dart: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
and AddAttendee.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 the main.dart
and if any error happens and an exception is thrown then it is displayed in form of transcompiled main.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 to main.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/
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 using dart: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.
Callback is basically a function or a method that we pass as an argument into another function or a method to perform an action. In the simplest words, we can say that Callback or VoidCallback are used while sending data from one method to another and vice-versa. It is very important to maintain a continuous flow of data throughout the flutter app.
In Flutter, method channels are two-way communication tools and their APIs are uniform in all platforms. By storing a reference to a channel in any language, we can call methods with invokeMethod () and accept calls with setMethodCallHandler ().
A Guide for Flutter beginners. What is the simplest method by which we can pass and receive data in Flutter? Certainly, through the widget class constructors we can easily pass and receive data. However, we also need to understand another concept named route, which involves another Flutter widget, such as Navigator.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With