I'm building my first Flutter application and I've run into a bit of an async issue.
When my application executes I'd like it to ask for permissions and wait until they are granted. My main() function looks like this:
import 'permission_manager.dart' as Perm_Manager;
void main() async
{
//Ensure valid permissions
Perm_Manager.Permission_Manager pm = Perm_Manager.Permission_Manager();
var res = await pm.get_permissions();
print(res);
return runApp(MyApp());
}
The Permission Manager class' get_permissions() function uses the Flutter Simple Permissions package to check and ask for permissions.
import 'package:simple_permissions/simple_permissions.dart';
import 'dart:io' as IO;
import 'dart:async';
class Permission_Manager {
/* Get user permissions */
Future<bool> get_permissions() async
{
//Android handler
if (IO.Platform.isAndroid)
{
//Check for read permissions
SimplePermissions.checkPermission(Permission.ReadExternalStorage).then((result)
{
//If granted
if (result)
return true;
//Otherwise request them
else
{
SimplePermissions.requestPermission(Permission.ReadExternalStorage)
.then((result)
{
// Determine if they were granted
if (result == PermissionStatus.authorized)
return true;
else
IO.exit(0); //TODO - display a message
});
}
});
}
else
return true;
}
}
When I run the application it does not wait for the function to complete as intended and prints the value of "res" before the Future is updated.
Launching lib\main.dart on Android SDK built for x86 in debug mode...
Built build\app\outputs\apk\debug\app-debug.apk.
I/SimplePermission(15066): Checking permission : android.permission.READ_EXTERNAL_STORAGE
I/flutter (15066): null
I/SimplePermission(15066): Requesting permission : android.permission.READ_EXTERNAL_STORAGE
The Future returns a value midway through the function! Does anyone know what I'm doing wrong?
To await something you have to call the await keyword on a future instead of .then
final result = await future;
// do something
instead of
future.then((result) {
// do something
});
If you really want to use .then then you can await the generated future:
await future.then((result) {
// do something
});
Just ensure that when using nested asynchronous calls that the async keyword is used on each:
await future.then((result) async{
// do something
await future.then((result_2) {
// do something else
});
});
Got it working. The issue seems to be resolved using a Completer:
import 'package:simple_permissions/simple_permissions.dart';
import 'dart:io' as IO;
import 'dart:async';
class Permission_Manager {
/* Get user permissions */
final Completer c = new Completer();
Future get_permissions() async
{
//Android handler
if (IO.Platform.isAndroid)
{
//Check for read permissions
SimplePermissions.checkPermission(Permission.ReadExternalStorage).then((result)
{
//If granted
if (result)
{
c.complete(true);
}
//Otherwise request them
else
{
SimplePermissions.requestPermission(Permission.ReadExternalStorage).then((result)
{
// Determine if they were granted
if (result == PermissionStatus.authorized)
{
c.complete(true);
}
else
{
IO.exit(0); //TODO - display a message
}
});
}
});
}
else
{
c.complete(true);
}
return c.future;
}
}
Related
I implemented FlutterFire Messaging with the subscribeToTopic method, as the snippet below:
final _topics = ['topicName'];
Future<void> subscribeTopics() async {
for (final topic in _topics) {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
}
Future<void> unsubscribeTopics() async {
for (final topic in _topics) {
await FirebaseMessaging.instance.unsubscribeFromTopic(topic);
}
}
And everything is fine in debug mode, but in my Crashlytics I'm receiving some reports about it (iOS only):
[firebase_messaging/unknown] The request timed out
[firebase_messaging/unknown] An unknown error has occurred
[firebase_messaging/unknown] A data connection is not currently allowed
[firebase_messaging/unknown] The Internet connection appears to be offline
All the errors appears to be about internet connection, so my question is: "Should I validate the user connection before use FCM or the lib is ready to deal with it but only in Android?"
Crashlytics Stacktrace
Non-fatal Exception: FlutterError
0 ??? 0x0 MethodChannelFirebaseMessaging.subscribeToTopic + 368 (method_channel_messaging.dart:368)
1 ??? 0x0 Messaging.subscribeTopics + 40 (messaging.dart:40)
2 ??? 0x0 Messaging.observeMessaging + 22 (messaging.dart:22)
It's a general error so you shouldn't worry about it. When the internet connection restores the method will hopefully restart itself. You should also consider the case that It might generate an error while the app is in the background.
You can use try catch block to avoid these messages in your crashlytics dashboard.
final _topics = ['topicName'];
Future<void> subscribeTopics() async {
try {
for (final topic in _topics) {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
} on FirebaseException {
// do nothing
}
}
Future<void> unsubscribeTopics() async {
try {
for (final topic in _topics) {
await FirebaseMessaging.instance.unsubscribeFromTopic(topic);
}
} on FirebaseException {
// do nothing
}
}
Note It will also ignore all other error messages too. In that case
You can handle it otherwise
final _topics = ['topicName'];
Future<void> subscribeTopics() async {
try {
for (final topic in _topics) {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
} on FirebaseException catch (e) {
if (e.code == 'unknown') {
// do nothing
} else {
// do something
}
}
}
Future<void> unsubscribeTopics() async {
try {
for (final topic in _topics) {
await FirebaseMessaging.instance.unsubscribeFromTopic(topic);
}
} on FirebaseException catch (e) {
if (e.code == 'unknown') {
// do nothing
} else {
// do something
}
}
}
My method processData() is executing before pullAllData() is finished but I need processData() to wait until pullAllData() is completely finished before running. This is causing my isDownloadSuccessful bool to be Null when processData() is ran.
Future getCoinData() async {
calculateNumberOfDataPoints();
pullTimesAndPrices();
return timesAndPrices;
}
Future pullTimesAndPrices() async {
for (String cryptoCurrency in cryptoAbbreviation) {
pullAllData(cryptoCurrency);
processData(cryptoCurrency);
}
}
Future pullAllData(cryptoCurrency) async {
String historicalRequestURL =
'$cryptoAPIURL$cryptoCurrency$currency/ohlc?periods=$periodValue&apikey=$apiKey';
http.Response historicalResponse = await http.get(historicalRequestURL);
isPullSuccessful = (historicalResponse.statusCode == 200);
}
void processData(cryptoCurrency) {
if (isPullSuccessful) {
...
} else {
throw 'Problem pulling data';
}
}
You are marking your function pullTimesAndPrices as async but not using await. Use the await keyword before calling the pullAllData function.
I'm trying to understand Streams and wrote some code.
Everything seems to work, the program exits with status code 0. But it doesn't print the 'loop done' and 'main done' strings. I can't figure out why.
import 'dart:async';
Stream<int> countStream(int to) async* {
for (int i = 1; i <= to; i++) {
yield i;
}
}
class Retry {
StreamController<int> _outgoing;
Retry(Stream<int> incoming) {
_outgoing = StreamController<int>();
_outgoing.addStream(incoming);
}
Future<void> process() async {
await for (final i in _outgoing.stream) {
print("got $i");
}
print('loop done'); // Not printed
}
}
void main() async {
var stream = countStream(4);
var retry = Retry(stream);
await retry.process();
print('main done'); // Not printed
}
The _outgoing.stream is never closed, so code after the await for will never execute. The VM does notice that there also won't be any new events on that stream so nothing else will ever happen, and it can exit. You could fix the bug with:
_outgoing.addStream(incoming).whenComplete(() {
_outgoing.close();
});
I am trying to use dart isolate library to improve my application performance.
Look at following code:
import 'dart:isolate';
import 'package:dbcrypt/dbcrypt.dart';
main() {
var pwConPort = new ReceivePort();
pwConPort.listen((data) {
print(data);
pwConPort.close();
}, onError: (err) {
print(err);
});
Isolate.spawn(generatePasswordConcurrency, pwConPort.sendPort);
}
void generatePasswordConcurrency(SendPort sendPort) {
sendPort.send(_generateHashPassword('Passsowr1222!'));
}
String _generateHashPassword(String password) {
var regex = new RegExp(r'^.*(?=.{7,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).*$');
if (!regex.hasMatch(password)) {
throw new StateError('Errors');
}
return new DBCrypt().hashpw(password, new DBCrypt().gensalt());
}
Everything works fine but i can only pass a static password, or better to say, i don't know, how to pass something dynamically. Here you can see, password is hardcoded, but i want to pass a variable for example.
void generatePasswordConcurrency(SendPort sendPort) {
sendPort.send(_generateHashPassword('Passsowr1222!'));
}
If the method _generateHashPassword will throw an error, how can I handling this error? I try to catch the error on listen method from ReceivePort
pwConPort.listen((data) {
print(data);
pwConPort.close();
}, onError: (err) {
print(err);
});
but still got unhandling exceptions message.
Observatory listening on http://127.0.0.1:51433
in ShutdownIsolate: Unhandled exception:
Bad state: Errors
#0 _generateHashPassword (file:///D:/Dart/samples/bin/isolate_error.dart:26:9)
#1 generatePasswordConcurrency (file:///D:/Dart/samples/bin/isolate_error.dart:19:40)
#2 _startIsolate.isolateStartHandler (dart:isolate-patch/isolate_patch.dart:221)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:124)
Conclusion my question:
How can I pass a variable to called method on isolate?
How can I handling error on isolate?
First of all,
Isolate are not thread, they are independant process more like a fork() than a thread
dartApi: Isolate
Concurrent programming using isolates:
independent workers that are similar to threads but don't share memory, communicating only via
messages.
So, you can't access to the same variable than your parent process. It's a choice made by the dart team, because it's a mechanism usable when you compile your dart code in js. So it need to be possible in JS
How can I pass a variable to called method on isolate?
To do this, you need to see ReceivePort() like a unidirectionnal way of communication, so to pass variable in two way, you need two.
So on you main process:
pwConPort.listen((data) {
if (isolateSendPort == null && data is SendPort) {
isolateSendPort = data; // Receive the communication object of the isolate
isolateSendPort.send("Passsowr1222!");
} else {
print("Generated password: ${data}");
pwConPort.close();
}
}, onError: (err) {
print("SendPortError: ${err}");
});
});
In you isolate entry point :
sendPort.send(isolateConPort.sendPort);
isolateConPort.listen((data) {
// code ....
});
Note: be careful of what message you send. message send between one process and another need to respect some rules
DartApi: SendPort
The content of message can be: primitive values (null, num, bool,
double, String), instances of SendPort, and lists and maps whose
elements are any of these. List and maps are also allowed to be
cyclic.
How can I handling error on isolate?
Isolate get one method to listen throw error send by the isolate : addErrorListner
That is a useful function.
BUT ! this method is not implement in every plate-forme, so you need to do this in a others.
The way i chose is to send 2 SendPort in the entry point function :
One for the communication
One for the error.
So the spawn function looks like :
Isolate.spawn(generatePasswordConcurrency, [pwConPort.sendPort, errorPort.sendPort])
and the generatePasswordConcurrency :
void generatePasswordConcurrency(List<SendPort> commList) {
var sendPort = commList[0];
var errorPort = commList[1];
var isolateConPort = new ReceivePort();
sendPort.send(isolateConPort.sendPort);
isolateConPort.listen((data) {
try {
sendPort.send(_generateHashPassword(data));
} catch (e) {
errorPort.send("error: ${e.toString()}");
}
});
}
Here the full code :
import 'dart:isolate';
import 'package:dbcrypt/dbcrypt.dart';
main() {
var pwConPort = new ReceivePort();
var errorPort = new ReceivePort();
SendPort isolateSendPort = null;
Isolate.spawn(generatePasswordConcurrency, [pwConPort.sendPort, errorPort.sendPort])
.then((Isolate pcs) {
errorPort.listen((err) {
print("Error: ${err}");
pwConPort.close();
errorPort.close();
});
print(pcs);
pwConPort.listen((data) {
if (isolateSendPort == null && data is SendPort) {
isolateSendPort = data;
isolateSendPort.send("Passsowr1222!");
} else {
print("Generated password: ${data}");
pwConPort.close();
errorPort.close();
//pcs.kill();
}
}, onError: (err) {
print("SendPortError: ${err}");
});
});
}
void generatePasswordConcurrency(List<SendPort> commList) {
var sendPort = commList[0];
var errorPort = commList[1];
var isolateConPort = new ReceivePort();
sendPort.send(isolateConPort.sendPort);
isolateConPort.listen((data) {
try {
sendPort.send(_generateHashPassword(data));
} catch (e) {
errorPort.send("error: ${e.toString()}");
}
});
}
String _generateHashPassword(String password) {
var regex = new RegExp(r'^.*(?=.{7,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).*$');
if (!regex.hasMatch(password)) {
throw new StateError('Errors');
}
return new DBCrypt().hashpw(password, new DBCrypt().gensalt());
}
(I'm using the new lib v2 version of dart:io.)
I'd like to register multiple handlers for an HttpServer, specifically a WebSocketTransformer and other arbitrary handlers. Something like this:
// pseudo-code
var server = HttpServer;
server.register('/foo', someHandlerFunction); // 1
server.register('/bar', someOtherHandlerFunction); // 2
server.register('/ws', webSocketHandler); // 3
If #1 matches, then #2 isn't tested, and so on. So, it's greedy.
I've seen samples with just one handler. How do I register many handlers? Thanks in advance!
New answer: Use the route package: http://pub.dartlang.org/packages/route
Here's your example using route's serve() method:
HttpServer.bind('127.0.0.1', 8889).then((server) {
var router = new Router(server)
..serve('/ws').transform(new WebSocketTransformer()).listen(handleWebSocket)
..serve('/foo').listen((req) {
req.response..addString('foo')..close();
});
});
Router automatically catches unhandled requests and sends a 404, though soon you'll be able to override that with a defaultStream you can listen to.
Router also supports filters, useful for logging, auth, compression, etc.:
HttpServer.bind('127.0.0.1', 8889).then((server) {
var router = new Router(server)
..filter(new RegExp(r'/.*'), (req) {
//log all requests
_logger.info("request: $req");
return new Future.immediate(true); // keep processing request
})
..filter(new Regexp(r'/secure/.*'), (req) {
// check authentication asynchronously
return getUserFromRequest(req).then((user) {
if (user == null) {
sendRedirect('/login'); // sendRedirect coming soon
return false; // stop processing request
} else {
return true; // keep processing
}
});
})
..serve(/* ... */ );
});
Here's how the API docs recommend to register a WebSocket handler:
server
.where((request) => request.uri.path == "/ws")
.transform(new WebSocketTransformer()).listen((webSocket) => ...);
However, the server is a single-subscription stream. Once a listen is attached, you can't attach other listeners.
What I really want is for something to look at an event, decide if it can handle it, and if so then route it to another stream. Otherwise, pass it along. This way, the event (in this case an HttpRequest object) is passed along a chain until it's handled.
I built a TakeAndRoute class that extends StreamEventTransformer. The TakeAndRoute uses a function to determine if it should grab the event and route it to another stream, or simply forward it along.
Here's what I came up with:
import 'dart:io';
import 'dart:async';
handleWebSocket(WebSocket webSocket) {
webSocket.listen((event) {
if (event is MessageEvent) {
/* Handle message. */
} else if (event is CloseEvent) {
/* Handle closed. */
}
});
}
typedef bool ShouldTake(e);
typedef void RouteTo(Stream stream);
typedef void HandleEvent(e);
class TakeAndRoute<S, T> extends StreamEventTransformer<S, T> {
ShouldTake shouldTake;
RouteTo routeTo;
StreamController controller = new StreamController();
HandleEvent handler;
TakeAndRoute(this.shouldTake, {this.routeTo, this.handler}) {
if (routeTo != null) routeTo(controller.stream);
}
handleData(event, StreamSink sink) {
print("handling");
if (shouldTake(event)) {
if (routeTo != null) {
controller.add(event);
}
if (handler != null) {
handler(event);
}
} else {
sink.add(event);
}
}
}
main() {
HttpServer.bind('127.0.0.1', 8888)
.then((HttpServer server) {
server
.transform(new TakeAndRoute<HttpRequest, HttpRequest>(
(req) => req.uri.path == '/ws',
routeTo: (stream) => stream.transform(new WebSocketTransformer()).listen(handleWebSocket)))
.transform(new TakeAndRoute<HttpRequest, HttpRequest>(
(req) => req.uri.path == '/foo',
handler: (req) {
print('got foo');
req.response.addString("foo");
req.response.close();
}))
.listen((req) {
print("got 404 for ${req.uri}");
req.response.statusCode = 404;
req.response.close();
});
});
}
Admittedly, this might be overkill.
Here is a more manual, but shorter way to do it:
HttpServer.bind('127.0.0.1', 8889)
.then((HttpServer server) {
var sc = new StreamController();
sc.stream.transform(new WebSocketTransformer()).listen(handleWebSocket);
server.listen((HttpRequest request) {
print("new connection from ${request.uri.scheme} ${request.uri}");
// See https://code.google.com/p/dart/issues/detail?id=8825
//if (request.uri.scheme == 'ws') {
if (request.uri.path == '/ws') {
sc.add(request);
} else if (request.uri.path == '/foo') {
request.response.addString('foo');
request.response.close();
} else {
print("got 404 for ${request.uri}");
request.response.statusCode = 404;
request.response.close();
}
});
});
Notice how I had to create a StreamController so I could pump events to WebSocketTransformer