Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check to see if Image Should Reload When Attached #24023

Merged
merged 4 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue14471.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue14471"
xmlns:ns="clr-namespace:Maui.Controls.Sample.Issues">
<TabBar>
<ShellContent Route="tab1" Title="tab1" ContentTemplate="{DataTemplate ns:Issue14471Tab1Content}"/>
<ShellContent Route="tab2" Title="tab2" ContentTemplate="{DataTemplate ns:Issue14471Tab2Content}"/>
</TabBar>
</Shell>
55 changes: 55 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue14471.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 14471, "Image can disappear when going back to the page", PlatformAffected.Android)]
public partial class Issue14471 : Shell
{
public Issue14471()
{
InitializeComponent();
}
}

public class Issue14471Tab1Content : ContentPage
{

public Issue14471Tab1Content()
{
var image = new Image() { HeightRequest = 100, AutomationId = "image" };
var imageButton = new ImageButton() { HeightRequest = 100, AutomationId = "imageButton" };
Content = new VerticalStackLayout()
{
new Button()
{
AutomationId = "switchToTab2Button",
Text = "Switch to tab 2",
Command = new Command(async () => await Shell.Current.GoToAsync("//tab2"))
},
image,
imageButton
};
LoadImage((source) => image.Source = source);
LoadImage((source) => imageButton.Source = source);
}

async void LoadImage(Action<ImageSource> setImageSource)
{
await Task.Delay(1);
var imageBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAB+FBMVEUAAAA/mUPidDHiLi5Cn0XkNTPmeUrkdUg/m0Q0pEfcpSbwaVdKskg+lUP4zA/iLi3msSHkOjVAmETdJSjtYFE/lkPnRj3sWUs8kkLeqCVIq0fxvhXqUkbVmSjwa1n1yBLepyX1xxP0xRXqUkboST9KukpHpUbuvRrzrhF/ljbwaljuZFM4jELaoSdLtElJrUj1xxP6zwzfqSU4i0HYnydMtUlIqUfywxb60AxZqEXaoifgMCXptR9MtklHpEY2iUHWnSjvvRr70QujkC+pUC/90glMuEnlOjVMt0j70QriLS1LtEnnRj3qUUXfIidOjsxAhcZFo0bjNDH0xxNLr0dIrUdmntVTkMoyfL8jcLBRuErhJyrgKyb4zA/5zg3tYFBBmUTmQTnhMinruBzvvhnxwxZ/st+Ktt5zp9hqota2vtK6y9FemNBblc9HiMiTtMbFtsM6gcPV2r6dwroseLrMrbQrdLGdyKoobKbo3Zh+ynrgVllZulTsXE3rV0pIqUf42UVUo0JyjEHoS0HmsiHRGR/lmRz/1hjqnxjvpRWfwtOhusaz0LRGf7FEfbDVmqHXlJeW0pbXq5bec3fX0nTnzmuJuWvhoFFhm0FtrziBsjaAaDCYWC+uSi6jQS3FsSfLJiTirCOkuCG1KiG+wSC+GBvgyhTszQ64Z77KAAAARXRSTlMAIQRDLyUgCwsE6ebm5ubg2dLR0byXl4FDQzU1NDEuLSUgC+vr6urq6ubb29vb2tra2tG8vLu7u7uXl5eXgYGBgYGBLiUALabIAAABsElEQVQoz12S9VPjQBxHt8VaOA6HE+AOzv1wd7pJk5I2adpCC7RUcHd3d3fXf5PvLkxheD++z+yb7GSRlwD/+Hj/APQCZWxM5M+goF+RMbHK594v+tPoiN1uHxkt+xzt9+R9wnRTZZQpXQ0T5uP1IQxToyOAZiQu5HEpjeA4SWIoksRxNiGC1tRZJ4LNxgHgnU5nJZBDvuDdl8lzQRBsQ+s9PZt7s7Pz8wsL39/DkIfZ4xlB2Gqsq62ta9oxVlVrNZpihFRpGO9fzQw1ms0NDWZz07iGkJmIFH8xxkc3a/WWlubmFkv9AB2SEpDvKxbjidN2faseaNV3zoHXvv7wMODJdkOHAegweAfFPx4G67KluxzottCU9n8CUqXzcIQdXOytAHqXxomvykhEKN9EFutG22p//0rbNvHVxiJywa8yS2KDfV1dfbu31H8jF1RHiTKtWYeHxUvq3bn0pyjCRaiRU6aDO+gb3aEfEeVNsDgm8zzLy9egPa7Qt8TSJdwhjplk06HH43ZNJ3s91KKCHQ5x4sw1fRGYDZ0n1L4FKb9/BP5JLYxToheoFCVxz57PPS8UhhEpLBVeAAAAAElFTkSuQmCC");
setImageSource.Invoke(ImageSource.FromStream(() => new MemoryStream(imageBytes)));
}
}

