How to properly indicate page is loading?

Brian_SnoddyBrian_Snoddy ✭✭USUniversity ✭✭

Hello,

I have tried many different ways of doing this and none are consistent across all platforms. I have my testpage.cs that loads 400 labels in a grid which takes several seconds and am in need of a way to indicate that it is busy loading. I will most likely have other pages in my app that need the same ability.

Could someone please show me with-in my sample code how to display a loading page/popup while my page is loading? If there isn't a clean way to display a loading page an activity indicator would be ok but I would prefer a loading page. My main issue is finding a solution that displays from the moment my 'click me' button is pressed until the page is actually loaded. I haven't been able to come up with anything that displays the entire time on all 3 platforms.

Below is a sample with all my previous attempts removed.

**MainPage.cs
**
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks;
        using Xamarin.Forms;

        namespace App1
        {
            public class MainPage : ContentPage
            {
                public MainPage()
                {
                    this.Title = "Main Page";
                    Button button = new Button();
                    button.Text = "Click ME";
                    button.Clicked += button_Clicked;
                    this.Content = button;
                }
                async void button_Clicked(object sender, EventArgs e)
                {
                    await this.Navigation.PushAsync(new TestPage());
                }
            }

        }


**TestPage.cs  **

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Xamarin.Forms;
    using System.Windows.Input;

    namespace App1
    {
        public class TestPage : ContentPage
        {
            public TestPage()
            {
                LoadPage();
            }

            private void LoadPage()
            {
                Label Header = new Label
                {
                    Text = "Test Page",
                    Font = Font.SystemFontOfSize(25, FontAttributes.Bold),
                    HorizontalOptions = LayoutOptions.Center
                };
                this.Title = "Test Page";
                this.Padding = new Thickness(5, Device.OnPlatform(40, 20, 20), 5, 5);
                StackLayout layout = new StackLayout
                {
                    VerticalOptions = LayoutOptions.FillAndExpand,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Orientation = StackOrientation.Vertical,
                };
                    RowDefinition RD = new RowDefinition { Height = GridLength.Auto };
                    ColumnDefinition CD1 = new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) };
                    ColumnDefinition CD2 = new ColumnDefinition { Width = GridLength.Auto };
                    ColumnDefinitionCollection CDC = new ColumnDefinitionCollection();
                    CDC.Add(CD1);
                    CDC.Add(CD2);
                    RowDefinitionCollection RDC = new RowDefinitionCollection();
                    for (int i = 0; i < 200; i++)
                    {
                        RDC.Add(RD);
                    }
                    var grid = new Grid
                    {
                        ColumnSpacing = 10,
                        IsClippedToBounds = false,
                        RowDefinitions = RDC,
                        ColumnDefinitions = CDC
                    };
                    for (int i = 0; i < 200; i++)
                    {
                            Label labelReq = new Label();
                            labelReq.Text = "Label " + i;
                            if (i == 2)
                                labelReq.Text = "Some really long text that should go to the next line.";
                            Label labeldate = new Label();
                            labeldate.Text = "__/__/__";
                            grid.Children.Add(labelReq, 0, i);
                            grid.Children.Add(labeldate, 1, i);
                    }
                    layout.Children.Add(grid);

                var scroll = new ScrollView();

                var stack = new StackLayout
                {
                    Children = { Header,  layout }
                };
                this.Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 5);
                scroll.Content = stack;
                this.Content = scroll;
            }
        }
    }

**Loading.cs**

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace App1
{
    public class Loading : ContentPage
    {

        protected override void OnAppearing()
        {
            base.OnAppearing();
            IsBusy = true;
        }
        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            IsBusy = false;
        }
        public Loading()
        {
            Label Header = new Label
            {
                Text = "Loading",
                Font = Font.SystemFontOfSize(25, FontAttributes.Bold),
                HorizontalOptions = LayoutOptions.Center
            };
            this.Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 5);
            this.Content = Header;
        }
    }

}

