void main() {
test('simple', () async {
await fakeAsync((async) async {
final stream = Stream.value(42);
print('hi before await');
await stream.toList();
print('hi after await');
});
});
}
stucks forever in the toList line...
I have also tried to call whatever methods, but still stuck forever in the "await" line
test('simple', () async {
await fakeAsync((async) async {
final stream = Stream.value(42);
final future = stream.toList();
print('hi before await');
async.flushMicrotasks();
async.flushTimers();
async.elapse(const Duration(seconds: 10));
async.elapseBlocking(const Duration(seconds: 10));
await future;
print('hi after await');
});
});
I have looked at the implementation of Stream.toList as follows, but it seems quite normal
Future<List<T>> toList() {
List<T> result = <T>[];
_Future<List<T>> future = new _Future<List<T>>();
this.listen(
(T data) {
result.add(data);
},
onError: future._completeError,
onDone: () {
future._complete(result);
},
cancelOnError: true);
return future;
}
Future<bool> connectServer(BuildContext context) async {
try {
// await _displayTextInputDialog(context);
// socket = await Socket.connect(_textFieldController.text, 2508,
// timeout: const Duration(seconds: 4));
socket = await Socket.connect("192.168.14.148", 2508,
timeout: const Duration(seconds: 4));
stream = socket.listen(null);
return true;
} on SocketException {
await Navigator.pushNamed(
context,
'/connectionE',
);
return false;
}
}
Future<dynamic> receiveMessage() {
final completer = Completer();
print("before");
stream.onData((data) {
print(data);
if (data.contains(49)) {
print(data);
completer.complete(extractData(data));
stream.cancel();
}
});
print('after');
return completer.future;
}
Future<String> sendAndWait(
BuildContext context, String message, int code) async {
try {
sendMessage(message, code);
print("sended");
String reply = await receiveMessage();
stream.onData((data) {});
print(reply);
return "";
} on SocketException {
Navigator.pushReplacementNamed(
context,
'/serverDownE',
);
}
return "2";
}
receiveMessage work on the first time, but then stop working, it doesn't even get into the onData function. Does someone know why is this happening? I cant use cancel on the subscription, cancel and start new one because when I start listening again I get an error Bad state stream has already been listening. I can't close the socket because the the server is a stateful server.
I'm using workmanager to retrieve user's location in background every 15 minutes. When the location fetch fails, I receive a notification with the error as you can see in picture. I would like to know how can I prevent the notification to show up in failure cases.
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
if (taskName == FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME) {
// TODO: Find a better way to get user position, maybe with ServiceLocator or even better with BLoC
final dataSource = GeolocatorDataSource();
final remoteDataSource = FirestoreRemoteDataSource(
firebaseFirestore: FirebaseFirestore.instance,
);
final repository = GeolocationRepository(
geolocationDataSource: dataSource,
remoteDataSource: remoteDataSource,
);
final positionEither = await repository.getUserPosition();
positionEither.fold((failure) async {
print('failure: $failure');
}, (position) async {
print('position = $position');
final storePositionEither =
await repository.storeUserPosition(position, inputData['uid']);
storePositionEither.fold((failure) async {
print('failure: $failure');
}, (isStored) async {
print("Position has been successfully stored in background!");
});
});
}
return Future.value(true);
});
}
void _initializeWorkManagerWhenAuthenticated(String userId) {
bool isProduction = bool.fromEnvironment('dart.vm.product');
Workmanager.initialize(
callbackDispatcher,
isInDebugMode: !isProduction,
);
Workmanager.registerPeriodicTask(
FETCH_USER_POSITION_IN_BACKGROUND_TASK_ID,
FETCH_USER_POSITION_IN_BACKGROUND_TASK_NAME,
frequency: Duration(minutes: 15),
existingWorkPolicy: ExistingWorkPolicy.keep,
inputData: {
'userId': userId,
},
);
}
Have you checked if the notifications are appearing when you run with isInDebugMode: false?
See: https://github.com/fluttercommunity/flutter_workmanager/blob/ea274c33b60ef1a4e29bdd392a477f67466dc25d/lib/src/workmanager.dart#L90
I create a service to get config from Firebase remote config:
const String _ShowDataBanner = "show_data_banner";
const String _ShowMainBanner = "show_main_banner";
const String _ShowMainColorBanner = "show_main_color_banner";
class RemoteConfigService {
final RemoteConfig _remoteConfig;
final defaults = <String, dynamic>{
_ShowMainBanner: false,
_ShowMainColorBanner: "0xffcccccc"
};
RemoteConfigService({RemoteConfig remoteConfig})
: _remoteConfig = remoteConfig;
static RemoteConfigService _instance;
static Future<RemoteConfigService> getInstance() async {
if (_instance == null) {
_instance = RemoteConfigService(
remoteConfig: await RemoteConfig.instance,
);
}
return _instance;
}
String get showMainBanner => _remoteConfig.getString(_ShowDataBanner);
Future initialise() async {
try {
await _remoteConfig.setDefaults(defaults);
await _fetchAndActivate();
} on FetchThrottledException catch (e) {
print("Remote config fetch throttled: $e");
} catch (e) {
print(
"unable to fetch remote config. Catched or default values will be used");
}
}
Future _fetchAndActivate() async {
// await _remoteConfig.fetch();
await _remoteConfig.fetch(expiration: Duration(seconds: 0));
_remoteConfig
.activateFetched()
.then((value) => print("---------> ${value.toString()}"));
}
}
When i change config from Firebase console I have to stop/start app to updated my config.It is possible to received new config in client from remote immediately when i changed config from console?
Firebase remote config is not meant to be used like this. Its not a real-time thing. But if you want to update immediately after updating config from firebase console, then you can do that by providing expiration parameter a duration of 0 seconds.
await remoteConfig.fetch(expiration: const Duration(seconds: 0));
It will fetch the latest values every time but this is not recommended as you can also get FetchThrottledException.
Future<RemoteConfig> setupRemoteConfig() async {
final RemoteConfig remoteConfig = RemoteConfig.instance;
await remoteConfig.ensureInitialized();
await remoteConfig.fetchAndActivate();
var hello = remoteConfig.getString("hello");
print("hello: |${hello}|");
return remoteConfig;
}
await remoteConfig.fetchAndActivate(); is very important.
This method submits a simple HTTP request and calls a success or error callback just fine:
void _getSimpleReply( String command, callback, errorCallback ) async {
try {
HttpClientRequest request = await _myClient.get( _serverIPAddress, _serverPort, '/' );
HttpClientResponse response = await request.close();
response.transform( utf8.decoder ).listen( (onData) { callback( onData ); } );
} on SocketException catch( e ) {
errorCallback( e.toString() );
}
}
If the server isn't running, the Android-app more or less instantly calls the errorCallback.
On iOS, the errorCallback takes a very long period of time - more than 20 seconds - until any callback gets called.
May I set for HttpClient() a maximum number of seconds to wait for the server side to return a reply - if any?
There are two different ways to configure this behavior in Dart
Set a per request timeout
You can set a timeout on any Future using the Future.timeout method. This will short-circuit after the given duration has elapsed by throwing a TimeoutException.
try {
final request = await client.get(...);
final response = await request.close()
.timeout(const Duration(seconds: 2));
// rest of the code
...
} on TimeoutException catch (_) {
// A timeout occurred.
} on SocketException catch (_) {
// Other exception
}
Set a timeout on HttpClient
You can also set a timeout on the HttpClient itself using HttpClient.connectionTimeout. This will apply to all requests made by the same client, after the timeout was set. When a request exceeds this timeout, a SocketException is thrown.
final client = new HttpClient();
client.connectionTimeout = const Duration(seconds: 5);
You can use timeout
http.get(Uri.parse('url')).timeout(
const Duration(seconds: 1),
onTimeout: () {
// Time has run out, do what you wanted to do.
return http.Response('Error', 408); // Request Timeout response status code
},
);
The HttpClient.connectionTimeout didn't work for me. However, I knew that the Dio packet allows request cancellation. Then, I dig into the packet to find out how they achieve it and I adapted it to me. What I did was to create two futures:
A Future.delayed where I set the duration of the timeout.
The HTTP request.
Then, I passed the two futures to a Future.any which returns the result of the first future to complete and the results of all the other futures are discarded. Therefore, if the timeout future completes first, your connection times out and no response will arrive. You can check it out in the following code:
Future<Response> get(
String url, {
Duration timeout = Duration(seconds: 30),
}) async {
final request = Request('GET', Uri.parse(url))..followRedirects = false;
headers.forEach((key, value) {
request.headers[key] = value;
});
final Completer _completer = Completer();
/// Fake timeout by forcing the request future to complete if the duration
/// ends before the response arrives.
Future.delayed(timeout, () => _completer.complete());
final response = await Response.fromStream(await listenCancelForAsyncTask(
_completer,
Future(() {
return _getClient().send(request);
}),
));
}
Future<T> listenCancelForAsyncTask<T>(
Completer completer,
Future<T> future,
) {
/// Returns the first future of the futures list to complete. Therefore,
/// if the first future is the timeout, the http response will not arrive
/// and it is possible to handle the timeout.
return Future.any([
if (completer != null) completeFuture(completer),
future,
]);
}
Future<T> completeFuture<T>(Completer completer) async {
await completer.future;
throw TimeoutException('TimeoutError');
}
This is an example of how to extend the http.BaseClient class to support timeout and ignore the exception of the S.O. if the client's timeout is reached first.
you just need to override the "send" method...
the timeout should be passed as a parameter to the class constructor.
import 'dart:async';
import 'package:http/http.dart' as http;
// as dart does not support tuples i create an Either class
class _Either<L, R> {
final L? left;
final R? right;
_Either(this.left, this.right);
_Either.Left(L this.left) : right = null;
_Either.Right(R this.right) : left = null;
}
class TimeoutClient extends http.BaseClient {
final http.Client _httpClient;
final Duration timeout;
TimeoutClient(
{http.Client? httpClient, this.timeout = const Duration(seconds: 30)})
: _httpClient = httpClient ?? http.Client();
Future<http.StreamedResponse> send(http.BaseRequest request) async {
// wait for result between two Futures (the one that is reached first) in silent mode (no throw exception)
_Either<http.StreamedResponse, Exception> result = await Future.any([
Future.delayed(
timeout,
() => _Either.Right(
TimeoutException(
'Client connection timeout after ${timeout.inMilliseconds} ms.'),
)),
Future(() async {
try {
return _Either.Left(await _httpClient.send(request));
} on Exception catch (e) {
return _Either.Right(e);
}
})
]);
// this code is reached only for first Future response,
// the second Future is ignorated and does not reach this point
if (result.right != null) {
throw result.right!;
}
return result.left!;
}
}
Their is onError option which works fine if their is any exception like no internet.It has to return response(my case in below code) or null.
In response their are 2 options Body and Status code.
var response = await http.post(url, body: body, headers: _headers).onError(
(error, stackTrace) => http.Response(
jsonEncode({
'message':no internet please connect to internet first
}),
408));
You can also choose to override the settings for a HttpClient:
class DevHttpOverrides extends HttpOverrides {
#override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..connectionTimeout = Duration(seconds: 2);
}
}