I make an app using firestore and a bottom navigation bar in Flutter. The problem is that when I switch between tabs, the build method is called everytime. The build method downloads data from firestore. Therefore, the app flickers when I switch tabs (the spinning bar is showed for a very short time). I tried to fix this by moving the firestore stream to the constructor. However, since the stream can emit before the build method, it loads forever.
A solution could been to save the last value that was emitted. I tried to fix this using the shareReplay method in Rx, but they have not implemented in RxDart yet. So, what is the best practice to implement this?
Use the shareValue operator of rxdart:
final observable = Observable(yourStream).shareValue();
Internally, this operator uses a BehaviorSubject. It will subscribe to the stream as soon as there is a single subscriber (it will only subscribe once), and unsubscribe (and dispose the subject) when there are no more subscribers.
Also, as you said, you have to create the observable in initState or a similar method (NOT the build method!). The observable should be stored in a field in the State.
In the currently accepted answer, the Observable class in RXDart has now been deprecated. Instead, you could use a BehaviorSubject but its probably best to use a ValueConnectableStream instead, like this:
final newStream = ValueConnectableStream(yourStream).autoConnect()
See the RXDart docs for more info.
Convert stream to BehaviorSubject in rxDart.
BehaviorSubject<Data> _subject = BehaviorSubject<Data>();
stream.listen((x) => _subject.add(x));
I ran the flutter app in release mode and the lag was gone, without any modifications.
You could have a look at BehaviorSubject in rxdart. According to the docs
The latest item that has been added to the subject will be sent to any new listeners of the subject.
Related
On RxCocoa I was wondering why the PublishRelay doesn't have an asDriver() method like the BehaviorRelay ? Currently if I want to convert the publishRelay into a Driver, I have to specify what to return in case of error which seems weird given that the relays can't generate errors...
Those two versions of ...Relay are used to model different concepts:
BehaviorRelay represents State
PublishRelay represents Events
It makes sense to replay State, hence BehaviorRelay replays its latest value.
It makes less (no?) sense to replay Events, hence PublishRelay does not replay its latest value.
With this in mind, it makes sens for a BehaviorRelay to be transformable to Driver, as a driver drives the application using State. The sharing strategy for BehaviorRelay and Driver is to share side effects and replay the latest value while at least one observable is connected.
A PublishRelay is better represented by a Signal, so you probably could use a Signal to emit to. The sharing strategy in this case is will not replay the latest value, but still share the side effects while at least one observable is connected.
(I build this answer using this great comment from #freak4pc on RxSwift's repository)
If someone need a simple example:
publishRelay
.asDriver(onErrorDriveWith: Driver.empty())
.drive(onNext: { value in
})
.disposed(by: disposeBag)
I am new to flutter, dart or reactive programming in general.
I suppose, I am calling add() on the BehaviorSubject while items are being added to that stream by some previous call.
How can I avoid this bad state? How can I add the event once the event in previous call have been added?
As mentioned in the documentation: "Events must not be added directly to this controller using add, addError, close or addStream, until the returned future is complete." You may want to wait for the Future callback first before calling add().
I'm looking for a way to use a handler function to respond to changes to an observable collection in Dart. I want to pipe my change directly to a function.
List things = toObservable([]);
//...
things.onChange.listen((e) => onThingsChanged(e) ); //blows up
//...
function onThingsChanged(e){
//...
}
There obviously isn't such thing as onChange, so what am I looking for here? All the examples I find are just watching the changes with a <template> tag in the HTML.
There is a good (official) article about Observables and Data Binding with Web UI. I think it is still under construction and thus there are no links on the dartlang.org website yet.
The part that answers your question is: Expression Observers
You can do something like this:
List things = toObservable([]);
observe(() => things, onThingsChanged);
onThingsChanged(ChangeNotification e) {
// ...
}
Few additions to Marco's answer which might not be obvious.
Besides observe which takes an expression, you can also use observeChanges which takes an instance of Observable, so you can write observeChanges(things, (c) => ...).
More important is the fact that if you use ObservableList outside of Web UI context (e.g. in a standalone script), the changes will not be triggered immediately. Instead, changes are queued and you need to call deliverChangesSync to trigger the notifications. The listener will then get notified with list of changes.
If I write the following Dart code, how do I know which click handler happens first?
main() {
var button = new ButtonElement();
var stream = button.onClick.asBroadcastStream();
stream.listen(clickHandler1);
stream.listen(clickHandler2);
}
Let's say I'm in other code that doesn't know anything about the first two click handlers, but I register another one.
Can I know that the stream has two listeners?
Can I pause or cancel all other subscribers?
If I write button.onClick.asBroadcastStream() again elsewhere, does it point to the same stream as was used in main?
Can I say in one of the handlers to not pass event on to the other broadcast listener? Is that a consumer?
Let's say I'm in other code that doesn't know anything about the first
two click handlers, but I register another one.
Can I know that the stream has two listeners?
No, you can't. You could extend the stream class or wrap it and provide this functionality yourself, but it does not feel like a good design choice, because I don't think a listener should know about other listeners. What are you trying to do exactly? Perhaps there's a better way than letting listeners know about each other.
Can I pause or cancel all other subscribers?
You can cancel/pause/resume only the subscriber you are dealing with. Again, you probably shouldn't touch other listeners, but I guess you could wrap/extend the Stream class to have this behavior.
If I write button.onClick.asBroadcastStream() again elsewhere, does it point to the same stream as was used in main?
No, at least not at the current version of SDK. So, unfortunately, you need to store a reference to this broadcast stream somewhere, and refer to it, because calling asBroadcastStream() multiple times will not yield in the result you might expect. (Note: at least based on quick testing: http://d.pr/i/Ip0K although the documentation seems to indicate different, I have yet to test a bit more when I find the time).
Can I say in one of the handlers to not pass event on to the other broadcast listener?
Well, there's stopPropagation() in the HTML land which means that the event won't propagate to other elements, but it's probably not what you were looking for.
For being able to stop an event firing in other listeners, there needs to be an order of which the listeners are getting called. I believe the order is the order of registration of those listeners. From the design perspective, I don't think it would be a good idea to allow a listener to cancel/pause others.
Event propagation in HTML makes sense since it's about hierarchy, but here we don't have that (and even in case of events in HTML there can be multiple listeners for the single element).
There's no way to assign weight to listeners or define the order of importance, therefore it's not surprising that there isn't a way to stop the event.
Instead of letting listeners know about each other and manipulate each other, maybe you should try to think of another way to approach your problem (whatever that is).
Is that a consumer?
The StreamConsumer is just a class that you can implement if you want to allow other streams to be piped into your class.
Can I know that the stream has two listeners?
No, you have a ´Stream´ that wraps the DOM event handling. There is no such functionality.
Can I pause or cancel all other subscribers?
Look at Event.stopPropagation() and Event.stopImmediatePropagation(), and possibly Event.preventDefault().
If I write button.onClick.asBroadcastStream() again elsewhere, does it point to the same stream as was used in main?
[Updated] No, the current implementation doesn't gives you the same Stream back since the onClick getter returns a new stream every time it is invoked. However, the returned stream is already a broadcast stream so you shouldn't invoke asBroadcastStream() on it. If you do you will hower just get a reference to the same object back.
Stream<T> asBroadcastStream() => this;
Can I say in one of the handlers to not pass event on to the other broadcast listener? Is that a consumer?
Again, take a look at Event.stopPropagation() and Event.stopImmediatePropagation(), and possibly Event.preventDefault().
I am creating an action script library.I am calling some APIs which parses some xml and gets me the result. It dispatches an Event.COMPLETE when the parsing is done. I want to monitor whether this event is dispatched in some while loop like "while(eventnotdispatched)"
is it possible? I know the other way would be to addeventlistener. But please let me know if the other thing is possible.
Thanks
NO, it is not possible. Actionscript is single threaded. Thus while you are waiting in your while loop, that is the only thread running, and the process you are waiting for can never complete. This is why everything is done with events, so that's what you should use. If you need to update your display periodically while you are waiting for something to complete...again, use events. Create a Timer object which generates a TIMER event every so often, and use that to make your updates.
EDIT: Davr is right, you would not be able to use the while loop like this. You would need a timer.
Yes, it is possible to poll for it. BUT you will still need to create an event listener. It will work something like this:
private var loadCompleted = false;
private var timer:Timer= new Timer(1);
private function onInitCompleted(event:Event):void
{
timer.addEventListener(TimerEvent.TIMER, timerHandler);
timer.start();
}
private function loadCompleteEventHandler(event:Event):void
{
loadCompleted = true;
...
}
private function timerHandler()
{
if(!loadCompleted)
{
... // stop the timer or something.
timer.stop();
}
}
Please note, this is VERY BAD code. I would NEVER use it in production because Actionscript is a event driven language. There should be absolutely NO REASON for you to need to do this. Whatever you are trying to do could be accomplished using another method much simpler. Tell me what you are trying to accomplish with this and I will present a better solution.
Sorry for yelling, it's late and I am sleepy.
Doing that means forcing a synchronous model of execution on the underlying asynchronous model (that works with callbacks).
What are you trying to achieve exactly, and why not use a callback?
I agree with the statements about it probably being a bad idea and a while loop will certainly not work this way in ActionScript. However, there may be legitimate reasons for doing what you are attempting to do. Only you can prevent bad code. Instead of judging, I'll just get to an answer for your question.
First I'm going to make an assumption, that what you really want to do is monitor a property and for some reason the API for this object does not dispatch an event when this property changes. I'm making this assumption because if you have the event available, I assume you would just use the event.
So... you have an object weirdXmlObj with a property loaded that defaults to false but goes to true when the XML is loaded.
In this case with slight modifications the code posted by CookieOfFortune would in fact work. You wouldn't need the loadCompleteEventHandler function (which was never attached anyway) and in the timer handler you would simply check if( weirdXmlObj.loaded ) and then branch however you wanted to.
Ah but there may be a simpler way, depending on what you are doing.
If you have a display object handy. (i.e. something that makes sense, not just some random object.) You can attach your code to the stage's EnterFrame event instead of using a timer.
myDisplayObject.stage.addEventListner(Event.ENTER_FRAME,frameEnterHandler);
A couple of things to be aware of:
You don't really even need to go to the stage level, all display objects support the EnterFrame event, but it's a nice place to attach the event listener.
You really should keep whatever the function calls to a minimum. In particular the actual frameEnterHandler function should do nothing more than do the if( weirdXmlObj.loaded ) check.
You are attempting to circumvent event-driven programming, which is not a good idea. This is often the case when someone approaches from an older model and does not yet have a good frame of reference to appreciate the elegance of event-driven programming.
Events are your friends. They work very well. Your loadCompleteHandler is all that is required. Want to do something else in response? Add the call in that handler:
private function loadCompletedHandler(event:Event):void
{
waitingObject.fileWasLoadedSoGoDoThatThing();
}
There is no need to make it any more complicated than that. No need for a semaphore or a loop to check the semaphore. Unnecessary environmental semaphores can break the encapsulation that could shield you from unwanted side effects.