Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] this.RaiseAndSetIfChanged does not trigger PropertyChanged event for generic IReactiveObject #2594

Closed
timothylcooke opened this issue Dec 8, 2020 · 9 comments
Labels

Comments

@timothylcooke
Copy link

timothylcooke commented Dec 8, 2020

Describe the bug
If a custom IReactiveObject (in this example, IReactiveObjectFilter) calls this.RaiseAndSetIfChanged(ref _field, value), then RaisePropertyChanging and RaisePropertyChanged methods are never called, and therefore PropertyChanging and PropertyChanged events are not triggered.

While IReactiveObjectFilter.WhenAnyValue(x => x.ShouldFilter) receives notifications when this.RaiseAndSetIfChanged is called, SourceList<IReactiveObjectFilter>.Connect().AutoRefresh(x => x.ShouldFilter) does not receive notifications, since PropertyChanged is never called.

Steps To Reproduce
https://github.com/timothylcooke/IReactiveObjectExample.git demonstrates the behavior. This is a WPF project that shows a window with a TabControl. Both Tabs shows a MyUserControl. Each MyUserControl has a binding to a MyViewModel, which has a SourceList<IFilter> of filters (toggled with the CheckBoxes). In the first tab, the IFilter objects are ReactiveObjectFilter objects, and in the second tab, the IFilter objects are IReactiveObjectFilter objects. Whether the IFilter extends ReactiveObject or implements IReactiveObject determines if the PropertyChanged event is triggered, and therefore if the SourceList<IFilter>.Connect().AutoRefresh(x => x.ShouldFilter) triggers updates.

Expected behavior
If I implement IReactiveObject and call this.RaiseAndSetIfChanged(), it should call RaisePropertyChanged.

Screenshots
In the screenshots below, the key difference is that in the first screenshot, where the IFilter objects inherit from ReactiveObject, we see the line Filter… PropertyChanged("ShouldFilter") fired. This ripples down and ends up triggering filtersSource.Connect().AutoRefresh(x => x.ShouldFilter), and in turn updates the currentFilters collection. In the second screenshot, where the IFilter objects implement IReactiveObject, PropertyChanged is never called on the filter, and therefore filtersSource.Connect().AutoRefresh(x => x.ShouldFilter) is never fired. This leaves our currentFilters collection unmodified, and therefore does not filter any items in the Filtered Names list.

image
image

Environment

  • OS: Windows 10 Pro x64
  • Version ReactiveUI 12.1.5
  • Device: Dell XPS 13 9360

Additional context
N/A

@timothylcooke timothylcooke changed the title [BUG] this.RaisePropertyChanging does not trigger PropertyChanged event for generic IReactiveObject [BUG] this.RaiseAndSetIfChanged does not trigger PropertyChanged event for generic IReactiveObject Dec 8, 2020
@glennawatson
Copy link
Contributor

I'll have a look at this one after I get my massive .net 5 branch sorted.

Will do it as part of a Fody refactor I did a while back and get that in.

@glennawatson
Copy link
Contributor

Also thanks for providing the sample, that always helps.

@timothylcooke
Copy link
Author

I just added screenshots of the sample app. I figured that's easier to see than cloning and debugging.

Also thanks for providing the sample, that always helps.

You're welcome. I can't tell you how many "bugs" I've identified in various libraries where I try to make a sample project to reproduce it, find that I can't, and eventually realize it's not a bug and I'm doing something stupid in my main project. I always feel better about reporting something if I can reproduce it in a simple project.

P.S. I'm still fairly new to Dynamic Data, so I hope you'll forgive me if some of my observables are less than optimal.

@bruzkovsky
Copy link
Contributor

Hi, I have the same problem after upgrading to 11.5.35 from 10.5.43

@bruzkovsky
Copy link
Contributor

@glennawatson @RLittlesII can I be of any help with fixing this?

