I create a simple stream, add a few elements and listen to the stream. But there is a problem when I use the map on the stream. For simplicity I just map the value to the same value (I guess?).
When I try to run the program I get an map-error:
Uncaught Error: TypeError: Instance of '_MapStream<String, dynamic>': type '_MapStream<String, dynamic>' is not a subtype of type 'Stream<String>'
import 'dart:async';
void main() async {
StreamController controller = StreamController<String>.broadcast();
final StreamTransformer transformer = StreamTransformer<String, String>.fromHandlers(
handleData: (data, EventSink sink) {
sink.add(data);
}
);
Stream stream = controller.stream;
stream
.map((value) => value) // <-- Problem in this line
.transform(transformer)
.listen(
(data) {
print('listen: $data');
},
onError: (err) => print(err));
controller.add('foo');
controller.add('baa');
controller.close();
}
Your problem is you are forcing non-generic types from generic types. If you write the code like this where you are not use explicit typing:
import 'dart:async';
void main() async {
final controller = StreamController<String>.broadcast(); // <-- fixed here
final StreamTransformer transformer =
StreamTransformer<String, String>.fromHandlers(
handleData: (data, EventSink sink) {
sink.add(data);
});
final stream = controller.stream; // <-- fixed here
stream
.map((value) => value) // <-- Problem in this line
.transform(transformer)
.listen((data) {
print('listen: $data');
}, onError: (err) => print(err));
controller.add('foo');
controller.add('baa');
controller.close();
}
Dart will then automatically determine that the type is StreamController<String> and Stream<String> and not StreamController and Stream which you are enforcing.
When you are removing the generic part of the type you are also removing Dart's ability to guess the type for all methods you are calling.
If using explicit typing you should write:
import 'dart:async';
void main() async {
StreamController<String> controller = StreamController<String>.broadcast();
final StreamTransformer transformer =
StreamTransformer<String, String>.fromHandlers(
handleData: (data, EventSink sink) {
sink.add(data);
});
Stream<String> stream = controller.stream;
stream
.map((value) => value) // <-- Problem in this line
.transform(transformer)
.listen((data) {
print('listen: $data');
}, onError: (err) => print(err));
controller.add('foo');
controller.add('baa');
controller.close();
}
Related
I have an app feature where the user picks images from his phone and then uploads them to Firebase Storage.
I thought that the upload process should be done in a separate isolate.
I keep getting an exception which I think is related to the Multi Image Picker package.
The exception is:
E/flutter (12961): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)]
Unhandled Exception: Exception: NoSuchMethodError: The getter
'defaultBinaryMessenger' was called on null.
When the user presses on the upload button, this method is called:
Future<void> _initIsolate() async {
ReceivePort receivePort = ReceivePort();
receivePort.listen(
(message) {
print(message.toString());
},
onDone: () => print('Done'),
onError: (error) => print('$error'),
);
await compute(
_function, // This function is called in the separate isolate
{
'sendingPort': receivePort.sendPort,
'images': images,
},
);
}
The _function method is as follows:
static void _function(Map<String, dynamic> parameterMap) async {
SendPort sendingPort = parameterMap['sendingPort'];
List<Asset> images = parameterMap['images'];
List<String> urls = [];
int index = 0;
images.forEach(
(image) async {
String url = await getDownloadUrl(image); // a helper method
urls.add(url);
sendingPort.send('Image number: $index uploaded');
index += 1;
},
);
final CollectionReference collectionRef = FirebaseFirestore.instance.collection('offers');
final user = CurrentUser.getCurrentUser();
await collectionRef.doc(user.uid).set(
{
'time': FieldValue.serverTimestamp(),
'urls': urls,
},
);
}
The helper method _getDownloadUrl is as follows:
Future<String> getDownloadUrl(Asset image) async {
String rannum = Uuid().v1();
final ByteData byteData = await image.getByteData(); // --> This produces a defaultBinaryMessenger
final List<int> imageData = byteData.buffer.asUint8List();
Reference reference = FirebaseStorage.instance.ref().child("offers/$rannum");
UploadTask uploadTask = reference.putData(imageData);
TaskSnapshot downloadUrl = await uploadTask.whenComplete(() => null);
Future<String> futureUrl = downloadUrl.ref.getDownloadURL();
return futureUrl;
}
The getByteData method is part of the multi_image_picker package.
The source code is:
Future<ByteData> getByteData({int quality = 100}) async {
if (quality < 0 || quality > 100) {
throw new ArgumentError.value(
quality, 'quality should be in range 0-100');
}
Completer completer = new Completer<ByteData>();
ServicesBinding.instance.defaultBinaryMessenger // --> Exception here. ServicesBinding.instance is null
.setMessageHandler(_originalChannel, (ByteData message) async {
completer.complete(message);
ServicesBinding.instance.defaultBinaryMessenger
.setMessageHandler(_originalChannel, null);
return message;
});
await MultiImagePicker.requestOriginal(_identifier, quality);
return completer.future;
}
Why is the ServicesBinding.instance null?
Since this method is working fine without using Isolates, does this have something to do with the isolates?
So im trying to stream data from firestore but when printing the data I get:
I/flutter ( 8356): Closure: () => Map<String, dynamic> from Function 'data':.
I am using this code to fetch the data:
void messagesStream() async {
Stream collectionStream = _firestore.collection('messages').snapshots();
await for (var snapshot in collectionStream) {
for (var message in snapshot.docs) {
print(message.data());
}
}
When new data is added to the messages collection I get the Closure message so it is interacting with the databse.
What I want is it to print out the contents of the new document within the collection.
Any help is appreciated.
That's not the way you're supposed to iterate the results of a Stream. If you have a Stream and you want to process its results, you're supposed to use listen() to receive the results asynchronously.
Stream collectionStream = _firestore.collection('messages').snapshots();
collectionStream.listen((QuerySnapshot querySnapshot) {
querySnapshot.documents.forEach((document) => print(document.data()));
}
See also: Firestore collection query as stream in flutter
You might also want to review the documentation to learn how to query Firestore in Flutter.
void getMessages() async {
final messages= await _firestore.collection('messages').get();
for(var message in messages.docs){
print(message.data());
}
this is working check this and call getMessages() wherever you wana call
I encountered the same issue with pretty much your exact same code (sans your Stream variable). My suggestion is to delete the Stream var altogether (I tested the code below and got it to print the data from the Firestore database) :
void messagesStream() async {
await for (var snapshot in _firestore.collection('messages').snapshots()) {
for (var message in snapshot.docs) {
print(message.data());
}
}
}
Alternatively, try addding QuerySnapShot as the data type for your Stream variable (untested):
Stream<QuerySnapshot> collectionStream = _firestore.collection('messages').snapshots();
You could also replace the entire method by creating a new Stateless Widget (MessagesStream) that returns a StreamBuilder:
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').snapshots(),
builder: (context, snapshot) {
final messages = snapshot.data.docs;
for (var message in messages) {
print(message.data());
}
...and call it wherever you see fit while you test:
class _ChatScreenState extends State<ChatScreen> { (...)
body: Column(children: <Widget> [
//Just an example.
MessageStream(),
],
),
(...)
}
***Be sure you make the _fireStore (which should be a FirebaseFirestore.instance) a global variable if you're going with the Stateless Widget route.
I received this error while trying to throw a custom error class InkError:
You meed add toMap the class.
final response = await http.post(url, body: body, headers: headers);
final json = jsonDecode(response.body);
if (response.statusCode == HttpStatus.ok) {
return json;
} else {
throw InkError(
code: 0,
message: json['message'],
statusCode: response.statusCode,
).toMap();
InkError
class InkError {
/// Error code.
final int code;
/// Error message.
final String message;
/// HTTP Status Code
final int? statusCode;
const InkError({
required this.code,
required this.message,
this.statusCode,
});
factory InkError.fromJSON(Map<String, dynamic> json) => InkError(
code: json['code'] as int,
message: json['message'] as String,
statusCode: json['statusCode'],
);
Map<String, dynamic> toMap() {
return {
'code': code,
'message': message,
'statusCode': statusCode,
};
}
#override
String toString() {
return toMap().toString();
}
}
I have a class called PostController, and I trying to test the following function create:
class PostController {
constructor(Post) {
this.Post = Post;
}
async create(req, res) {
try {
this.validFieldRequireds(req);
const post = new this.Post(req.body);
post.user = req.user;
...some validations here
await post.save();
return res.status(201).send(message.success.default);
} catch (err) {
console.error(err.message);
const msg = err.name === 'AppError' ? err.message :
message.error.default;
return res.status(422).send(msg);
}
}
My test class is:
import sinon from 'sinon';
import PostController from '../../../src/controllers/posts';
import Post from '../../../src/models/post';
describe('Controller: Post', async () => {
it.only('should call send with sucess message', () => {
const request = {
user: '56cb91bdc3464f14678934ca',
body: {
type: 'Venda',
tradeFiatMinValue: '1',
... some more attributes here
},
};
const response = {
send: sinon.spy(),
status: sinon.stub(),
};
response.status.withArgs(201).returns(response);
sinon.stub(Post.prototype, 'save');
const postController = new PostController(Post);
return postController.create(request, response).then(() => {
sinon.assert.calledWith(response.send);
});
});
});
But I'm getting the following error:
Error: Timeout of 5000ms exceeded. For async tests and hooks, ensure
"done()"
is called; if returning a Promise, ensure it resolves.
(D:\projeto\mestrado\localbonnum-back-end\test\unit\controllers\post_spec.js)
Why?
Most probably it's because misuse of sinon.stub.
You've
sinon.stub(Post.prototype, 'save');
without telling what this stub will do, so in principle this stub will do nothing (meaning it returns undefined).
IDK, why you don't see other like attempt to await on stub.
Nevertheless, you should properly configuture 'save' stub - for example like this:
const saveStub = sinon.stub(Post.prototype, 'save');
saveStub.resolves({foo: "bar"});
My nodejs app calls nexmo API to send SMS message. Here is the API:
nexmo.message.sendSms(sender, recipient, message, options, callback);
In the app, it is
const nexmo = new Nexmo({
apiKey: "nexmoApiKey",
apiSecret: "nexmoSecret"
}, { debug: true });
nexmo.message.sendSms(nexmo_sender_number, cell_country + to, message, {type: 'unicode'}, async (err, result) => {....});
Is there a way I can convert it to async/await structure like below:
const {err, result} = nexmo.message.sendSms(nexmo_sender_number, cell_country + to, vcode, {type: 'unicode'});
if (err) {.....};
//then process result...
I would like to return the message to parent function after the message was successfully sent out.
The nexmo-node library only supports callbacks for now. You'll need to use something like promisify or bluebird to convert the sendSms function to promises, and the use async/await with it. Here is an example using Node's promisify
const util = require('util');
const Nexmo = require('nexmo');
const nexmo = new Nexmo({
apiKey: "nexmoApiKey",
apiSecret: "nexmoSecret"
}, { debug: true });
const sendSms = util.promisify(nexmo.message.sendSms);
async function sendingSms() {
const {err, result} = await sendSms(nexmo_sender_number, cell_country + to, message, {type: 'unicode'});
if (err) {...} else {
// do something with result
}
}
Though Alex's solution is elegant. It breaks TypeScript and util does some 'hidden logic' on the promises; when it errors stack traces are not clear.
This also allows you to stay true to the API and get auto-fill on the properties.
So instead you can do this (TypeScript)
/**
* Return the verification id needed.
*/
export const sendSMSCode = async (phoneNumber: string): Promise<string> => {
const result = await new Promise(async (resolve: (value: RequestResponse) => void, reject: (value: VerifyError) => void) => {
await nexmo.verify.request({
number: phoneNumber,
brand: `${Config.productName}`,
code_length: 4
}, (err, result) => {
console.log(err ? err : result)
if (err) {
reject(err)
}
resolve(result)
});
})
return result.request_id
}
I would like to call an asynchronous function outside the lambda handler with by the following code:
var client;
(async () => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
});
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
The 'client' does not get initialized. The same code works perfectly if executed within the handler.
initSecrets contains a lambda invocation of getSecrets() which calls the AWS SecretsManager
Has anyone an idea how asynchronous functions can be properly called for initialization purpose outside the handler?
Thank you very much for your support.
I ran into a similar issue trying to get next-js to work with aws-serverless-express.
I fixed it by doing the below (using typescript so just ignore the :any type bits)
const appModule = require('./App');
let server: any = undefined;
appModule.then((expressApp: any) => {
server = createServer(expressApp, null, binaryMimeTypes);
});
function waitForServer(event: any, context: any){
setImmediate(() => {
if(!server){
waitForServer(event, context);
}else{
proxy(server, event, context);
}
});
}
exports.handler = (event: any, context: any) => {
if(server){
proxy(server, event, context);
}else{
waitForServer(event, context);
}
}
So for your code maybe something like
var client = undefined;
initSecrets("MyWebApi").then(result => {
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET)
})
function waitForClient(){
setImmediate(() => {
if(!client ){
waitForClient();
}else{
client('Request')
}
});
}
exports.handler = async function (event, context) {
if(client){
client('Request')
}else{
waitForClient(event, context);
}
};
client is being called before it has initialised; the client var is being "exported" (and called) before the async function would have completed. When you are calling await client() the client would still be undefined.
edit, try something like this
var client = async which => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
let api = new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
return api(which) // assuming api class is returning a promise
}
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
This can be also be solved with async/await give Node v8+
You can load your configuration in a module like so...
const fetch = require('node-fetch');
module.exports = async () => {
const config = await fetch('https://cdn.jsdelivr.net/gh/GEOLYTIX/public/z2.json');
return await config.json();
}
Then declare a _config outside the handler by require / executing the config module. Your handler must be an async function. _config will be a promise at first which you must await to resolve into the configuration object.
const _config = require('./config')();
module.exports = async (req, res) => {
const config = await _config;
res.send(config);
}
Ideally you want your initialization code to run during the initialization phase and not the invocation phase of the lambda to minimize cold start times. Synchronous code at module level runs at initialization time and AWS recently added top level await support in node14 and newer lambdas: https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/ . Using this you can make the init phase wait for your async initialization code by using top level await like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
console.log("start init");
await sleep(1000);
console.log("end init");
export const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
This works great if you are using ES modules. If for some reason you are stuck using commonjs (e.g. because your tooling like jest or ts-node doesn't yet fully support ES modules) then you can make your commonjs module look like an es module by making it export a Promise that waits on your initialization rather than exporting an object. Like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const main = async () => {
console.log("start init");
await sleep(1000);
console.log("end init");
const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
return { handler };
};
# note we aren't exporting main here, but rather the result
# of calling main() which is a promise resolving to {handler}:
module.exports = main();