-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
XF view activation is fundamentally broken #1116
Comments
I've just come across this as a giant memory leak in our application, are there any possible workarounds we can use to get the view deactivation to happen when using a ReactiveContentView? |
What if change the activation paradigm to use ViewModel based activation? RoutedViewHost will activate VM and VM will activate View. |
We ended up fixing it generally using a platform effect to hook into the lifetime of the view renderer and routing that through to the view to be used by the IActivationForViewFetcher We only implemented for UWP and Android, but this approach would work on iOS and WPF without an issue (and likely the rest) In our case we subclassed ReactiveView and added the effect/ICanActivate there (to avoid unintended regressions) but ideally we would have modified the ActivationForViewFetcher I'll try to remember to post some code samples tomorrow when I'm at work |
@rbev can you provide some sample code? Tks in advance. |
I can't find the guide i originally used that this code largely came from, but create a routing effect & singleton registration logic
public class ViewLifecycleEffect : RoutingEffect
{
public event EventHandler<EventArgs> Loaded;
public event EventHandler<EventArgs> Unloaded;
private ViewLifecycleEffect() : base($"myeffectnamespace.{nameof(ViewLifecycleEffect)}") { }
public bool IsLoaded { get; set; }
public void RaiseLoaded(Element element) => Loaded?.Invoke(element, EventArgs.Empty);
public void RaiseUnloaded(Element element) => Unloaded?.Invoke(element, EventArgs.Empty);
/// <summary>
/// Registers or returns an existing instance of the effect on the specified element.
/// This is because the limitations of the routed effect means that we have to only have a single instance registered or we can't raise
/// the events for all of them without causing duplicate events to be raised.
/// </summary>
public static ViewLifecycleEffect Register(VisualElement element)
{
var effect = element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();
if (effect == null)
{
effect = new ViewLifecycleEffect();
element.Effects.Add(effect);
}
return effect;
}
} Then in the platform implementation you just need to do something simliar to this (iOS): protected override void OnAttached()
{
_viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();
UIView nativeView = Control ?? Container;
_isLoadedObserverDisposable = nativeView?.AddObserver("superview", ObservingOptions, IsViewLoadedObserver);
}
protected override void OnDetached()
{
_viewLifecycleEffect.RaiseUnloaded(Element);
_isLoadedObserverDisposable.Dispose();
}
private void IsViewLoadedObserver(NSObservedChange nsObservedChange)
{
if (!nsObservedChange.NewValue.Equals(NSNull.Null))
{
_viewLifecycleEffect.IsLoaded = true;
_viewLifecycleEffect?.RaiseLoaded(Element);
}
else if (!nsObservedChange.OldValue.Equals(NSNull.Null))
{
_viewLifecycleEffect.IsLoaded = false;
_viewLifecycleEffect?.RaiseUnloaded(Element);
}
} and android use these events to do the same thing..... _nativeView = Control ?? Container;
_nativeView.ViewAttachedToWindow += OnViewAttachedToWindow;
_nativeView.ViewDetachedFromWindow += OnViewDetachedFromWindow; then register this to integrate it into the XF platform!
We also use this effect to allow view activation when binding to a data template using an attached property - this is primarily used to allow list binding UI controls to handle lifecycle of data templates (and virtualisation) rather than forcing it to go through the full view locator system. |
The XF activation-for-view-fetcher attempts to support:
ICanActivate
(as of a recent PR)The views bit is totally busted. It currently hooks into
IsVisible
on the view, because that totally worked Once Upon A Time. However,IsVisible
no longer changes tofalse
when the view's page disappears.I figured the best way around this is to get ahold of the view's
Page
and hook into itsAppeared
andDisappeared
events. If we combine that stream with another stream ofIsVisible
changes, we could totally support view activation.Alas, getting ahold of a view's
Page
is impossible. More accurately, knowing when to get ahold of a view'sPage
is impossible. We can always traverse up the UI tree to find thePage
, but we have no hook telling us when we should do this. There's noPageChanged
event, for example. I tried everything I could think of as an alternative. The most promising options were:PropertyChanged
where the property name is"Parent"
. Do this recursively all the way up the chain so that if any parent changes, we can re-determine thePage
. No dice because it doesn't always fire (seemingly when deserializing XAML).OnParentChanged
and forward the event. Same problem as above.Even if we could get one of these options to work, I suspect the performance would be woeful. What we really need is to hook into whatever mechanism it is that XF itself uses to tear down bindings. Or maybe the bindings are weak and it doesn't tear them down at all - not sure.
So for my particular use case (an app I'm trying to get into production), I had to throw my hands in the air and change view activations to be manual. That is, the containing
Page
forwards activation calls onto the child view. It was ugly and time-consuming, but it worked.Longer term I think we're going to need XF to support some kind of mechanism that tells us when a view is re-housed in a different
Page
, then update our XF activation-for-view-fetcher accordingly.The text was updated successfully, but these errors were encountered: