Xamarin.Forms: Reopening application best pratices

If you have experienced that reopening your application from the phone’s app drawer or launcher is fails because it crashes your application, or displays the first set main page again instead of displaying the latest one, keep reading this article.

If you have crashes, or malfunctions, you are probably initializing something in the constructor of the App.xaml.cs, or in the overridden method called ‘OnStart’ like this:

        public App()
        {
            InitializeComponent();
            InitalizeOnlyOnceClass.Initalize();
        }

        protected override void OnStart()
        {
            AnAnotherInitalizeOnlyOnceClass.Initalize();
        }

Imaginary InitalizeOnlyOnceClass’s Initialize method can be called only once, for the second call, it throws an exception. After you have started your program, sent to background, and bringing it back throws the second Initalization’s exception. This is because the App class gets constructed again when reopening application from the drawer.

To handle this, you should make a boolean for the application’s initialization progress.

        private static bool isInitalized;

        public App()
        {
            InitializeComponent();

            if(isInitalized == false)
            {
                InitalizeOnlyOnceClass.Initalize();
            }
        }

If you receive a blank white screen, you are probably forget to set the Main Page

Make sure, that the Application.Current.MainPage always gets a value.
If you have implemented the Initialization by your self, or an another way, make sure you have handled correctly the else statement also.
Even if the application has been initialized once, the Application.Current.MainPage have to be set always when reconstructing the App.

Continue with the last page opened in the application

If you want to continue always with the last page opened, you need to store the last page always, when you are navigating from one to an another.

You can store the last page with making a class used for navigation, like this:

    public static class SimpleNavigationLogic
    {
        private static Xamarin.Forms.Page lastNavigatedPage;

        public static void ChangeMainPage(Xamarin.Forms.Page pageToSet)
        {
            Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
            {
                Application.Current.MainPage = pageToSet;
            });
            lastNavigatedPage = pageToSet;
        }

        public static void RestoreMainPage()
        {
            Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
            {
                Application.Current.MainPage = lastNavigatedPage;
            });
        }
    }

Than you can make the initialization for your application like this:

        public App()
        {
            InitializeComponent();

            if(!isInitalized)
            {
                InitalizeOnlyOnceClass.Initalize();
                SimpleNavigationLogic.ChangeMainPage(new AwesomeMainPage());
            }
            else
            {
                SimpleNavigationLogic.RestoreMainPage();
            }
        }

Xamarin Forms: Custom Rendererek más assemblyből

A Xamarin Forms a CustomRenderer Attribútumot csak a betöltött referenciák között keresi. Ez azt jelenti, hogyha egy másik projektben található a customrenderer, akkor annak a projektnek kifordított dll-jének már a Forms.Init előtt be kell töltődnie. Jó megoldás lehet, ha létrehozunk egy statikus osztályt, aminek a statikus Initalize metódusára ráhívunk Android esetén az onCreate, iOS esetén pedig az AppDelegate FinishedLauching függvényében.

public static class Initainer
{
    public static void Initalize()
    {
        Console.WriteLine("Initainer is initializing Custom renderers")
    }
}

Xamarin Forms: Auto magasságú ListView

Formsban, ha egy ListView HorizontalOptions, VerticalOptions tulajdonságait Fill-re állítjuk, és a ListView egy Grid sorában van mondjuk, amelynek a Height-je Auto, a lista le fogja foglalni a Grid által kínált összes helyet. Ez azért van, mert a ListView measure-nél még nem tudja hogy milyen listaelemek milyen listaelem formátummal fognak megjelenni benne. Így elveszítjük a lehetőséget a dinamikus megjelenítésre, mert a felületünk szét fog csúszni.

Ennek a problémakörnek megoldásaképpen készítettem egy olyan ListView-t, amely képes a listaelemek tényleges lemért magassága után újraméretezni a lista magasságát. Ez a lista, mindig a benne található elemek magassága méretűre fog nyúlni. Meg lehet még fűszerezni annyival, hogy a Separatorok magasságát is beleszámolja, illetve hogy legyen egy maximálisan engedett magassága is, én ezt nem tettem meg, de jó kiindulási alap.

 public class AutoHeightListView : ListView
    {
        public AutoHeightListView()
        {
        }

        protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            base.OnPropertyChanged(propertyName);

            // ItemsSource változásakor 
            if (propertyName == nameof(ListView.ItemsSource))
            {
                if(ItemsSource != null)
                {
                    // Kikérjük a ListView feltemapltezett celljeit.
                    IEnumerable<PropertyInfo> pInfos = (this as ItemsView<Cell>).GetType().GetRuntimeProperties();
                    var templatedItems = pInfos.FirstOrDefault(info => info.Name == "TemplatedItems");

                    if (templatedItems != null)
                    {
                        var cells = templatedItems.GetValue(this);
                        if(cells is Xamarin.Forms.ITemplatedItemsList<Xamarin.Forms.Cell> cellsCasted)
                        {
                            if(cellsCasted.Count != 0)
                            {
                                ((ViewCell)cellsCasted.Last()).View.SizeChanged += FirstElementHeightChanged;
                            }
                        }
                    }
                }
            }
        }

        double prevHeight = 0;

        private void FirstElementHeightChanged(object sender, EventArgs e)
        {
            SetHeightRequestByCellsHeight();
        }

        /// <summary>
        /// Megméri minden sor Viewcell magasságát, és az egész listview magasságát ezeknek az összegére állítja be.
        /// </summary>
        private void SetHeightRequestByCellsHeight()
        {
            double calculatedHeight = 0;

            // Kikérjük a ListView feltemapltezett celljeit.
            IEnumerable<PropertyInfo> pInfos = (this as ItemsView<Cell>).GetType().GetRuntimeProperties();
            var templatedItems = pInfos.FirstOrDefault(info => info.Name == "TemplatedItems");

            if (templatedItems != null)
            {
                var cells = templatedItems.GetValue(this);
                foreach (ViewCell cell in cells as Xamarin.Forms.ITemplatedItemsList<Xamarin.Forms.Cell>)
                {
                    calculatedHeight += cell.View.Height;
                }
            }

            if (calculatedHeight == prevHeight)
                return;

            prevHeight = calculatedHeight;
            HeightRequest = calculatedHeight;
        }
    }