Posts

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    You should probably use a ListView for a case like this because it virtualizes (i.e., doesn't create views for all the rows that can't be seen). Aside from that suggestion, I have an example that shows a loading indicator while a background task is running. You can find it here (view) and here (view model).

  • Brian_SnoddyBrian_Snoddy ✭✭ USUniversity ✭✭

    Thank you for the code example. I had originally tried something similar having my own viewmodel and setting the isloading property before and after the button click. I have also tried to set the isloading before and after my loadpage function. Either way the android device would only show the indicator a split second before the page was loaded. There was still a few seconds of inactivity.

    I will look into maybe seeing if a listview would work in my case but these pages are suppose to resemble a report so I am not sure this will work in my case. It might for some of my larger pages though.

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    The difference is that in my case the long task runs in the background and doesn't block the UI thread. Your code blocks the UI thread, which prevents your intermediate changes from being visible. UI updates aren't made visible until you return to the UI thread.

    You could try this:

    public async void LoadPage()
    {
        ShowLoadingInProgressView();
        await Task.Yield();
        CreateControls();
        RemoveLoadingInProgressView();
    }
    

    The await Task.Yield() line allows returns to the UI thread, allowing it to make your changes visible. Then the rest of your function executes after that is complete.

    This would still block the UI thread for a while, which is still a bad thing. I think it's worth trying to make this not take so long. The ListView is one possible way to do that.

  • Brian_SnoddyBrian_Snoddy ✭✭ USUniversity ✭✭

    I gave that a try but I still got similar results with the activity indicator not showing the entire time the page was loading. A listview isn't going to have the appearance I am looking for. The example code I posted is just a quick sample of what I am trying to do. My actual pages are laid out a little differently but still have the same issue of loading many labels on a single screen in a grid.
    I may be able to use the tableview would this give me better performance than adding labels to a grid?

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    I'm pretty sure ListViews can be customized quite a bit. I'm not yet convinced you can't do what you want with it. You may just have to use some custom cells.

    TableView is nearly the same thing as ListView, but it has more support for sections I think. That could also be a valid solution because TableView also virtualizes the views.

  • Brian_SnoddyBrian_Snoddy ✭✭ USUniversity ✭✭

    Awesome. Thank you again for the suggestions. I have created a listview with a custom cell that works great for my larger pages and it loads much faster. I will look at the tableview for the more complex layouts.

    Not a big deal but do you know if there is any way to remove the lines in between each cell on the listview?

  • CraigDunnCraigDunn Xamurai USXamarin Team Xamurai

    I made a 'recipe' for hiding row dividers since it's asked so often.

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    @CraigDunn‌, that's awesome! One question about implementation:

        protected override void OnElementChanged (ElementChangedEventArgs<ListView> e)
        {
            base.OnElementChanged (e);
    
            var tableView = Control as UITableView;
    
            tableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
        }
    

    In Jason Smith's Evolve presentation he covered OnElementChanged using a subclassed renderer as an example (~17:00), and in his implementation he checked first whether Control was null. Is there a reason that's not necessary in your recipe?

  • CraigDunnCraigDunn Xamurai USXamarin Team Xamurai

    Undocumented bug in my code? I pulled this out of one of the first samples I ever wrote for Xamarin.Forms (when it was OnModelChanged :) ) I've updated it for best-practice :)

    Thanks for the reminder!

  • crecre ✭✭ USUniversity ✭✭

    @CraigDunn‌: In the Android example of your recipe you are using the INTERNAL ListViewRenderer. When can we expect the Android ListViewRenderer to be public available?

  • Brian_SnoddyBrian_Snoddy ✭✭ USUniversity ✭✭

    @CraigDunn: I copied your example into my project all is well with the iOS version.

    The android version gives me an error for ListViewRenderer 'Xamarin.Forms.Platform.Android.ListViewRenderer is inaccessible due to its protection level'

    I also get does not exist in the current context for 'OnElementChanged(e)' and 'Control'. Any ideas of what I am missing?

            using Android.Widget;
            using Xamarin.Forms;
            using Xamarin.Forms.Platform.Android;
            using System;
    
            [assembly: ExportRenderer(typeof(TMMobileScout.Models.MenuListView), typeof(TMMobileScout.Android.MenuListViewRenderer))]
            namespace TMMobileScout.Android
            {
                public class MenuListViewRenderer : ListViewRenderer 
                {
                    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
                    {
                        base.OnElementChanged(e);
    
                        if (Control == null)
                            return;
    
                        var listView = Control as global::Android.Widget.ListView;
                        listView.DividerHeight = 0;
                    }
                }
            }
    
  • CraigDunnCraigDunn Xamurai USXamarin Team Xamurai

    Yes - my bad - it's been pointed out that the Android ListViewRenderer isn't public yet; I was working with TableViewRenderer which is otherwise very similar... we need to fix this on our end first :-\

    Apologies!

  • Brian_SnoddyBrian_Snoddy ✭✭ USUniversity ✭✭

    Ok Thanks. I am new to Xamarin how will I know when it is updated?

  • CraigDunnCraigDunn Xamurai USXamarin Team Xamurai

    This forum gets a thread pinned to the start for each new release of Xamarin.Forms; NuGet will also prompt you in the IDE (Xamarin Studio is more visible) when new versions are available for download).

Sign In or Register to comment.