KentBoogaart

Badges

Third Anniversary5 LikesSecond AnniversaryFirst AnniversaryName Dropper10 CommentsFirst Comment

About

Username
KentBoogaart
Location
AU
Joined
Visits
55
Last Active
Roles
Member
Points
58
Badges
7

KentBoogaart · Kent Boogaart · ✭✭

About

Username
KentBoogaart
Location
AU
Joined
Visits
55
Last Active
Roles
Member
Points
58
Badges
7
  • Re: Let's talk performance

    I hope they won't be as tough as this makes it sound

    I hope they will! This will really help to force through perf improvements. And I'm not just talking about Xamarin improving XF performance (though that's certainly something I'm craving) - I'm also talking about me being allocated the time to work on perf in my own apps. Being able to point at these strict measures and the implications if they're not met will perhaps mean that perf is treated as a feature by clients instead of something to address "sometime in the future" (i.e. never).

  • Re: Let's talk performance

    I just discovered this thread, and it's somewhat therapeutic to discover others struggling with this, as I have for nearly 2 years now.

    @EZHart yeah, my tweet did not have sufficient context. I've thrown my test projects up here. You'll notice I even added support libraries to the Android project, which is one of the things I meant by "comparable project".

    Some ideas I've been pondering, but haven't had time to try/investigate:

    1. Would merging all assemblies (with ILMerge or ILRepack) help? It seems a huge amount of time is spent just loading assemblies and I'm wondering whether there's an overhead per load that could be amortized by merging.
    2. How does XF resolve renderers? My intuition is that it must be reflection looking for the ExportRenderer attributes. Surely this is expensive?
  • Improved life cycle support

    Summary

    As one of the authors of ReactiveUI, I have found that Xamarin.Forms does not provide the requisite view life cycle hooks to enable the framework to manage the activation and deactivation of resources in response to the comings and goings of views.

    Specifically, I have found these problems:

    • Page.Appearing actually fires in response to ViewDidAppear on iOS. One of ReactiveUI's benefits is its code-based, type-safe binding infrastructure. Appearing is the only hook we can use to get stuff on the screen, but it fires too late on iOS, so there is a brief period where the screen is not populated with data. I have already attempted to start a discussion here, but it seemed to stagnate.
    • Given a View, there is no way to tell when that View appears and disappears from the screen. There is no View.Appearing or View.Disappearing events as there is for Page. It might seem appropriate to therefore search up the visual tree for the Page and hook into that. However, this is inefficient (but perhaps that's inevitable) and there is no means of knowing when the Page itself changes. There is no PageChanged event, and PropertyChanged does not fire in response to the Page changing.

    API Changes

    • Page.Appearing should fire in response to ViewWillAppear on iOS. Moreover, there should be Page.Appeared and Page.Disappeared counterparts. Yes, this is potentially breaking to people who are hooked into Appearing and rely on it firing in response to ViewDidAppear on iOS. However, that behavior has always been incorrect and the fix for these people is literally to change Appearing to Appeared (or hold off on the XF upgrade).
    • Ideally, add Appearing, Appeared, Disappearing, Disappeared to View and have them Just Work. Failing that, ensure there is a hook by which framework authors can know the hosting Page for a View has changed. Perhaps a specific PageChanged event, or just ensuring PropertyChanged works as expected in this scenario.

    Intended Use Case

    The use case here is to give framework authors what they need to facilitate the creation of self-sufficient, standalone, encapsulated components by framework consumers. I specifically work on ReactiveUI, but other framework authors will/have run into the same issues.

    ModEdit - Spec incoming ASAP

  • Re: XForms needs an ItemsControl

    @EricGrover I hope you don't mind, but I took your code and improved it. Changes include:

    • removed the generic parameter. It doesn't add any value and makes the control harder to consume in XAML
    • made the ItemsSource property of type IEnumerable instead of restricting to ObservableCollection<T>
    • assumed created views are of type View, never ViewCell. The latter doesn't really make sense because no events like Appearing and Disappearing will be raised, so "supporting" ViewCell will break such expectations
    • bug fixes, including:
      • detaching from old collections where relevant
      • raises ItemCreated for items already in the new collection

    Here's the code:

        using System;
        using System.Collections;
        using System.Collections.Specialized;
        using Xamarin.Forms;
    
        public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
    
        // in lieu of an actual Xamarin Forms ItemsControl, this is a heavily modified version of code from https://forums.xamarin.com/discussion/21635/xforms-needs-an-itemscontrol
        public class RepeaterView : StackLayout
        {
            public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create<RepeaterView, IEnumerable>(
                p => p.ItemsSource,
                null,
                BindingMode.OneWay,
                propertyChanged: ItemsChanged);
    
            public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create<RepeaterView, DataTemplate>(
                p => p.ItemTemplate,
                default(DataTemplate));
    
            public event RepeaterViewItemAddedEventHandler ItemCreated;
    
            public IEnumerable ItemsSource
            {
                get { return (IEnumerable)GetValue(ItemsSourceProperty); }
                set { SetValue(ItemsSourceProperty, value); }
            }
    
            public DataTemplate ItemTemplate
            {
                get { return (DataTemplate)GetValue(ItemTemplateProperty); }
                set { SetValue(ItemTemplateProperty, value); }
            }
    
            private static void ItemsChanged(BindableObject bindable, IEnumerable oldValue, IEnumerable newValue)
            {
                var control = (RepeaterView)bindable;
                var oldObservableCollection = oldValue as INotifyCollectionChanged;
    
                if (oldObservableCollection != null)
                {
                    oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
                }
    
                var newObservableCollection = newValue as INotifyCollectionChanged;
    
                if (newObservableCollection != null)
                {
                    newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
                }
    
                control.Children.Clear();
    
                if (newValue != null)
                {
                    foreach (var item in newValue)
                    {
                        var view = control.CreateChildViewFor(item);
                        control.Children.Add(view);
                        control.OnItemCreated(view);
                    }
                }
    
                control.UpdateChildrenLayout();
                control.InvalidateLayout();
            }
    
            protected virtual void OnItemCreated(View view) =>
                this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
    
            private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                var invalidate = false;
    
                if (e.OldItems != null)
                {
                    this.Children.RemoveAt(e.OldStartingIndex);
                    invalidate = true;
                }
    
                if (e.NewItems != null)
                {
                    for (var i = 0; i < e.NewItems.Count; ++i)
                    {
                        var item = e.NewItems[i];
                        var view = this.CreateChildViewFor(item);
    
                        this.Children.Insert(i + e.NewStartingIndex, view);
                        OnItemCreated(view);
                    }
    
                    invalidate = true;
                }
    
                if (invalidate)
                {
                    this.UpdateChildrenLayout();
                    this.InvalidateLayout();
                }
            }
    
            private View CreateChildViewFor(object item)
            {
                this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
                return (View)this.ItemTemplate.CreateContent();
            }
        }
    
        public class RepeaterViewItemAddedEventArgs : EventArgs
        {
            private readonly View view;
            private readonly object model;
    
            public RepeaterViewItemAddedEventArgs(View view, object model)
            {
                this.view = view;
                this.model = model;
            }
    
            public View View => this.view;
    
            public object Model => this.model;
        }