public class Issue14471Tab2Content : ContentPage
{
public Issue14471Tab2Content()
{
Content = new Button()
{
AutomationId = "switchToTab1Button",
Text = "Switch to tab 1",
Command = new Command(async () => await Shell.Current.GoToAsync("//tab1"))
};
}
}
}
22 changes: 22 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue6625.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue6625">
<ContentPage.Content>
<Grid x:Name="Container"
RowDefinitions="Auto,*">
<Label x:Name="Label"
Grid.Row="0"
LineBreakMode="CharacterWrap"
HorizontalOptions="Center"
VerticalOptions="Start"
Padding="8,16"/>
<Image x:Name="Image"
Grid.Row="1"
BackgroundColor="WhiteSmoke"
Aspect="AspectFit"
VerticalOptions="Center"
HorizontalOptions="Center" />
</Grid>
</ContentPage.Content>
</ContentPage>
51 changes: 51 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue6625.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Linq;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;
using Microsoft.Maui.Platform;

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 6625, "Changing image source on Android causes flicker between images", PlatformAffected.Android)]
public partial class Issue6625 : ContentPage
{
readonly ImageSource[] imageSources =
[
"https://64.media.tumblr.com/14cb5aa197e5d9ca6479d955f68344f0/cb28a32e384437f2-07/s540x810/005508be5849eff8fda5b0ebda23f9fcbede164e.jpg",
"https://64.media.tumblr.com/d66b1709639c23315d26c0a4e977c399/1fb3e31c5e63625d-81/s540x810/2e9a893ec8c1f65a4b9fb8f1264b4fb76914ced8.jpg",
null,
"https://64.media.tumblr.com/e7db300b8248c0a7f4c66884032c9414/8f89498ebe784d1c-4e/s540x810/90835c041547bf2936ee249c5c5e1b4eddd589a3.jpg",
"https://64.media.tumblr.com/fd901060692d2c9ca6f05ddc58e28e5d/2db73c9c5730d87c-e2/s500x750/092c9dc3cff5150acf24bf22a9e61b9719d9ccd6.jpg",
"https://64.media.tumblr.com/c011aa547a45ac6fe47c46beeb290596/eafc76ded66f16f9-dc/s500x750/5ed923ed086296a2bd41cab3106079eb7addc2d0.jpg",
"https://this.is.a.broken.url",
new FontImageSource { FontFamily = "FA", Glyph = "\uf111", Size = 200, Color = Colors.Blue },
new FontImageSource { FontFamily = "FA", Glyph = "\uf192", Size = 200, Color = Colors.Blue },
new FontImageSource { FontFamily = "FA", Glyph = "\uf111", Size = 200, Color = Colors.Blue },
new FontImageSource { FontFamily = "FA", Glyph = "\uf192", Size = 200, Color = Colors.Blue }
];

int imageNo = -1;

public Issue6625()
{
InitializeComponent();
Container.GestureRecognizers.Add(new TapGestureRecognizer { Command = new Command(NextImage) });
NextImage();
}

void NextImage()
{
imageNo = (imageNo + 1) % imageSources.Length;

ImageSource imageSource = imageSources[imageNo];
Label.Text = imageSource switch
{
UriImageSource uri => uri.Uri.ToString(),
FontImageSource font => $"Glyph: {Convert.ToBase64String(font.Glyph.Select(c => (byte)c).ToArray())}",
_ => imageSource?.GetType().Name ?? "null"
};
Image.Source = imageSource;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#if !MACCATALYST // VerifyScreenshot() is not supported on MacCatalyst
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue14471 : _IssuesUITest
{
public Issue14471(TestDevice device) : base(device){ }

public override string Issue => "Image can disappear when going back to the page";

[Test]
[Category(UITestCategories.Image)]
public void ImageDoesntDisappearWhenNavigatingBack()
{
App.WaitForElement("image");
App.Click("switchToTab2Button");
App.WaitForElement("switchToTab1Button");
App.Click("switchToTab1Button");
App.WaitForElement("image");

// The test passes if image is loaded
VerifyScreenshot();
}
}
#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 47 additions & 1 deletion src/Core/src/Handlers/Image/ImageHandler.Android.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System.Threading.Tasks;
using Android.Graphics.Drawables;
using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.Widget;
using Google.Android.Material.Button;

namespace Microsoft.Maui.Handlers
{
public partial class ImageHandler : ViewHandler<IImage, ImageView>
{

protected override ImageView CreatePlatformView()
{
var imageView = new AppCompatImageView(Context);
Expand All @@ -21,9 +22,15 @@ protected override ImageView CreatePlatformView()
return imageView;
}

protected override void ConnectHandler(ImageView platformView)
{
platformView.ViewAttachedToWindow += OnPlatformViewAttachedToWindow;
}

protected override void DisconnectHandler(ImageView platformView)
{
base.DisconnectHandler(platformView);
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;
SourceLoader.Reset();
}

Expand Down Expand Up @@ -54,10 +61,17 @@ await handler
.SourceLoader
.UpdateImageSourceAsync();


// This indicates that the image has finished loading
// So, now if the attached event fires again then we need to see if Glide has cleared the image out
handler.SourceLoader.CheckForImageLoadedOnAttached = true;

// Because this resolves from a task we should validate that the
// handler hasn't been disconnected
if (handler.IsConnected())
{
handler.UpdateValue(nameof(IImage.IsAnimationPlaying));
}
}

public override void PlatformArrange(Graphics.Rect frame)
Expand All @@ -79,12 +93,44 @@ public override void PlatformArrange(Graphics.Rect frame)
base.PlatformArrange(frame);
}

internal static void OnPlatformViewAttachedToWindow(IImageHandler imageHandler)
{

// Glide will automatically clear out the image if the Fragment or Activity is destroyed
// So we want to reload the image here if it's supposed to have an image
if (imageHandler.SourceLoader.CheckForImageLoadedOnAttached &&
imageHandler.PlatformView.Drawable is null &&
imageHandler.VirtualView.Source is not null)
{
imageHandler.SourceLoader.CheckForImageLoadedOnAttached = false;
imageHandler.UpdateValue(nameof(IImage.Source));
}
}

void OnPlatformViewAttachedToWindow(object? sender, View.ViewAttachedToWindowEventArgs e)
{
if (sender is not View platformView)
{
return;
}

if (!this.IsConnected())
{
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;
return;
}

OnPlatformViewAttachedToWindow(this);
}

partial class ImageImageSourcePartSetter
{
public override void SetImageSource(Drawable? platformImage)
{
if (Handler?.PlatformView is not ImageView image)
{
return;
}

image.SetImageDrawable(platformImage);
}
Expand Down
18 changes: 18 additions & 0 deletions src/Core/src/Handlers/ImageButton/ImageButtonHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ protected override void DisconnectHandler(ShapeableImageView platformView)
platformView.FocusChange -= OnFocusChange;
platformView.Click -= OnClick;
platformView.Touch -= OnTouch;
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;

base.DisconnectHandler(platformView);

Expand All @@ -34,6 +35,7 @@ protected override void ConnectHandler(ShapeableImageView platformView)
platformView.FocusChange += OnFocusChange;
platformView.Click += OnClick;
platformView.Touch += OnTouch;
platformView.ViewAttachedToWindow += OnPlatformViewAttachedToWindow;

base.ConnectHandler(platformView);
}
Expand Down Expand Up @@ -90,6 +92,22 @@ void OnClick(object? sender, EventArgs e)
VirtualView?.Clicked();
}

void OnPlatformViewAttachedToWindow(object? sender, View.ViewAttachedToWindowEventArgs e)
{
if (sender is not View platformView)
{
return;
}

if (!this.IsConnected())
{
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;
return;
}

ImageHandler.OnPlatformViewAttachedToWindow(this);
}

partial class ImageButtonImageSourcePartSetter
{
public override void SetImageSource(Drawable? platformImage)
Expand Down
13 changes: 13 additions & 0 deletions src/Core/src/Platform/ImageSourcePartLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public partial class ImageSourcePartLoader
IImageSourceServiceProvider? _imageSourceServiceProvider;
#endif

#if ANDROID
// This is a temporary workaround for Android so that images don't just keep vanishing
// We will have a better fix in the next release that's better integrated with Glide
internal bool CheckForImageLoadedOnAttached { get; set; }
#endif
Comment on lines +31 to +35
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get an issue assigned to something so this does not haunt us forever?


readonly IImageSourcePartSetter _setter;

internal ImageSourceServiceResultManager SourceManager { get; } = new ImageSourceServiceResultManager();
Expand Down Expand Up @@ -77,10 +83,17 @@ public async Task UpdateImageSourceAsync()
#else
await Task.CompletedTask;
#endif

#if ANDROID
CheckForImageLoadedOnAttached = true;
#endif
}
else
{
Setter.SetImageSource(null);
#if ANDROID
CheckForImageLoadedOnAttached = false;
#endif
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Microsoft.Maui.SoftInputExtensions
override Microsoft.Maui.FontSize.Equals(object? obj) -> bool
override Microsoft.Maui.FontSize.GetHashCode() -> int
override Microsoft.Maui.Handlers.EditorHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect frame) -> void
override Microsoft.Maui.Handlers.ImageHandler.ConnectHandler(Android.Widget.ImageView! platformView) -> void
override Microsoft.Maui.Handlers.RadioButtonHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect frame) -> void
override Microsoft.Maui.Handlers.ShapeViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size
override Microsoft.Maui.Layouts.FlexBasis.Equals(object? obj) -> bool
Expand Down
Loading