realm-dotnet icon indicating copy to clipboard operation
realm-dotnet copied to clipboard

[Bug]: PropertyChange events lead to endless Binding loop in Xamarin Forms App

Open markusroessler opened this issue 2 years ago • 4 comments

What happened?

In our Xamarin Forms app we have an EntryControl whose text property is bidirektional bound to a XF property which in turn is bidirektional bound to a RealmObject property. When the XF property is changed twice very quickly (by user interaction) the app hangs forever. The XF binding mechanism keeps changing the property value back and forth between the two values. The issue only occurs when binding to a persistent Realm property! When binding to a transient/ignored property (using RaisePropertyChanged), the binding works as intended.

Please see the attached sample app:

  • Press the „Change transient value“ button -> the Entry displays „1“ very shortly and then switches to „2“ as intended
  • Press the „Change persistent value“ button -> the app hangs forever

I think there is something wrong with the PropertyChange event propagation of persistent Realm properties. The following stacktrace was captured, when the MainPage „Value“ property was changed from „1“ to „2“. There you can see that the WovenSetterMethodInfo actually receives the value „2“ and invokes „RaisePropertyChanged“ - but MyRealmObject.Value is still „1“?! The value should be changed to „2“ before invoking „RaisePropertyChanged“. Bildschirmfoto 2022-06-10 um 18 01 34

Repro steps

Please see the attached sample app:

  • Press the „Change transient value“ button -> the Entry displays „1“ very shortly and then switches to „2“ as intended
  • Press the „Change persistent value“ button -> the app hangs forever

Version

10.14.0

What SDK flavour are you using?

Local Database only

What type of application is this?

Xamarin

Client OS and version

Android 10

Code snippets

https://github.com/markusroessler/XamarinRealmDelayedPropertyChange

Stacktrace of the exception/crash you're getting

No response

Relevant log output

No response

markusroessler avatar Jun 10 '22 17:06 markusroessler

Hi @markusroessler. Thank you for reporting the issue and supplying a repro project. We'll take a look soon and report back the findings.

LaPeste avatar Jun 13 '22 09:06 LaPeste

one additional note: The binding works as intended, if I do a Task.Yield() between the two property changes. This may be an indicator, that the SynchronizationContextScheduler.Post you are doing for (some?) PropertyChange-Notifications is problematic. The "Post" should not be necessary if the transaction is committed on the main thread or is it?

markusroessler avatar Jun 13 '22 10:06 markusroessler

I think I can see what's going on there. The second write will force notifications to be delivered synchronously. This will update the value of the entry, which in turn triggers triggers the two-way databinding and attempts to set the value to the previous value ("1"). It's not immediately clear to me how we can avoid that easily.

Regarding SynchronizationContextScheduler.Post - that's necessary because the Realm change notifications are not computed on the main thread, even if the transaction did happen on the main thread. The notification callback is invoked from a background thread that is computing notifications and if we didn't post back to the original thread, attempting to access the object raising notifications would result in a "Realm accessed from incorrect thread" exception.

We'll need to do a bit more investigation here, but wanted to update you that this is not super trivial to fix and may take a while. In the meantime, is this a simplified repro for a behavior you're experiencing in your own project? If that's the case, one workaround is to explicitly call realm.Refresh() before doing the second write transaction. Alternatively, you can also make some bindings one-way to avoid the loop of updates. Yielding the thread is an alternative of realm.Refresh() with its own drawbacks/benefits.

nirinchev avatar Jun 17 '22 13:06 nirinchev

@nirinchev Thank you for the detailed update! Yes, thats a very simplified repro. Our project actually looks like this:

Library > UserControl

  • contains an Entry and two Buttons to increase/decrease the the Entry's value
  • contains a Value-Property that is bidirectional bound to the Entry.Text
  • The Value-Property is changed when pressing the Buttons

App > Page

  • binds the UserControl.Value to a ViewModel/RealmObj-Property

The UserControl is implemented in a separate library that has no reference to Realm. So I think realm.Refresh() is not an option. Making some bindings one-way is also not possible in this case - all properties can be changed independently. So I think we stick with Task.Yield() for now and hope the issue doesn't occur in other binding scenarios :grimacing:

markusroessler avatar Jun 17 '22 15:06 markusroessler