Search code examples
xamarin.formscarousel

Images not shown in CarouselView on first load


The scenario is, a content page is opened with a carousel displaying multiple images. The images are bound as an observable collection of ImageSource and they're fine When the page is displayed, I can see the carousels empty.

I have tried with two different CarouselView. The one that xamarin brings by default and the one from CardsView nuget library. With both of them the result is the same.

The page shows the carousel views without the image, but with the right amount of items (i.e: I can see that the observable collection has 3 items).

I suspect it must be some kind of racing condition because as soon as I force a hot reload of the view while debuggin (ie: I save the XAML and the view reloads in the device) I can see the carousels with the images displayed properly.

To reproduce it I have also added the images separately outside the carousel to prove that they're there. I've also added both carousels to the view. This is the result on the first load:

first load

and after I save the xaml (without doing anything on it at all) and the view refreshes, this is what I see, and it's correct.

after hot reload of xaml

Notice how in the first load, the 3 images are displayed correctly outside the carousel, but not inside any of the carousels, although there is no problem if what I add in the carousel is text.

Any idea where may be the issue? Is it a racing condition? Or is it a problem with both different carousels?

This is my code

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:viewModels="clr-namespace:Sasw.AforoPass.ViewModels;assembly=Sasw.AforoPass"
             xmlns:panCardView="clr-namespace:PanCardView;assembly=PanCardView"
             xmlns:controls="clr-namespace:PanCardView.Controls;assembly=PanCardView"
             mc:Ignorable="d"
             x:Class="Sasw.AforoPass.Views.MainPage" 
             x:DataType="viewModels:MainViewModel">
    <ContentPage.Content>
        <StackLayout>
            <StackLayout.Resources>
                <ResourceDictionary>
                    <ResourceDictionary.MergedDictionaries>
                        <ResourceDictionary Source="/Styles/Styles.xaml" />
                    </ResourceDictionary.MergedDictionaries>
                </ResourceDictionary>
            </StackLayout.Resources>

            <CarouselView BackgroundColor="White"
                          x:Name="Carousel"
                          ItemsSource="{Binding Images}">
                <CarouselView.ItemTemplate>
                    <DataTemplate>
                        <StackLayout 
                            HorizontalOptions="CenterAndExpand" 
                            VerticalOptions="CenterAndExpand">
                            <Image Source="{Binding .}"></Image>
                            <Label Text="image should be here"></Label>
                        </StackLayout>
                    </DataTemplate>
                </CarouselView.ItemTemplate>
            </CarouselView>


            <panCardView:CarouselView 
                ItemsSource="{Binding Images}"
                BackgroundColor="{StaticResource ColorPrimary}"
                HorizontalOptions="FillAndExpand"
                VerticalOptions="FillAndExpand">
                <panCardView:CarouselView.ItemTemplate>
                    <DataTemplate>
                        <StackLayout 
                            HorizontalOptions="CenterAndExpand" 
                            VerticalOptions="CenterAndExpand">
                            <Image Source="{Binding .}"></Image>
                            <Label Text="image should be here"></Label>
                        </StackLayout>
                    </DataTemplate>
                </panCardView:CarouselView.ItemTemplate>

                <controls:LeftArrowControl />
                <controls:RightArrowControl />
                <controls:IndicatorsControl ToFadeDuration="1500"/>
            </panCardView:CarouselView>
            <Image WidthRequest="20" HeightRequest="20" Source ="{Binding Images[0]}"></Image>
            <Image WidthRequest="20" HeightRequest="20"  Source ="{Binding Images[1]}"></Image>
            <Image WidthRequest="20" HeightRequest="20"  Source ="{Binding Images[2]}"></Image>

            <Button Text="Close"></Button>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

and the collection bound is:

public ObservableCollection<ImageSource> Images
{
    get => _images;
    set
    {
        _images = value;
        OnPropertyChanged();
    }
}

UPDATE 1: I've just edited the whole question because at first I though the problem could be related with Rg.Plugins.Popup because it was happening within a popup. But I've tried the same with a normal content page and the problem remains, so it's definitely something to do with the carousel.


UPDATE 2 I keep investigating. So far, the problem seems to be with image streams in carousels, either the Xamarin carousel or the CardView's library carousel (which have different mechanisms).

I've tried adding options and height and width request to the image that accesses the stream without luck

<!--in DataTemplate within the carousel-->
<Image Source="{Binding .}" HeightRequest="100"
   WidthRequest="100"
   VerticalOptions="FillAndExpand" 
   HorizontalOptions="FillAndExpand"></Image>
<Label Text="image should be here"></Label>

UPDATE 3:

Thanks to Jack Hua for his comment and repo, I updated it to reflect the issue I have and managed to reproduce it at this specific commit here

Basically I have taken the same repo, added a QRCoder library to quickly generate images as byte[] at runtime and added the image sources from stream.

public Model() {

    Images = new ObservableCollection<ImageSource>();

    var imageOne = GetQrImageAsBytes();
    var imageTwo = GetQrImageAsBytes();
    var imageThree = GetQrImageAsBytes();

    // This works
    //Images.Add(ImageSource.FromFile("logo.jpg"));
    //Images.Add(ImageSource.FromFile("sample.jpg"));
    //Images.Add(ImageSource.FromFile("ttt.png"));

    Images.Add(ImageSource.FromStream(() => new MemoryStream(imageOne)));
    Images.Add(ImageSource.FromStream(() => new MemoryStream(imageTwo)));
    Images.Add(ImageSource.FromStream(() => new MemoryStream(imageThree)));
}

private byte[] GetQrImageAsBytes()
{
    var randomText = Guid.NewGuid().ToString();
    var qrGenerator = new QRCodeGenerator();
    var qrCodeData = qrGenerator.CreateQrCode(randomText, QRCodeGenerator.ECCLevel.L);
    var qRCode = new PngByteQRCode(qrCodeData);
    var qrCodeBytes = qRCode.GetGraphic(20);
    return qrCodeBytes;
}

UPDATE 4:

I found the issue, I had to try a few times to make sure this really was the problem, but it certainly is.

Re-Sharper suggests me to make a change in ItemsSource="{Binding Images}" as it does not recognize Images. So I follow Re-Sharper recommendation and let it add a x:DataType="app265:Model" in xaml's ContentPage tag. That causes the problem. I don't really know why, but I'll sleep better tonight :)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:app265="clr-namespace:App265;assembly=App265"
             mc:Ignorable="d"
             x:Class="App265.MainPage" 
             x:DataType="app265:Model"><!--this line that Re-Sharper adds causes the issue-->

Is this a Xamarin bug? or does it make sense to you? An explanation to why this x:DataType breaks things but only on the first load would be chosen as the right answer.


Solution

  • x:DataType="ImageSource"
    

    Add this to your carousel view DataTemplate. It should solve your problem. It seems there some issues with BindignContext detecting, so you should "help" to detect it yourself.