I often have the situation where I want a UI element to "watch" an underlying value - supposing I am displaying an int - I want an an IObservable that I can subscribe to.
I have been using a Subject underneath, so I can just set it. That works really well... except for the first time. If I setup the subject - then later subscribe to it by opening a new UI element - it doesn't trigger an onnext until the next change.
I basically want something that works like a subject - but always and immediately does an onNext of the latest value to any new subscribers.
I know I can write such a construct myself - but it seems a common use case - is there something standard I'm missing?
You want either BehaviorSubject<T> or ReplaySubject<T>.
BehaviorSubject<T> replays the single most recent value and requires that you give an initial value in the constructor.
You use it like this:
var bs = new BehaviorSubject<int>(0);
bs.Subscribe(x => Console.WriteLine(x));
bs.OnNext(42);
bs.Subscribe(x => Console.WriteLine(x));
That would output 0, then 42, then 42 to the console.
ReplaySubject<T> is a little more general. It allows you to specify the number of values to replay, but doesn't enforce that you provide an initial value.
var rs = new ReplaySubject<int>(1);
rs.Subscribe(x => Console.WriteLine(x));
rs.OnNext(42);
rs.Subscribe(x => Console.WriteLine(x));
This produces 42, and then 42 to the console.
Compare this to a standard Subject<T>.
var s = new Subject<int>();
s.Subscribe(x => Console.WriteLine(x));
s.OnNext(42);
s.Subscribe(x => Console.WriteLine(x));
That just produces 42.
If you are using Rx in UI, you should take a look at ReactiveUI.
It has handy extension .ToProperty and more:
_isValid = this.WhenAnyValue(x => x.Username, x => x.Password, (user, password) => !string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password)).ToProperty(this, x => x.IsValid);
public bool IsValid => _isValid.Value;
This is basically what you are doing:
this.WhenAnyValue(x => x.Property) => property as observable
obs.ToProperty() => observable to property that you can bind to UI
both work with INotifyPropertyChanged
Related
Scenario
I'm receiving differents notification ids every 100 ms (Source1) and I need to do put every id in a Cache with the specific received date, if the id came twice I only update the date. After that I need to search information for the ids invoking a service, when I receive that information on my app, I need to show it ordered by the received date, updating the screen every 5 seconds. If any id is not refreshed in the range of 10 seconds by the Source1, it needs to change of state to display it in a different category or state
Problem
I'm trying to use Reactive Extensions to solve this problem, but I'm not sure if it's the correct technology because:
I don't know where I should have the cache and how to manage those states
How is the best way to manage the concurrency in general to invoke the external service in the meantime I can receive more ids could be new or old
At the end to have clean list as a result of information where I can see which elements are being updated and which of them not.
Can anyone help me? Thanks
It sounds like the .Scan operator might meet your needs.
Try this:
var source = new Subject<int>();
var query =
source
.Scan(new Dictionary<int, DateTime>(), (a, x) =>
{
a[x] = DateTime.Now;
return new Dictionary<int, DateTime>(a);
})
.Select(x => x.OrderByDescending(y => y.Value));
You can test this with the following code:
var values = new [] { 1, 2, 1, 3, 2, 1 };
Observable
.Interval(TimeSpan.FromSeconds(5.0))
.Take(values.Length)
.Select(x => values[x])
.Subscribe(source);
I get:
It's better though to use ImmutableDictionary so then the query looks like this:
var query =
source
.Scan(
new Dictionary<int, DateTime>().ToImmutableDictionary(),
(a, x) => a.SetItem(x, DateTime.Now))
.Select(x => x.OrderByDescending(y => y.Value));
var query =
source
.Scan(ImmutableDictionary<int, DateTime>.Empty, (a, x) => a.SetItem(x, DateTime.Now))
.Select(x => Observable.Interval(TimeSpan.FromSeconds(5.0)).Select(y => x).StartWith(x))
.Switch()
.Select(x => x.OrderByDescending(y => y.Value));
Try this query - it continues to produce values when your source does, but every 5 seconds after the latest value to come out it repeats the last item (unless the source produces a value and it then reset the 5 second timer).
To generate a "measure" every 5 sec I'm doing something like :
var Events = Observable.
Interval(TimeSpan.FromSeconds(5)).
Select(i => factory.GenerateRandomMeasure())
I would like to do the same but based on an existing Measure collection.
I assume I have to do something like :
var Events = existingList.ToObservable();
But is It possible to do add an interval notion in order to get each list item with a interval? (one item every 5 sec for example)
You can do either of these which work just fine:
(1)
var Events =
Observable
.Interval(TimeSpan.FromSeconds(5))
.Zip(existingList, (i, x) => x)
.Select(i => factory.GenerateRandomMeasure());
(2)
var Events2 =
Observable
.Generate(
0,
x => x < existingList.Count,
x => x + 1,
x => existingList[x],
x => TimeSpan.FromSeconds(5))
.Select(i => factory.GenerateRandomMeasure());
The first is probably more sensible and easier to write. The second is very much worth learning if you don't know it already as .Generate is very powerful and can be used in a lot of places.
I think I am missing a fundamental Observable concept.
I have a ChargeViewModel class which has a ReactiveList property which is a collection of itself
ChargeViewModel.Charges which contains related charges
I want to observe the latest entry in the charges collection (it has a ChargeViewModel.lastModified) and also have other properties in the UI based on the latest entry.
In the ctor I have this code which works to initialize the values; however it does not update the values for latestActionDate, latestBillToName, lastestNote when the observable variable "last" changes
I want the UI to update if the user updates the "last" charge or creates a new "last".
var last = Charges?
.Changed
.Select(_ => Charges.OrderByDescending(c => c.Model.LastModified).FirstOrDefault())
.StartWith(Charges.OrderByDescending(c =>c.Model.LastModified).FirstOrDefault());
last
.Select(c => c.Model.LastModified)
.ToProperty(this, vm => vm.LatestActionDate, out latestActionDate);
last
.Select(c => c.Model.BillToName)
.ToProperty(this, vm => vm.LatestBillToName, out latestBillToName);
last
.Select(c => c.Model.Note)
.ToProperty(this, vm => vm.LatestNote, out latestNote);
ReactiveList (by default) only notifies upon elements added/moved/removed, not upon content changes. It's fairly simply to enable it (and maybe you already did) by setting ChangeTrackingEnabled=true.
However when enabled, you'll receive such motifications on ItemChanged observable, and not on Changed one, so in your case you probably need to do:
Charges.Changed.Select(_ => Unit.Default).Merge(Charges.ItemChanged.Select(_ => Unit.Default))
.Select(_ => /* ... */)
....
This is related to my other question here. James World presented a solution as follows:
// idStream is an IObservable<int> of the input stream of IDs
// alarmInterval is a Func<int, TimeSpan> that gets the interval given the ID
var idAlarmStream = idStream
.GroupByUntil(key => key, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
<edit 2:
Question: How do I start the timers immediately without waiting for the first events to arrive? That's the root problem in my question, I guess. For that end, I planned on sending off dummy objects with the IDs I know should be there. But as I write in following, I ended up with some other problems. Nevertheless, I'd think solving that too would be interesting.
Forwards with the other interesting parts then! Now, if I'd like to group a complex object like the following and group by the key as follows (won't compile)
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
then I get into trouble. I'm unable to modify the part about SelectMany, Concat and Observable.Return so that the query would work as before. For instance, if I make query as
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key.First())))
.Subscribe(i => Console.WriteLine(i.Id + "-" + i.IsTest);
Then two events are needed before an output can be observed in the Subscribe. It's the effect of the call to First, I gather. Furthermore, I woul like to use the complex object attributes in the call to alarmInterval too.
Can someone offer an explanation what's going on, perhaps even a solution? The problem in going with unmodified solution is that the grouping doesn't look Ids alone for the key value, but also the IsTest field.
<edit: As a note, the problem probably could be solved firsly by creating an explicit class or struct and then that implements a custom IEquatable and secondly then using James' code as-is so that grouping would happen by IDs alone. It feels like hack though.
Also, if you want to count the number of times you've seen an item before the alarm goes off you can do it like this, taking advantage of the counter overload in Select.
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key))
.SelectMany(grp => grp.Select((count, alarm) => new { count, alarm }).TakeLast(1));
Note, this will be 0 for the first (seed) item - which is probably what you want anyway.
You are creating an anonymous type in your Select. Lets call it A1. I will assume your idStream is an IObservable. Since this is the Key in the GroupByUntil you do not need to worry about key comparison - int equality is fine.
The GroupByUntil is an IObservable<IGroupedObservable<int, A1>>.
The SelectMany as written is trying to be an IObservable<A1>. You need to just Concat(Observable.Return(grp.Key)) here - but the the type of the Key and the type of the Group elements must match or the SelectMany won't work. So the key would have to be an A1 too. Anonymous types use structural equality and the return type would be stream of A1 - but you can't declare that as a public return type.
If you just want the Id, you should add a .Select(x => x.Id) after the Throttle:
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)
.Select(x => x.Id))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
If you want A1 instead - you'll need to create a concrete type that implements Equality.
EDIT
I've not tested it, but you could also flatten it more simply like this, I think this is easier! It is outputing A1 though, so you'll have to deal with that if you need to return the stream somewhere.
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key))
.SelectMany(grp => grp.TakeLast(1));
I have two observables. One is from Observable.fromEvent(..), where the underlying event is the user checking a Winforms checkbox. The other is Observable.Interval(..) which I subscribe to in order to do some IO, and I would like to prevent this observable from doing IO, whenever the checkbox is not checked.
I could do it like this:
var gui = new GUI();
var booleans = Observable
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
Observable.Interval(TimeSpan.FromSeconds(10))
.CombineLatest(booleans, Tuple.Create)
.Where(t => t.Item2)
.Select(t => t.Item1)
.Subscribe(l => DoStuff(l));
but this has the overhead of mixing the booleans in and out of the stream. A nicer way of doing this would be, if I could generate a time-varying value from the booleans variable, which at all times had the value of the last event. Then I could do something like this:
var gui = new GUI();
var booleanState = Observable // typeof(booleanState) == ???
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
.TimeValue() // hypothetical syntax
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(_ => booleanState)
.Subscribe(l => DoStuff(l));
, which to me seems much closer to the problem statement. Is there anything like this in Rx, or is there anything else, that could make such problems easier to handle?
The Where statement in your interval should work with a properly scoped normal bool:
var booleans = Observable
.FromEvent<GUI.NewAllowHandler, bool>(
h => gui.NewAllow += h,
h => gui.NewAllow -= h)
var isBoxChecked = false;
booleans.Subscribe(t => isBoxChecked = t);
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(_ => isBoxChecked)
.Subscribe(l => DoStuff(l))
Edit: Per your comment, another way of doing it:
intervals = Observable.Interval(TimeSpan.FromSeconds(10));
booleans
.Where(t => t)
.SelectMany(_ => intervals.TakeUntil(booleans))
.Subscribe(l => DoStuff(l))
You need to model the checkbox checked state as Behavior and not as Event stream (because behavior has always a value and this value changes over a period of time - which fits with checkbox checked state). So you can do something like:
var booleans = new BehaviorSubject<bool>(chk.Checked)
var chkEvents = ... //generate boolean observable from checkbox check event
chkEvents.Subscribe(booleans);
Observable.Interval(TimeSpan.FromSeconds(10))
.Where(i => booleans.First())
.Subscribe(i => DoIO());
I'm going to give you two solutions. The first is a very simple and hopefully obvious one using only one observable. The second is a uses both observables.
Since you want to allow the IO only when the box is checked then this is the simplest approach:
Observable
.Interval(TimeSpan.FromSeconds(10))
.Where(_ => gui.IsChecked)
.Subscribe(l => DoStuff(l));
No need at all for the other observable.
But if you really need to use it then the Switch() extension method is your best bet. Try this:
booleans
.Select(b => b == true
? Observable.Interval(TimeSpan.FromSeconds(10))
: Observable.Empty<long>())
.Switch()
.Subscribe(l => DoStuff(l));
It's pretty clean and helps to show that there are empty periods if the checkbox is not ticked.
I hope this helps.