This issue is pressing for us, because after updating to ReactiveUI 13.3.1 none of the Xamarin Forms bindings that go directly to the PageViewModel are working. This is because we use MvvmCross, and they require us to extend MvxViewModel, so we had to implement IReactiveObject in those view models ourselves. Now, it looks like you are requiring us to extend ReactiveObject too, which is not possible (no multiple inheritance in C#). So the only thing that works is using Rx bindings where there are no PropertyChanged events fired (interestingly, that works). But that's not a permanent solution, I think you'd agree.

When I look at the ReactiveObject implementation, it seems the following line is needed to enable propagation of PropertyChanged events - if I read the execution flow correctly.

this.SubscribePropertyChangedEvents();

This is the implementation of the extension method:

internal static void SubscribePropertyChangedEvents<TSender>(this TSender reactiveObject)
where TSender : IReactiveObject
{
var s = state.GetValue(reactiveObject, _ => (IExtensionState<IReactiveObject>)new ExtensionState<TSender>(reactiveObject));
s.SubscribePropertyChangedEvents();
}

Here is the implementation in IReactiveObjectExtensions.ExtensionState (called in line 220):

public void SubscribePropertyChangedEvents() => _ = _propertyChanged.Value;

And finally, where the event should be propagated (line 375 checks if the Lazy was created with SubscribePropertyChangedEvents):

public void RaisePropertyChanged(string propertyName)
{
if (!AreChangeNotificationsEnabled())
{
return;
}
var changed = new ReactivePropertyChangedEventArgs<TSender>(_sender, propertyName);
if (_propertyChanged.IsValueCreated)
{
// Do not use NotifyObservable because event exceptions shouldn't be put in ThrownExceptions
_propertyChanged.Value.OnNext(changed);
}
if (_changed.IsValueCreated)
{
NotifyObservable(_sender, changed, _changed.Value.subject);
}
}

I assume that simply calling SubscribePropertyChangedEvents would be enough, but it's internal. What was the idea behind that decision / can we make an easy fix and turn this method public?

@glennawatson
Copy link
Contributor

Looking through it you should be able to make those public. The rationale was due to it accessing a internal state object but it should abstract it for you.

It's a breaking change but we have some changes to binding coming that are breaking so now would be a good time for you to make these changes.

@bruzkovsky
Copy link
Contributor

Thanks for the quick reply, I'll fire up a PR!

bruzkovsky pushed a commit to bruzkovsky/ReactiveUI that referenced this issue Jun 1, 2021
- this change enables consumers to implement IReactiveObject and get PropertyChanged and PropertyChanging notifications (which regressed in version 11.4.1)
- to get the notifications, one must call this.SubscribePropertyChangedEvents() or this.SubscribePropertyChangingEvents() on the IReactiveObject instance
- related: reactiveui#2594
glennawatson pushed a commit that referenced this issue Jun 2, 2021
* fix: Change access modifier for SubscribePropertyChangedEvents to public

- this change enables consumers to implement IReactiveObject and get PropertyChanged and PropertyChanging notifications (which regressed in version 11.4.1)
- to get the notifications, one must call this.SubscribePropertyChangedEvents() or this.SubscribePropertyChangingEvents() on the IReactiveObject instance
- related: #2594

* fix: unit tests failing on Device with de_AT culture

- there were unit tests failing because they compared string representations of double in the CultureInfo.InvariantCulture format. This was changed to CultureInfo.CurrentCulture to work with the culture of the Device running the tests

* fix: approval tests failing because of wrong parameter names

* fix: Approval tests fail after saving file which automatically adds blank line

Co-authored-by: Matthias Bruzek <matthias.bruzek@opti-q.com>
@timothylcooke
Copy link
Author

timothylcooke commented Jun 16, 2021

For the benefit of anybody who stumbles across this issue in the future, this was fixed in version Version 14.1.1. From the commit notes for commit 4f94c6b, we have:

This change enables consumers to implement IReactiveObject and get PropertyChanged and PropertyChanging notifications (which regressed in version 11.4.1) to get the notifications, one must call this.SubscribePropertyChangedEvents() or this.SubscribePropertyChangingEvents() on the IReactiveObject instance

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 23, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants