CollectionView Xamarin How to speed rendering - xamarin

I have a list to show where we show repeat information from SQlite database to user. In this we show basically the product price, name and type. Usually a user will have 10-15 of such records.
Problem is it take 10+ sec to display the list. Now, the architecture of application. On page load of Shell application we fetch the information from web request and store that in SQlite and then Bind the Result from Sqlite StackLayout with Bindable Property. But it took more than 10-11 Sec to display us.
So we change it to Grid with Bindable property and results are same. Though I don't like CollectionView (as I found their select property weird. ) We use CollectionView with dataTemplate as Grid (as we have some tabular information to show) . The results still take same 10+ secs to load up.
i.e. I use Stacklayout, Grid, CollectionView, it take that much time. We are not using the ObservableCollection to bind, but our class elements are all INotifyPropertyChanged. So, I try to disable it but still even when I do not inherity INotifyPropertyChanged and remove "OnPropertyChanged" calls it still take same time.
However, when I reduce the information to just show the name of product (through binding) it reduce to 5 secs. Which is still a lot as a Collection View with just 10 records and showing one Bindable Grid showing result in 5 second is lot more than expected. I understand my other data (which call functions) maybe slowing it down but can we reduce those 5 second somehow?
I cannot share much of class, but some of properties are pretty straight forward Property with INotifyPropertyChange on string or int values.
Here is Code for loading CollectionView
public async Task LoadSearch()
{
CurrentState = LayoutState.Loading;
try
{
var o = await PatientMedicine.FetchPatientMedicine();
if (o)
{
var _sr = (await DBCoreAsync.GetDataAsync<PatientMedicine>())?.OrderBy(x => x.PatientMedID).ToList();
if (_sr != null)
{
SearchResults = _sr;
}
CurrentState = LayoutState.Success;
}
else
{
CurrentState = LayoutState.Error;
}
}
catch (Exception ex)
{
CurrentState = LayoutState.Error;
}
}
Here is Collection itself.
<CollectionView ItemsSource="{Binding SearchResults}" ItemSizingStrategy="MeasureFirstItem">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0,7" ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<BoxView BackgroundColor="{Binding LowStock, Converter={StaticResource BoolToColorReverse}}" CornerRadius="5,0,5,0" />
<Grid Grid.Column="1" Margin="0"
Padding="10,5" BackgroundColor="{StaticResource PrimaryLight2}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.Column="0" Grid.Row="0" Source="{Binding Medicine.RxIcon}" ></Image>
<Label Grid.Column ="1" Grid.Row="0" Style="{StaticResource GeneralLabelBlack}" Text="{Binding Medicine.MedicineName}"></Label>
<Button Grid.Column="2" Grid.Row="0" Command="{Binding Path=BindingContext.NavigateToMedicineReminder, Source={x:Reference Name=MedicineListPageName}}" CommandParameter="{Binding .}"
BackgroundColor="Transparent" VerticalOptions="Center" HorizontalOptions="End" ImageSource="{FontImage FontFamily=FontAwesomeSolid, Glyph=, Color={StaticResource Primary},Size=16}" ></Button>
<Image Grid.Column="0" Grid.Row="1"
Source="{StaticResource ImageSourceIconClockOpenHand}"></Image>
<Label Grid.Column ="1" Grid.Row="1" Text="{Binding RepeatStatusFormatted}"></Label>
<Image Grid.Column="0" Grid.Row="2"
Source="{StaticResource ImageSourceIconStock}"></Image>
<Label Grid.Column ="1" Grid.Row="2" Text="{Binding StockAndRequirement}"></Label>
<Image Grid.Column="0" Grid.Row="3"
Source="{StaticResource ImageSourceIconClock}"></Image>
<Label Grid.Column ="1" Grid.Row="3" Text="{Binding NextReminder}"></Label>
<Label Grid.Column ="0" Grid.Row="4" Grid.ColumnSpan="2" Text="{Binding Medicine.AilmentString}"></Label>
<Label Grid.Column ="0" Grid.ColumnSpan="2" Grid.Row="5" IsVisible="{Binding LowStock}" Text="Stock running low. Order a refill now" HorizontalOptions="End"></Label>
<Button Grid.Column="2" Grid.Row="5" Command="{Binding Path=BindingContext.AddtoCart, Source={x:Reference Name=MedicineListPageName}}" CommandParameter="{Binding .}"
BackgroundColor="Transparent" IsVisible="{Binding LowStock}" VerticalOptions="Center" HorizontalOptions="End" ImageSource="{FontImage FontFamily=FontAwesomeSolid, Glyph=, Color={StaticResource Primary},Size=16}" />
</Grid>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
To add more information: I am okay with Internet load time which is <=1.5 seconds the time I calculate and show here is exclusively for When I set the ViewModel to Values from temporary variable to main variable I did it just to see if DB access is slow or not. (to step it out). I have my control inside a StackLayout that use Xamarin Community Toolkit statelayout.
Also I go through MS's Suggestion on optimizing performance about Compile XAML etc, but since it is new App using Xamarin Forms 5.0 I don't think it is problem as it is already set by default.

Related

Why an Entry control do not receive the input from user in Xamarin Forms?

enter image description hereI want to implement the xamarin essentials map, but with a floating search for the pins that will appear on this map. I'm using a grid to divide the grid's rows and thus use the first row for the map and the second to show a cardview related to the selected pin. I put the search control in row 0 so that it seems to float on the map.
I couldn't get the focus of the search control to work. I don't know if I'm implementing the InputTransparent property correctly.
Any ideas?
<Grid InputTransparent="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="140"/>
</Grid.RowDefinitions>
<maps:Map
x:Name="map"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
MapType="Street"
HasZoomEnabled="True"
IsShowingUser="True"
InputTransparent="False"
MoveToLastRegionOnLayoutChange="false"
>
</maps:Map>
<StackLayout Grid.Row="0" Grid.Column="0" InputTransparent="True">
<Frame
Margin="20,40,20,0"
Padding="0"
BorderColor="{StaticResource BlackColor}"
CornerRadius="8"
HeightRequest="35">
<Grid VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="35" />
</Grid.ColumnDefinitions>
<Image
Grid.Row="0"
Grid.Column="0"
HeightRequest="20"
HorizontalOptions="Center"
Source="ic_search_bar"
VerticalOptions="Center"
WidthRequest="20" />
<controls:CustomEntry
x:Name="entrySearch"
Grid.Row="0"
Grid.Column="1"
FontSize="14"
HeightRequest="35"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Start"
Keyboard="Text"
Placeholder="¿Qué se te antoja hoy?"
PlaceholderColor="#979797"
ReturnCommand="{Binding SearchCommand}"
InputTransparent="False"
ReturnType="Search"
Text="{Binding SearchText}"
TextChanged="entrySearch_TextChanged"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Image
Grid.Row="0"
Grid.Column="2"
HeightRequest="15"
HorizontalOptions="Center"
Source="ic_clear_search.png"
VerticalOptions="Center"
WidthRequest="15">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ClearSearchCommand}" NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</Grid>
</Frame>
</StackLayout>
</Grid>
I try to use the property in many parts of the code
From the docs on InputTransparent
true if neither the element nor its children should receive input and
should, instead, pass inputs to the elements that are visually behind
the current visual element
Your stackLayout is transparent so it’s children, including the Entry, are also transparent
You might be thinking InputTransparent does the opposite of what it actually does. True makes an area effectively invisible to touch; touch goes through it.
If you want an element to be touchable, it has to be "False". BUT all its ancestors must also NOT BLOCK touch.
If you really mean "True", but you have children you want to click on, do this:
<SomeLayoutNameHere InputTransparent="True" CascadeInputTransparent="False" ...>
That allows children to specify whether they can receive touch (InputTransparent=False), or are invisible to touch (=True).
"False" is the default.

Xamarin: First call to InitializeComponent slow

The problem is that the first call to a page's InitializeComponent is very slow. Thus the page transition animation is laggy for the first time. StackLayout with a few Labels and a Grid can take like 70 ms on my phone. I removed almost everything from the page and tried to find a bottle neck but it seems that even an empty grid adds a significant amount of time. So my full page takes something like 180 ms and when it's loaded for the first time there is almost no animation and it looks like a freezing app. Consecutive animations take like 15~20 ms.
Answering your potential questions: Yes, I have XAML compilation enabled. And yes, I've tried Release build but it doesn't help much.
Updated
I have added a button and a static grid to an items detail of default Xamarin application created in Visual Studio.
This is current content of the page:
<StackLayout Spacing="20" Padding="15">
<Label Text="Text:" FontSize="Medium" />
<Label Text="{Binding Text}" FontSize="Small"/>
<Label Text="Description:" FontSize="Medium" />
<Label Text="{Binding Description}" FontSize="Small"/>
<Button Text="{Binding Text}" />
<Grid RowSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="Coins:" FontAttributes="Bold" />
<Label Grid.Row="0" Grid.Column="1" Text="111" HorizontalTextAlignment="End" />
<Label Grid.Row="0" Grid.Column="2" Text="Total coins:" FontAttributes="Bold" />
<Label Grid.Row="0" Grid.Column="3" Text="100000" HorizontalTextAlignment="End"/>
<Label Grid.Row="1" Grid.Column="0" Text="Gold:" FontAttributes="Bold" />
<Label Grid.Row="1" Grid.Column="1" Text="222" HorizontalTextAlignment="End" />
<Label Grid.Row="1" Grid.Column="2" Text="Table gold:" FontAttributes="Bold" />
<Label Grid.Row="1" Grid.Column="3" Text="120 " HorizontalTextAlignment="End"/>
</Grid>
</StackLayout>
And this is how I measure the time:
public partial class ItemDetailPage : ContentPage
{
public ItemDetailPage()
{
var start = DateTime.Now.ToUniversalTime().Millisecond;
InitializeComponent();
var end = DateTime.Now.ToUniversalTime().Millisecond;
Debug.WriteLine($"ItemDetailPage.InitializeComponent: {end - start}");
BindingContext = new ItemDetailViewModel();
}
}

How to set all the item template data into a single view in Xamarin Carousel

I have tried to make all the items in the itemtemplate into a single view as like in the below image, how to achieve this by using Xamarin CarouselView, i am using like this
carousel = new CarouselView();
carousel.BindingContext = this;
carousel.ItemTemplate = itemTemplate;
carousel.SetBinding(CarouselView.ItemsSourceProperty, new Binding(nameof(this.Items), mode: BindingMode.OneWay));
LinearItemsLayout linearItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal);
linearItemsLayout.SnapPointsAlignment = SnapPointsAlignment.Start;
linearItemsLayout.SnapPointsType = SnapPointsType.Mandatory;
carousel.ItemsLayout = linearItemsLayout;
this.Children.Add(carousel,0,1);
Expected UI
The easiest way of doing something like that would be to use the Horizontal CollectionView
CollectionView can display its items in a horizontal list by setting its ItemsLayout property to HorizontalList:
When you check the documents it gives a similar example
<CollectionView ItemsSource="{Binding Monkeys}"
ItemsLayout="HorizontalList">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="140" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold"
LineBreakMode="TailTruncation" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Location}"
LineBreakMode="TailTruncation"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
Alternatively, this layout can also be accomplished by setting the ItemsLayout property to a LinearItemsLayout object, specifying the Horizontal ItemsLayoutOrientation enumeration member as the Orientation property value:
<CollectionView ItemsSource="{Binding Monkeys}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" />
</CollectionView.ItemsLayout>
...
</CollectionView>
This results in a single row list, which grows horizontally as new items are added:

BindableBase not working on CollectionView

I have a CollectionView on my project with the most stable version of Xamarin Forms that supports CollectionView (4.3.0.908675) with the following code below.
<CollectionView x:Name="ScrollButtons"
ItemsSource="{Binding MenuItems}"
SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
HeightRequest="90"
SelectionMode="Single"
SelectionChangedCommand="{Binding MenuItemSelectedCommand}"
BackgroundColor="{DynamicResource BackgroundColorShell}">
<CollectionView.Footer>
<!--HACK to keep showing last item on CollectionView -->
<BoxView BackgroundColor="Transparent" HeightRequest="90" WidthRequest="50"/>
</CollectionView.Footer>
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal"
Span="1" HorizontalItemSpacing="5"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid WidthRequest="90" HeightRequest="90" Padding="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Frame Grid.Column="0"
Grid.Row="0"
BorderColor="Black"
BackgroundColor="{Binding BackgroundColor}"
>
</Frame>
<StackLayout Padding="5" Grid.Row="0" Grid.Column="0">
<Label Text="{Binding Text}"
TextColor="{Binding TextColor}"
LineBreakMode="WordWrap"
FontSize="{StaticResource BaseFontSize}"
x:Name="tileLabel">
</Label>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" VerticalOptions="EndAndExpand">
<Image Source="{Binding SecondaryIconSource}"
HorizontalOptions="Start"
VerticalOptions="EndAndExpand"
WidthRequest="25"
HeightRequest="25"
IsVisible="{Binding IsSecondaryIconVisible}"
/>
<Image Source= "{Binding ImageIcon}"
HorizontalOptions="EndAndExpand"
VerticalOptions="EndAndExpand"
WidthRequest="25"
HeightRequest="25"
x:Name="tileIcon">
</Image>
</StackLayout>
</StackLayout>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
OnMenuSelectedItemCommand
private async Task OnMenuItemSelected()
{
Console.WriteLine("OnMenuItemSelected");
await NavigationService.NavigateAsync($"{SelectedMenuItem.NavigationPath}");
HighlightedMenuItem = SelectedMenuItem;
SelectedMenuItem = null;
}
The CollectionView consists of a collection of BottomMenuItem class which inherits from BindableBase for Prism. My goal is to change the properties of a BottomMenuItem as its selected on the CollectionView. However, the collection view is acting weird and it only changes based on BottomMenuItem that is not currently in the screen. As shown below, it only works on the 5th item and beyond, items that are not initially loaded on the screen.
Any help would be greatly appreciated. Thank you!
I figured it out. The problem is that my code relies on the scrolling to an item in order for a selectedItem to be highlighted. I made an assumption that the bug is based on item cells that are initially loaded are not working but in reality it was items that doesn't need scrolling.
Based on my OnMenuItemSelected, I pass the highlighted item to the next page. Handle that logic with the OnNavigatingTo logic below.
public override async void OnNavigatingTo(INavigationParameters parameters)
{
base.OnNavigatingTo(parameters);
Console.WriteLine("OnNavigatingTo");
HighlightedMenuItem = parameters.GetValue<BottomMenuItem>("highlightedMenuItem");
foreach (var item in MenuItems)
{
item.IsActive = false;
}
if (HighlightedMenuItem != null)
{
Console.WriteLine("OnNavigatingTo HighlightedItem - {0}", HighlightedMenuItem.Text);
HighlightedMenuItem.IsActive = true;
}
}

How to change the style of tab in segment control in xamarin forms

I have a segment control where I'm displaying tabs. But I'm not able to edit the style of the tab in Xamarin Forms. This is the UI I want
This is how I want to display my tabs in the segment control. I'm able to change the tint color, background color, and text color but none of that will get me a tab in this style. This is my current UI
This is XAML code where I implemented the segment control
<controls:SegmentedControl BackgroundColor="White" SelectedTextColor="Black" TintColor="#FFA500" x:Name="SegControl" ValueChanged="Handle_ValueChanged">
<controls:SegmentedControl.Children>
<controls:SegmentedControlOption Text="VENDOR NAME" />
<controls:SegmentedControlOption Text="PRODUCT/SERVICE" />
</controls:SegmentedControl.Children>
</controls:SegmentedControl>
<StackLayout x:Name="SegContent" />
</StackLayout>
<StackLayout Margin="0,30,0,0">
<StackLayout AbsoluteLayout.LayoutBounds=".20,1,1,.1" AbsoluteLayout.LayoutFlags="All" BackgroundColor="White" HorizontalOptions="FillAndExpand" Orientation="Horizontal">
<StackLayout Style="{StaticResource ButtonNavigationBarStackLayoutStyle}" x:Name="stckNear">
<Image Margin="0,10,0,10" x:Name="imgNear" Style="{StaticResource ButtonNavigationBarImageStyle}" />
<Label Text="Near" Style="{StaticResource ButtonNavigationBarLabelStyle}"></Label>
</StackLayout>
<StackLayout Style="{StaticResource ButtonNavigationBarStackLayoutStyle}" x:Name="stckSearch">
<Image Margin="0,10,0,10" x:Name="imgSearch" Style="{StaticResource ButtonNavigationBarImageStyle}" />
<Label Text="Search" Style="{StaticResource ButtonNavigationBarLabelStyle}"></Label>
</StackLayout>
<StackLayout Style="{StaticResource ButtonNavigationBarStackLayoutStyle}" x:Name="stckCart">
<Image Margin="0,10,0,10" x:Name="imgCart" Style="{StaticResource ButtonNavigationBarImageStyle}" />
<Label Text="Cart" Style="{StaticResource ButtonNavigationBarLabelStyle}"></Label>
</StackLayout>
<StackLayout Style="{StaticResource ButtonNavigationBarStackLayoutStyle}" x:Name="stckAccount">
<Image Margin="0,10,0,10" x:Name="imgAccount" Style="{StaticResource ButtonNavigationBarImageStyle}" />
<Label Text="Account" Style="{StaticResource ButtonNavigationBarLabelStyle}"></Label>
</StackLayout>
</StackLayout>
I'm not using any custom renderer for this segment control. Do I have to use a custom renderer for implementing the required UI? If yes how? Any suggestions?
SegmentedControl is not a built in Xamarin.Forms control. There are a few libraries that offer a SegmentedControl, so it would help to know which one you are using.
That said, the library author who created that SegmentedControl also made the platform renderers and so the different look on iOS vs Android is a result of that.
You can, of course, create your own custom renderer, but then why use the library?
Easier to me would be to make a control using Xamarin Forms, for instance you can use a grid that has a first row of two labels ( or Buttons) and a second row of 2 BoxViews that can act as the underline (very short height). Then just add TapGestureRecognizers to each Label (or just use a Buttons and style as needed).
Here's an example using Buttons and BoxViews:
XAML:
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="vBtn"
Text="VENDOR NAME" Clicked="Handle_Clicked"
TextColor="Black"
BackgroundColor="Transparent"
BorderColor="Transparent"
Grid.Row="0"
Grid.Column="0"/>
<Button x:Name="pBtn"
Text="PRODUCT/SERVICE" Clicked="Handle_Clicked"
TextColor="Black"
BackgroundColor="Transparent"
BorderColor="Transparent"
Grid.Row="0"
Grid.Column="1" />
<BoxView x:Name="vBox"
Color="#FFA500" HeightRequest="5"
Grid.Row="1"
Grid.Column="0"/>
<BoxView x:Name="pBox"
Color="Silver" HeightRequest="5"
Grid.Row="1"
Grid.Column="1"/>
</Grid>
Code behind:
void Handle_Clicked(object sender, System.EventArgs e)
{
Button btn = sender as Button;
if (btn.Text == "PRODUCT/SERVICE")
{
vBox.Color = Color.Silver;
pBox.Color = Color.FromHex("#FFA500");
// Do anything else you need to do when the PRODUCT/SERVICE is tapped
}
else
{
vBox.Color = Color.FromHex("#FFA500");
pBox.Color = Color.Silver;
// Do anything else you need to do when the VENDOR NAME is tapped
}
}
No library or custom renderer needed.

Resources