I'm having trouble successfully using shareReplay from RxDart (0.24.1) in my application. I have a Stream and I would like to 'cache' the latest emitted value, so that any 'late' listeners get it immediately (the stream can be idle for longer periods). I'm failing to do this, so I started experimenting outside of my app and must admit I don't fully understand what is happening.
Here is some code:
import 'package:rxdart/rxdart.dart';
void main() async {
final s = Stream.fromIterable([1, 2, 3]).shareReplay(maxSize: 10);
print('First:');
await for (final i in s) {
print(i);
}
print('Second:');
await for (final i in s) {
print(i);
}
}
I would expect the code to print
First
1
2
3
Second
1
2
3
but it never gets to the second await for, it doesn't even print the string Second:; however, the program finishes successfully, so the first await for does finish (as the original Stream is finite). What is going on?
In reality, the Stream that I have is potentially endless with long periods of time between events), and I'm only interested in the last event, so here is some code simulating this:
import 'package:rxdart/rxdart.dart';
void main() async {
final s = Stream.periodic(Duration(seconds: 1), (e) => e).shareReplay(maxSize: 1);
// print(s.isBroadcast);
await Future.delayed(Duration(seconds: 3));
print('First');
await for (final i in s.take(1)) {
print(i);
}
print('Second');
await for (final i in s.take(1)) {
print(i);
}
}
To make sure the Stream can finish (and so await for can finish) I use take(1). I expected the output to be:
First
2
Second
2
(Maybe the twos would be threes?)
What I expected to happen is this:
The periodic stream starts emitting every second (it is broadcast/hot).
The application waits for 3 seconds due to Future.delayed.
The first late listener comes and gets the latest value, 2 (or maybe 3?) and finishes due to take(1).
The second late listener comes and gets the latest value as well because the stream didn't emit anything else in the meantime (even if it does, the value would be greater by 1, which is Ok) and finishes due to take(1).
The app finishes.
However, the output is:
First
0
Second
Unhandled exception:
type '_TakeStream<int>' is not a subtype of type 'Future<bool>'
#0 _StreamIterator._onData (dart:async/stream_impl.dart:1067:19)
#1 _RootZone.runUnaryGuarded (dart:async/zone.dart:1384:10)
#2 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
#3 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:285:7)
#4 _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:127:11)
#5 _TakeStream._handleData (dart:async/stream_pipe.dart:318:12)
#6 _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:157:13)
#7 _RootZone.runUnaryGuarded (dart:async/zone.dart:1384:10)
#8 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
#9 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:285:7)
#10 _SyncBroadcastStreamController._sendData (dart:async/broadcast_stream_controller.dart:385:25)
#11 _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:250:5)
#12 _StartWithStreamSink._safeAddFirstEvent (package:rxdart/src/transformers/start_with.dart:56:12)
#13 _StartWithStreamSink.onListen (package:rxdart/src/transformers/start_with.dart:37:11)
#14 forwardStream.<anonymous closure>.<anonymous closure> (package:rxdart/src/utils/forwarding_stream.dart:31:37)
#15 forwardStream.runCatching (package:rxdart/src/utils/forwarding_stream.dart:24:12)
#16 forwardStream.<anonymous closure> (package:rxdart/src/utils/forwarding_stream.dart:31:16)
#17 _runGuarded (dart:async/stream_controller.dart:847:24)
#18 _BroadcastStreamController._subscribe (dart:async/broadcast_stream_controller.dart:213:7)
#19 _ControllerStream._createSubscription (dart:async/stream_controller.dart:860:19)
#20 _StreamImpl.listen (dart:async/stream_impl.dart:493:9)
#21 DeferStream.listen (package:rxdart/src/streams/defer.dart:37:18)
#22 StreamView.listen (dart:async/stream.dart:1871:20)
#23 new _ForwardingStreamSubscription (dart:async/stream_pipe.dart:118:10)
#24 new _StateStreamSubscription (dart:async/stream_pipe.dart:341:9)
#25 _TakeStream._createSubscription (dart:async/stream_pipe.dart:310:16)
#26 _ForwardingStream.listen (dart:async/stream_pipe.dart:83:12)
#27 _StreamIterator._initializeOrDone (dart:async/stream_impl.dart:1041:30)
#28 _StreamIterator.moveNext (dart:async/stream_impl.dart:1028:12)
#29 main (package:shopping_list/main.dart)
<asynchronous suspension>
#30 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#31 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
The first await for with take(1) to actually make it finish works as I would expect it to, with the exception that it gets the the initial value, which means the stream didn't stat emitting anything before the listener came (is not 'hot'). The second fails with an exception. What am I missing?
EDIT: my understanding of shareReplay may have been wrong all along, I though it is hot because of https://pub.dev/documentation/rxdart/latest/rx/ReplaySubject-class.html says it is hot.
I changed the code a bit:
import 'package:rxdart/rxdart.dart';
void main() async {
final s = BehaviorSubject();
s.addStream(Stream.periodic(Duration(seconds: 1), (e) => e));
// print(s.isBroadcast);
await Future.delayed(Duration(seconds: 3));
print('First');
await for (final i in s.take(1)) {
print(i);
}
print('Second');
await for (final i in s.take(1)) {
print(i);
}
}
and now the output is:
First
2
Second
2
BUT the program never finishes...
Without take(1) the output never gets to the second listener, so it does have some effect. I don't get it.
EDIT 2: I think I might know why the first example with shareReplay doesn't work, here is an excerpt from the docs:
It will automatically begin emitting items when first listened to, and shut down when no listeners remain.
In my case, the first listener/await for subscribes, and the stream starts, and when the loop is done the last (and only) listener is finished, so the stream returned from shareReplay shuts down. Still doesn't explain why the line print('Second'); doesn't execute, though.
The reason your final program doesn't terminate is that your broadcast stream is still emitting events every second.
Nobody's listening, but it's a broadcast stream so it doesn't care. It has a timer ticking every second, and that timer keeps the program alive, even if nobody is ever going to listen again.
You can keep your program alive by doing Timer.periodic(Duration(seconds: 1), (_) {}); as well. As long as the timer is live, something might happen.
The crash is interesting, though. It suggests a problem with the internal logic of the StreamIterator class. I'll look into that.
Can't say anything guaranteed useful about shareReplay, I'm not familiar with RxDart.
My guess is that the first program gets into a problem because the replayShared stream doesn't terminate properly - that is, doesn't send a done event when it's done. That would explain the program terminating early. The first await for is waiting for the next event, so it doesn't end and go to the next loop, but there are no live timers or scheduled events of any kind left, so the isolate just shuts down.
A quick read of BeheaviorSubject suggests that it works like a stream controller, so you probably need to close it after the addStream has completed. Maybe:
s.addStream(...).whenComplete(s.close);
Related
So I've read the docs and this example in particular:
runZoned(() {
HttpServer.bind('0.0.0.0', port).then((server) {
server.listen(staticFiles.serveRequest);
});
},
onError: (e, stackTrace) => print('Oh noes! $e $stackTrace'));
I understand the whole zone-local-values thing, and the sort of AOP option of tracing code through a zone - enter/exit.
But other than that, is the code block above any different than:
try {
var server = await HttpServer.bind('0.0.0.0', port);
server.listen(staticFiles.serveRequest);
} catch (e, stackTrace) {
print('Oh noes! $e ${e.stackTrace}');
}
Thanks :)
The runZoned code introduces a new uncaught error handler.
Some asynchronous errors do not happen in a context where they can be thrown to user code. For example, if you create a future, but never await it or listen to it in any other way, and that future then completes with an error, that error is considered "uncaught" or "unhandled". Futures work using callbacks, and there has been no callback provided that it can call.
In that case, the current zone's uncaught error handler is invoked with the error.
Your first example introduces such a handler, which means that if the body of the runZoned call has any uncaught async errors, you'll be told about them.
That includes the future returned by calling then, but also any other asynchronous errors that may happen (if any).
When you await a future, and that future completes with an error, that error is (re)thrown, an can be caught. The try/catch of the second example will catch that error (and any thrown by calling listen, which shouldn't be any as long as server isn't null, which it shouldn't be).
So, the difference is only really in whether uncaught asynchronous errors are handled. (And how any errors in the catch code is propagated, because an error happening during an uncaught-error zone handler will again be uncaught.)
I have initialized Firebase in different ways.
1.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
#override
void initState() {
super.initState();
Firebase.initializeApp().whenComplete(() {
print("completed");
setState(() {});
});
}
including using FirebaseOption and adding appId, apiKey, projectId, messgingSenderId(dummy id)
//Using a FutureBuilder
atm these libraries are present:
firebase_core: ^1.10.6
firebase_auth: ^3.3.7
cloud_firestore: ^3.1.8
firebase_database: ^9.0.6
firebase_core_platform_interface: ^4.2.4
GogleService-Info.plsit is under flutterProject>ios>Runner
added via Xcode
I'm using a mac
Podfile has platform: ios, '11.0'
printing snapshot.connectionState = waiting & done
I receive an error when trying to persist to the database
I'm using FireStore Database
when trying to persist to the database using the Andriod emulator, I do not receive an error. obj is persisted.
but when trying to persist the same obj using the ios simulator I receive this error:
[core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()
When the exception was thrown, this was the stack:
#0 MethodChannelFirebase.app (package:firebase_core_platform_interface/src/method_channel/method_channel_firebase.dart:159:5)
#1 Firebase.app (package:firebase_core/src/firebase.dart:55:41)
#2 FirebaseFirestore.instance (package:cloud_firestore/src/firestore.dart:27:21)
#3 _firestore (package:flutter_shopping_app_fraire/providers/products.dart:6:56)
#4 _firestore (package:flutter_shopping_app_fraire/providers/products.dart)
#5 Products.addProduct (package:flutter_shopping_app_fraire/providers/products.dart:71:5)
#6 _EditProductScreenState._saveForm (package:flutter_shopping_app_fraire/screens/edit_product_screen.dart:111:53)
#7 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:989:21)
#8 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:198:24)
#9 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:608:11)
#10 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:296:5)
#11 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:230:7)
#12 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:563:9)
#13 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:94:12)
#14 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:139:9)
#15 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:539:8)
#16 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:137:18)
#17 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:123:7)
#18 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:439:19)
#19 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:419:22)
#20 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:322:11)
#21 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:374:7)
#22 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:338:5)
#23 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:296:7)
#24 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:279:7)
#28 _invoke1 (dart:ui/hooks.dart:170:10)
#29 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#30 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
(elided 3 frames from dart:async)
Handler: "onTap"
Recognizer: TapGestureRecognizer#32df4
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(372.7, 95.0)
finalLocalPosition: Offset(30.7, 48.0)
button: 1
sent tap down
If you have followed all necessary implementation from google docs. Just do the following. I've faced the same issue. Adding options fixed mine.
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
Not sure exactly where I went wrong when implementing firebase/firestore into my flutter project(I was doing everything manually), but I got the ios simulator to connect with firestore database by registering my ios app using FlutterFire CLI.
so instead of manually creating a new firebase project, and registering my ios and android apps through my firebase console, I installed Firebase CLI to my project, then installed FlutterFire CLI and ran flutterfire configure, You go through a couple prompts. That created my project and also registered my apps.
It was really that simple and now both of my ios and andriod can reach the database.
See example below:
import 'dart:async';
Future<void> main(List<String> arguments) async {
try {
await someMethod();
} catch (e, st) {
print("error: ${e}");
print("st : ${st}");
}
}
Future<void> someMethod() async {
final completer = Completer.sync();
_scheduleCompletion(completer);
await completer.future;
}
Future<void> _scheduleCompletion(Completer completer) async {
Future.delayed(Duration(milliseconds: 1), () {
try {
[][0]; // simulating error
completer.complete();
} catch (e, st) {
completer.completeError("error occured", st);
}
});
}
I am receiving following output:
#0 List.[] (dart:core-patch/growable_array.dart:254:60)
#1 _scheduleCompletion.<anonymous closure> (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions.dart:26:9)
#2 new Future.delayed.<anonymous closure> (dart:async/future.dart:315:39)
#3 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#4 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:395:19)
#5 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:426:5)
#6 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
As you can see stacktrace does not allow you to see what steps led to error. What I expect is something like:
[anonymous closure, where error actually happens]
[someMethod]
[main]
while instead I see only last part.
I know there is package, called stack_trace, that allows you to "wrap your whole program in Chain.capture and you will be happy". Unfortunately my real-world case is not that simple and I can not simply "wrap my program" in "Chain.capture".
Another reason on not using "Chain.capture" thing is that in some cases it's onError handler gets even worse stacktrace vs. simple try/catch block .
Also I would like to understand why this is happening and what should I do to fix my program so part of valuable trace is not lost.
UPDATE (based on Irn's answer):
Let's re-write someMethod not to use completer, but rather call another async method:
Future<void> someMethod() async {
await someOtherMethod();
}
Future<void> someOtherMethod() async {
throw "another error";
}
In that case I will have following trace:
#0 someOtherMethod (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions_original.dart:24:3)
<asynchronous suspension>
#1 someMethod (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions_original.dart:19:3)
<asynchronous suspension>
#2 main (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions_original.dart:5:5)
<asynchronous suspension>
As you see it contains all invocations main -> someMethod -> someOtherMethod, that allows you to narrow down the issue. As I understand in that case we should also lose everything as we are scheduling calls on event loop. But we see everything works as expected. Why is that?
About stack_trace package: I would love to use it, but I made small experiment and looks like sometimes it provides worse trace then the one from try/catch. But that is topic for another question ;)
The short answer is that you can't trace a stack which doesn't exist.
Dart's asynchronous code is event loop based.
When you do await someFuture, your function really sets up a callback on that future, using Future.then, and then it returns. That unwinds the stack leading up to setting that callback, and eventually the stack all the way back to the event loop.
(The return only happens when you evaluate the await, the someFuture expression is invoked immediately, and if that manages to throw before doing something asynchronous, then you will get the stack trace containing the stack prior to the await returning).
At a later time, the event loop triggers and calls the callback, and the asynchronous computation continues from there.
The original stack no longer exists at that point, which is why you see a stack trace leading back to an event loop event (here, a timer, which the VM implements by sending port events at specific times). The stack you see is the only stack which exists at the point you ask for it.
You can use package:stack_trace to capture a stack trace at the point where you schedule a callback, and then the stack trace package will remember that stack trace until the callback is run, and attempt to combine it with the stack traces reported (or captured) during the callback.
Since you make lots of callbacks, but don't usually need to see that many stack traces, it has a significant performance impact to capture stacks at every callback.
My iphone app get locked malloc. If I press pause button in Xcode it pauses in OSSpinLockLock$VARIANT$mp function.
#0 0x95dfbc2d in OSSpinLockLock$VARIANT$mp ()
#1 0x95dc2613 in szone_malloc_should_clear ()
#2 0x95dc366b in szone_malloc ()
#3 0x95df9962 in malloc_zone_malloc ()
#4 0x95dfa882 in malloc ()
#5 0x0219743a in operator new(unsigned long) ()
if I press continue and then pause again then it always shows same stack trace.
Look at your other threads. One of them is likely blocked inside of a malloc or free as well and the two are deadlocked against each other.
A somewhat common cause of this is allocating memory inside of a signal handler (which you should never do). You'll see this especially if you have some kind of "crash catcher" in your system, and inside of the handler you do complex operations.
I have an application that makes heavy use of audio. I use AVAudioPlayers to simplify the task.
I've noticed crashes from time to time when using the players, usually when I call [player play]. The stack trace looks something like this:
EXC_BAD_ACCESS
#0 0x32d531e6 in memmove$VARIANT$CortexA8 ()
#1 0x351056dc in Cached_DataSource::ReadFromHeaderCache ()
#2 0x351052a4 in Cached_DataSource::ReadBytes ()
#3 0x350ba47c in AudioFileObject::ReadBytes ()
#4 0x35104562 in AudioFileObject::ReadPacketDataVBR ()
#5 0x35102c70 in AudioFileObject::ReadPacketData ()
#6 0x351020b2 in AudioFileReadPacketData ()
#7 0x36bad1b8 in AudioPlayerAQOutputCallbackCore ()
#8 0x36bad7a4 in prepareToPlayQueue ()
#9 0x36bad954 in playQueue ()
#10 0x36bac27e in -[AVAudioPlayer play] ()
I have breakpoints set up in every dealloc call, so I know that I'm not inadvertently dealloc-ing a player or delegate or similar.
The players are sometimes stopped before they play their full sound, so I always make a [player setCurrentTime:0] call before playing. I'm not sure if this could cause some negative side effects.
Clearly the audio queue is trying to access invalid memory, but from the looks of things that memory lives way down in the file cache code, so I can't see how anything I'm doing would affect it. All of the files I'm playing are included as resources in the main bundle.
I created a repro that reliably reproduces this in the 4.1 simulator and on my 5.0.1 device. It can take a little while and hundreds of calls to setCurrentTime, play, and stop before it crashes.
I need to get to the bottom of this. I can start porting all of my code to use audio queues or something else if required, but I want to make sure that there isn't something I'm missing or that I could do differently to make this more stable.