summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Xamarin.Forms.ControlGallery.Android/BrokenImageSourceHandler.cs38
-rw-r--r--Xamarin.Forms.ControlGallery.Android/Properties/AssemblyInfo.cs7
-rw-r--r--Xamarin.Forms.ControlGallery.Android/Resources/drawable/invalidimage.jpg1
-rw-r--r--Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj27
-rw-r--r--Xamarin.Forms.ControlGallery.Android/_38989CustomRenderer.cs2
-rw-r--r--Xamarin.Forms.ControlGallery.Windows/BrokenImageSourceHandler.cs21
-rw-r--r--Xamarin.Forms.ControlGallery.iOS/BrokenImageSourceHandler.cs36
-rw-r--r--Xamarin.Forms.ControlGallery.iOS/Properties/AssemblyInfo.cs6
-rw-r--r--Xamarin.Forms.ControlGallery.iOS/Resources/invalidimage.jpg1
-rw-r--r--Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj2
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla37625.cs4
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs187
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CustomImageRendererErrorHandling.cs40
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ImageLoadingErrorHandling.cs100
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems3
-rw-r--r--Xamarin.Forms.Core/FileImageSource.cs5
-rw-r--r--Xamarin.Forms.Core/UriImageSource.cs5
-rw-r--r--Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs35
-rw-r--r--Xamarin.Forms.Platform.Android/Extensions/JavaObjectExtensions.cs12
-rw-r--r--Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs86
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/FileImageSourceHandler.cs13
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/ImageLoaderSourceHandler.cs14
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs48
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/StreamImagesourceHandler.cs14
-rw-r--r--Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj1
-rw-r--r--Xamarin.Forms.Platform.WinRT/ImageRenderer.cs63
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs90
-rw-r--r--docs/Xamarin.Forms.Core/Xamarin.Forms/FileImageSource.xml17
-rw-r--r--docs/Xamarin.Forms.Core/Xamarin.Forms/UriImageSource.xml17
29 files changed, 696 insertions, 199 deletions
diff --git a/Xamarin.Forms.ControlGallery.Android/BrokenImageSourceHandler.cs b/Xamarin.Forms.ControlGallery.Android/BrokenImageSourceHandler.cs
new file mode 100644
index 00000000..390d77c9
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/BrokenImageSourceHandler.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Android.Content;
+using Android.Graphics;
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.Android;
+using Xamarin.Forms.Controls.Issues;
+using Xamarin.Forms.Platform.Android;
+
+[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
+namespace Xamarin.Forms.ControlGallery.Android
+{
+ public sealed class BrokenImageSourceHandler : IImageSourceHandler
+ {
+ public Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
+ {
+ throw new Exception("Fail");
+ }
+ }
+
+ public class _51173CustomImageRenderer : ImageRenderer
+ {
+ protected override async Task TryUpdateBitmap(Image previous = null)
+ {
+ try
+ {
+ await UpdateBitmap(previous).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ await Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
+ }
+ }
+ }
+}
+
+
diff --git a/Xamarin.Forms.ControlGallery.Android/Properties/AssemblyInfo.cs b/Xamarin.Forms.ControlGallery.Android/Properties/AssemblyInfo.cs
index dc197baf..82e72528 100644
--- a/Xamarin.Forms.ControlGallery.Android/Properties/AssemblyInfo.cs
+++ b/Xamarin.Forms.ControlGallery.Android/Properties/AssemblyInfo.cs
@@ -2,8 +2,11 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
+using Xamarin.Forms.Controls;
+using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
-
+using Xamarin.Forms.ControlGallery.Android;
+
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
@@ -37,6 +40,8 @@ using Xamarin.Forms;
[assembly: Android.App.MetaData("com.google.android.maps.v2.API_KEY", Value = "AIzaSyAdstcJQswxEjzX5YjLaMcu2aRVEBJw39Y")]
[assembly: Xamarin.Forms.ResolutionGroupName ("XamControl")]
+// Deliberately broken image source and handler so we can test handling of image loading errors
+[assembly: ExportImageSourceHandler(typeof(FailImageSource), typeof(BrokenImageSourceHandler))]
#if TEST_LEGACY_RENDERERS
[assembly: ExportRenderer(typeof(Button), typeof(Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer))]
[assembly: ExportRenderer(typeof(Image), typeof(Xamarin.Forms.Platform.Android.ImageRenderer))]
diff --git a/Xamarin.Forms.ControlGallery.Android/Resources/drawable/invalidimage.jpg b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/invalidimage.jpg
new file mode 100644
index 00000000..6c7ebbb3
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/invalidimage.jpg
@@ -0,0 +1 @@
+This is certainly not a real JPEG. \ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
index b606e109..e8011acb 100644
--- a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
+++ b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
@@ -26,6 +26,9 @@
<RestorePackages>true</RestorePackages>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
+ <AndroidTlsProvider>
+ </AndroidTlsProvider>
+ <JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<AndroidKeyStore>True</AndroidKeyStore>
@@ -46,20 +49,27 @@
<AndroidLinkMode>Full</AndroidLinkMode>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
- <BundleAssemblies>False</BundleAssemblies>
<CustomCommands>
<CustomCommands>
- <Command type="AfterBuild" command="xbuild /t:SignAndroidPackage Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj" workingdir="${SolutionDir}" />
+ <Command>
+ <type>AfterBuild</type>
+ <command>xbuild /t:SignAndroidPackage Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj</command>
+ <workingdir>${SolutionDir}</workingdir>
+ </Command>
</CustomCommands>
</CustomCommands>
- <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi;armeabi-v7a;x86</AndroidSupportedAbis>
- <Debugger>Xamarin</Debugger>
- <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
+ <Debugger>.Net (Xamarin)</Debugger>
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>
</NoWarn>
+ <BundleAssemblies>False</BundleAssemblies>
+ <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
+ <AotAssemblies>False</AotAssemblies>
+ <EnableLLVM>False</EnableLLVM>
+ <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
+ <EnableProguard>False</EnableProguard>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -73,8 +83,6 @@
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<AndroidLinkSkip />
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
- <BundleAssemblies>False</BundleAssemblies>
- <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
@@ -97,14 +105,11 @@
<AndroidLinkSkip />
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
- <BundleAssemblies>False</BundleAssemblies>
- <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<Debugger>Xamarin</Debugger>
- <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>
@@ -176,6 +181,7 @@
<Compile Include="CustomRenderers.cs" />
<Compile Include="ColorPicker.cs" />
<Compile Include="_38989CustomRenderer.cs" />
+ <Compile Include="BrokenImageSourceHandler.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\default.css" />
@@ -204,6 +210,7 @@
<AndroidResource Include="Resources\drawable\coffee.png" />
<AndroidResource Include="Resources\drawable\toolbar_close.png" />
<AndroidResource Include="Resources\drawable\test.jpg" />
+ <AndroidResource Include="Resources\drawable\invalidimage.jpg" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\Icon.png" />
diff --git a/Xamarin.Forms.ControlGallery.Android/_38989CustomRenderer.cs b/Xamarin.Forms.ControlGallery.Android/_38989CustomRenderer.cs
index 1cde98a8..ef4aad71 100644
--- a/Xamarin.Forms.ControlGallery.Android/_38989CustomRenderer.cs
+++ b/Xamarin.Forms.ControlGallery.Android/_38989CustomRenderer.cs
@@ -30,6 +30,4 @@ namespace Xamarin.Forms.ControlGallery.Android
return nativeView;
}
}
-
-
} \ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Windows/BrokenImageSourceHandler.cs b/Xamarin.Forms.ControlGallery.Windows/BrokenImageSourceHandler.cs
index ffe999ee..42ee3402 100644
--- a/Xamarin.Forms.ControlGallery.Windows/BrokenImageSourceHandler.cs
+++ b/Xamarin.Forms.ControlGallery.Windows/BrokenImageSourceHandler.cs
@@ -1,16 +1,20 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-
+using Xamarin.Forms.Controls.Issues;
using WImageSource = Windows.UI.Xaml.Media.ImageSource;
#if WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
+using Xamarin.Forms.ControlGallery.WindowsUniversal;
+[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
namespace Xamarin.Forms.ControlGallery.WindowsUniversal
#else
using Xamarin.Forms.Platform.WinRT;
+using Xamarin.Forms.ControlGallery.WinRT;
+[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
namespace Xamarin.Forms.ControlGallery.WinRT
#endif
{
@@ -21,4 +25,19 @@ namespace Xamarin.Forms.ControlGallery.WinRT
throw new Exception("Fail");
}
}
+
+ public class _51173CustomImageRenderer : ImageRenderer
+ {
+ protected override async Task TryUpdateSource()
+ {
+ try
+ {
+ await UpdateSource().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/BrokenImageSourceHandler.cs b/Xamarin.Forms.ControlGallery.iOS/BrokenImageSourceHandler.cs
new file mode 100644
index 00000000..5e58764b
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/BrokenImageSourceHandler.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using UIKit;
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.iOS;
+using Xamarin.Forms.Controls.Issues;
+using Xamarin.Forms.Platform.iOS;
+
+[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
+namespace Xamarin.Forms.ControlGallery.iOS
+{
+ public sealed class BrokenImageSourceHandler : IImageSourceHandler
+ {
+ public Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = new CancellationToken(),
+ float scale = 1)
+ {
+ throw new Exception("Fail");
+ }
+ }
+
+ public class _51173CustomImageRenderer : ImageRenderer
+ {
+ protected override async Task TrySetImage(Image previous = null)
+ {
+ try
+ {
+ await SetImage(previous).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.iOS/Properties/AssemblyInfo.cs b/Xamarin.Forms.ControlGallery.iOS/Properties/AssemblyInfo.cs
index e7f2b400..d11d7e2f 100644
--- a/Xamarin.Forms.ControlGallery.iOS/Properties/AssemblyInfo.cs
+++ b/Xamarin.Forms.ControlGallery.iOS/Properties/AssemblyInfo.cs
@@ -1,6 +1,9 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.iOS;
+using Xamarin.Forms.Controls;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@@ -35,3 +38,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyVersion ("1.0.0.0")]
[assembly: AssemblyFileVersion ("1.0.0.0")]
[assembly: Xamarin.Forms.ResolutionGroupName("XamControl")]
+
+// Deliberately broken image source and handler so we can test handling of image loading errors
+[assembly: ExportImageSourceHandler(typeof(FailImageSource), typeof(BrokenImageSourceHandler))] \ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Resources/invalidimage.jpg b/Xamarin.Forms.ControlGallery.iOS/Resources/invalidimage.jpg
new file mode 100644
index 00000000..6c7ebbb3
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Resources/invalidimage.jpg
@@ -0,0 +1 @@
+This is certainly not a real JPEG. \ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
index d76b5e58..4e2e7706 100644
--- a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
+++ b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
@@ -159,6 +159,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
+ <Compile Include="BrokenImageSourceHandler.cs" />
<Compile Include="BrokenNativeControl.cs" />
<Compile Include="CustomRenderer40251.cs" />
<Compile Include="Main.cs" />
@@ -218,6 +219,7 @@
<Content Include="oasis.jpg" />
<BundleResource Include="oasissmall.jpg" />
<Content Include="coffee%402x.png" />
+ <BundleResource Include="Resources\invalidimage.jpg" />
<Content Include="settings%402x.png" />
<Content Include="menuIcon%402x.png" />
<Content Include="crimsonsmall.jpg" />
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla37625.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla37625.cs
index 06d59c57..83df91e0 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla37625.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla37625.cs
@@ -13,13 +13,13 @@ namespace Xamarin.Forms.Controls.Issues
{
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Bugzilla, 37625, "App crashes when quickly adding/removing Image views (Windows UWP)")]
- public class Bugzilla37625 : TestContentPage // or TestMasterDetailPage, etc ...
+ public class Bugzilla37625 : TestContentPage
{
protected override async void Init ()
{
int retry = 5;
while (retry-- >= 0) {
- var imageUri = new Uri ("https://xamarin.com/content/images/pages/products/platform.png");
+ var imageUri = new Uri ("https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Xamarin.Forms.ControlGallery.Android/Assets/WebImages/XamarinLogo.png");
Content = new Image () { Source = new UriImageSource () { Uri = imageUri }, BackgroundColor = Color.Black, AutomationId = "success" };
await Task.Delay (50);
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs
new file mode 100644
index 00000000..46841122
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Globalization;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Image)]
+#endif
+
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 51173, "ImageRenderer, async void SetImage - Cannot catch exceptions", PlatformAffected.All)]
+ public class Bugzilla51173 : TestContentPage
+ {
+#if UITEST
+ [Test(Description = "Attempt to load an image from a URI which does not exist")]
+ public void Bugzilla51173_NonexistentUri()
+ {
+
+ RunningApp.WaitForElement(q => q.Marked(UriDoesNotExist));
+
+ RunningApp.Tap(UriDoesNotExist);
+ RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
+ RunningApp.WaitForElement(q => q.Marked(NotLoading));
+ }
+
+ [Test(Description = "The IImageSourceHandler itself throws an exception")]
+ public void Bugzilla51173_HandlerThrowsException()
+ {
+ RunningApp.WaitForElement(q => q.Marked(HandlerThrows));
+
+ RunningApp.Tap(HandlerThrows);
+ RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
+ RunningApp.WaitForElement(q => q.Marked(NotLoading));
+ }
+
+ [Test(Description = "The URI is valid, but the image data is not")]
+ public void Bugzilla51173_RealUriWithInvalidImageData()
+ {
+ RunningApp.WaitForElement(q => q.Marked(RealUriInvalidImage));
+
+ RunningApp.Tap(RealUriInvalidImage);
+ RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
+ RunningApp.WaitForElement(q => q.Marked(NotLoading));
+ }
+
+ [Test(Description = "Attempt to load a local image which does not exist")]
+ public void Bugzilla51173_NonexistentImage()
+ {
+ RunningApp.WaitForElement(q => q.Marked(ImageDoesNotExist));
+
+ RunningApp.Tap(ImageDoesNotExist);
+ RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
+ RunningApp.WaitForElement(q => q.Marked(NotLoading));
+ }
+
+ [Test(Description = "Attempt to load a local image which exists, but has corrupt data")]
+ public void Bugzilla51173_InvalidImage()
+ {
+ RunningApp.WaitForElement(q => q.Marked(ImageIsInvalid));
+
+ RunningApp.Tap(ImageIsInvalid);
+ RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
+ RunningApp.WaitForElement(q => q.Marked(NotLoading));
+ }
+
+ [Test(Description = "Load a valid image")]
+ public void Bugzilla51173_ValidImage()
+ {
+ RunningApp.WaitForElement(q => q.Marked(ValidImage));
+ RunningApp.Tap(ValidImage);
+ RunningApp.WaitForElement(q => q.Marked(NotLoading));
+ }
+#endif
+
+ const string ValidImage = "Valid Image";
+ const string ImageDoesNotExist = "Non-existent Image File";
+ const string ImageIsInvalid = "Invalid Image File (bad data)";
+ const string UriDoesNotExist = "Non-existent URI";
+ const string HandlerThrows = "Source Throws Exception";
+ const string RealUriInvalidImage = "Valid URI with invalid image file";
+ const string ErrorLogged = "Error logged";
+
+ const string Loading = "Loading";
+ const string NotLoading = "Not Loading";
+
+ Label _results;
+ Image _image;
+
+ class LoadingConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if ((bool)value)
+ {
+ return Loading;
+ }
+
+ return NotLoading;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ protected override void Init()
+ {
+ _results = new Label { Margin = 10, FontAttributes = FontAttributes.Bold, BackgroundColor = Color.Silver, HorizontalTextAlignment = TextAlignment.Center};
+
+ var errorMessage = new Label();
+
+ Log.Listeners.Add(
+ new DelegateLogListener((c, m) => Device.BeginInvokeOnMainThread(() =>
+ {
+ _results.Text = ErrorLogged;
+ errorMessage.Text = m;
+ })));
+
+ var instructions = new Label
+ {
+ Text =
+ "Pressing the 'Valid Image' button should display an image of a coffee cup. Every other button should cause the messager 'Error logged' to appear at the top of the page."
+ };
+
+ _image = new Image { BackgroundColor = Color.Bisque, HeightRequest = 20 };
+
+ var loadingState = new Label();
+ loadingState.SetBinding(Label.TextProperty, new Binding(Image.IsLoadingProperty.PropertyName, BindingMode.Default, new LoadingConverter()));
+ loadingState.BindingContext = _image;
+
+ var legit = CreateTest(() => _image.Source = ImageSource.FromFile("coffee.png"), ValidImage);
+
+ var invalidImageFileName = CreateTest(() => _image.Source = ImageSource.FromFile("fake.png"), ImageDoesNotExist);
+
+ var invalidImageFile = CreateTest(() => _image.Source = ImageSource.FromFile("invalidimage.jpg"), ImageIsInvalid);
+
+ var fakeUri = CreateTest(() => _image.Source = ImageSource.FromUri(new Uri("http://not.real")), UriDoesNotExist);
+
+ var crashImage = CreateTest(() => _image.Source = new FailImageSource(), HandlerThrows);
+
+ var uriInvalidImageData =
+ CreateTest(() => _image.Source = ImageSource.FromUri(new Uri("https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg")),
+ RealUriInvalidImage);
+
+ Content = new StackLayout
+ {
+ Margin = new Thickness(5, 20, 5, 0),
+ Children =
+ {
+ _image,
+ instructions,
+ legit,
+ invalidImageFileName,
+ invalidImageFile,
+ fakeUri,
+ crashImage,
+ uriInvalidImageData,
+ _results,
+ loadingState,
+ errorMessage
+ }
+ };
+ }
+
+ Button CreateTest(Action imageLoadAction, string title)
+ {
+ var button = new Button { Text = title };
+
+ button.Clicked += (sender, args) =>
+ {
+ _results.Text = "";
+ imageLoadAction();
+ };
+
+ return button;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CustomImageRendererErrorHandling.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CustomImageRendererErrorHandling.cs
new file mode 100644
index 00000000..3e4ea56a
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CustomImageRendererErrorHandling.cs
@@ -0,0 +1,40 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.None, 51173, "Custom ImageRenderer error handling demo", PlatformAffected.All)]
+ public class CustomImageRendererErrorHandling : TestContentPage
+ {
+ protected override void Init()
+ {
+ var layout = new StackLayout { Margin = new Thickness(5, 40, 5, 0) };
+
+ var instructions = new Label
+ {
+ Text =
+ @"
+Click 'Update Image Source'; it will update the coffee image with an image source which will throw an exception.
+Instead of just logging an error, the custom renderer will display an alert dialog about the error.
+"
+ };
+
+ var image = new _51173Image { Source = "coffee.png" };
+
+ var button = new Button { Text = "Update Image Source" };
+
+ button.Clicked += (sender, args) => image.Source = new FailImageSource();
+
+ layout.Children.Add(instructions);
+ layout.Children.Add(image);
+ layout.Children.Add(button);
+
+ Content = layout;
+ }
+ }
+
+ // custom image type for demonstrating custom error handling in a custom renderer
+ [Preserve(AllMembers = true)]
+ public class _51173Image : Image { }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ImageLoadingErrorHandling.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ImageLoadingErrorHandling.cs
deleted file mode 100644
index b2607c71..00000000
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/ImageLoadingErrorHandling.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System;
-using Xamarin.Forms.CustomAttributes;
-using Xamarin.Forms.Internals;
-
-namespace Xamarin.Forms.Controls
-{
- [Preserve(AllMembers = true)]
- [Issue(IssueTracker.None, 0, "Image Loading Error Handling", PlatformAffected.WinRT | PlatformAffected.UWP)]
- public class ImageLoadingErrorHandling : TestContentPage
- {
- protected override void Init()
- {
- Log.Listeners.Add(
- new DelegateLogListener((c, m) => Device.BeginInvokeOnMainThread(() => DisplayAlert(c, m, "Cool, Thanks"))));
-
- var image = new Image() {BackgroundColor = Color.White};
-
- Grid legit = CreateTest(() => image.Source = ImageSource.FromFile("coffee.png"),
- "Valid Image",
- "Clicking this button should load an image at the top of the page.",
- Color.Silver);
-
- Grid invalidImageFileName = CreateTest(() => image.Source = ImageSource.FromFile("fake.png"),
- "Non-existent Image File",
- "Clicking this button should display an alert dialog with an error that the image failed to load.");
-
- Grid invalidImageFile = CreateTest(() => image.Source = ImageSource.FromFile("invalidimage.jpg"),
- "Invalid Image File (bad data)",
- "Clicking this button should display an alert dialog with an error that the image failed to load.",
- Color.Silver);
-
- Grid fakeUri = CreateTest(() => image.Source = ImageSource.FromUri(new Uri("http://not.real")),
- "Non-existent URI",
- (Device.RuntimePlatform == Device.UWP || Device.RuntimePlatform == Device.WinRT) && Device.Idiom == TargetIdiom.Phone
- ? "Clicking this button should display an alert dialog. The error message should include the text 'NotFound'."
- : "Clicking this button should display an alert dialog. The error message should include the text 'the server name or address could not be resolved'.");
-
- // This used to crash the app with an uncatchable error; need to make sure it's not still doing that
- Grid crashImage = CreateTest(() => image.Source = new FailImageSource(),
- "Source Throws Exception",
- "Clicking this button should display an alert dialog. The error messages hould include the test 'error updating image source'.",
- Color.Silver);
-
- Grid uriInvalidImageData =
- CreateTest(() => image.Source = ImageSource.FromUri(new Uri("https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg")),
- "Valid URI with invalid image file",
- "Clicking this button should display an alert dialog. The error message should include the text 'UriImageSourceHandler could not load https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg'");
-
- Content = new StackLayout
- {
- Children =
- {
- image,
- legit,
- invalidImageFileName,
- invalidImageFile,
- fakeUri,
- crashImage,
- uriInvalidImageData
- }
- };
- }
-
- static Grid CreateTest(Action imageLoadAction, string title, string instructions, Color? backgroundColor = null)
- {
- var button = new Button { Text = "Test" };
-
- button.Clicked += (sender, args) => imageLoadAction();
-
- var titleLabel = new Label
- {
- Text = title,
- FontAttributes = FontAttributes.Bold
- };
-
- var label = new Label
- {
- Text = instructions
- };
-
- var grid = new Grid
- {
- ColumnDefinitions =
- new ColumnDefinitionCollection { new ColumnDefinition(), new ColumnDefinition(), new ColumnDefinition() },
- RowDefinitions = new RowDefinitionCollection { new RowDefinition { Height = 80 } }
- };
-
- if (backgroundColor.HasValue)
- {
- grid.BackgroundColor = backgroundColor.Value;
- }
-
- grid.AddChild(titleLabel, 0, 0);
- grid.AddChild(label, 1, 0);
- grid.AddChild(button, 2, 0);
-
- return grid;
- }
- }
-} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
index 73f1bc2f..f33dc6d7 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
@@ -200,6 +200,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34561.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34727.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComplexListView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)CustomImageRendererErrorHandling.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DefaultColorToggleTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla38416.xaml.cs">
<DependentUpon>Bugzilla38416.xaml</DependentUpon>
@@ -253,7 +254,7 @@
<Compile Include="$(MSBuildThisFileDirectory)TestPages\ScreenshotConditionalApp.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41842.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42277.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)ImageLoadingErrorHandling.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla51173.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla33561.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43214.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42602.cs" />
diff --git a/Xamarin.Forms.Core/FileImageSource.cs b/Xamarin.Forms.Core/FileImageSource.cs
index 9e1d1e73..f2f34719 100644
--- a/Xamarin.Forms.Core/FileImageSource.cs
+++ b/Xamarin.Forms.Core/FileImageSource.cs
@@ -18,6 +18,11 @@ namespace Xamarin.Forms
return Task.FromResult(false);
}
+ public override string ToString()
+ {
+ return $"File: {File}";
+ }
+
public static implicit operator FileImageSource(string file)
{
return (FileImageSource)FromFile(file);
diff --git a/Xamarin.Forms.Core/UriImageSource.cs b/Xamarin.Forms.Core/UriImageSource.cs
index 7d1a9eb2..5e0db2cb 100644
--- a/Xamarin.Forms.Core/UriImageSource.cs
+++ b/Xamarin.Forms.Core/UriImageSource.cs
@@ -92,6 +92,11 @@ namespace Xamarin.Forms
return stream;
}
+ public override string ToString()
+ {
+ return $"Uri: {Uri}";
+ }
+
static string GetCacheKey(Uri uri)
{
return Device.PlatformServices.GetMD5Hash(uri.AbsoluteUri);
diff --git a/Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs b/Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs
index f60acb7e..d0cd2045 100644
--- a/Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs
+++ b/Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs
@@ -1,28 +1,33 @@
using System;
using System.Threading.Tasks;
using Android.Graphics;
-using Java.IO;
using AImageView = Android.Widget.ImageView;
namespace Xamarin.Forms.Platform.Android
{
internal static class ImageViewExtensions
{
- public static async void UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
+ // TODO hartez 2017/04/07 09:33:03 Review this again, not sure it's handling the transition from previousImage to 'null' newImage correctly
+ public static async Task UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
{
+ if (imageView == null || imageView.IsDisposed())
+ return;
+
if (Device.IsInvokeRequired)
throw new InvalidOperationException("Image Bitmap must not be updated from background thread");
if (previousImage != null && Equals(previousImage.Source, newImage.Source))
return;
- ((IImageController)newImage).SetIsLoading(true);
+ var imageController = newImage as IImageController;
+
+ imageController?.SetIsLoading(true);
- (imageView as IImageRendererController).SkipInvalidate();
+ (imageView as IImageRendererController)?.SkipInvalidate();
imageView.SetImageResource(global::Android.Resource.Color.Transparent);
- ImageSource source = newImage.Source;
+ ImageSource source = newImage?.Source;
Bitmap bitmap = null;
IImageSourceHandler handler;
@@ -34,10 +39,7 @@ namespace Xamarin.Forms.Platform.Android
}
catch (TaskCanceledException)
{
- }
- catch (IOException ex)
- {
- Internals.Log.Warning("Xamarin.Forms.Platform.Android.ImageRenderer", "Error updating bitmap: {0}", ex);
+ imageController?.SetIsLoading(false);
}
}
@@ -47,14 +49,19 @@ namespace Xamarin.Forms.Platform.Android
return;
}
- if (bitmap == null && source is FileImageSource)
- imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
- else
- imageView.SetImageBitmap(bitmap);
+ if (!imageView.IsDisposed())
+ {
+ if (bitmap == null && source is FileImageSource)
+ imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
+ else
+ {
+ imageView.SetImageBitmap(bitmap);
+ }
+ }
bitmap?.Dispose();
- ((IImageController)newImage).SetIsLoading(false);
+ imageController?.SetIsLoading(false);
((IVisualElementController)newImage).NativeSizeChanged();
}
}
diff --git a/Xamarin.Forms.Platform.Android/Extensions/JavaObjectExtensions.cs b/Xamarin.Forms.Platform.Android/Extensions/JavaObjectExtensions.cs
new file mode 100644
index 00000000..007d8759
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/Extensions/JavaObjectExtensions.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal static class JavaObjectExtensions
+ {
+ public static bool IsDisposed(this Java.Lang.Object obj)
+ {
+ return obj.Handle == IntPtr.Zero;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs
index 845ec449..5ca1bd20 100644
--- a/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs
@@ -1,8 +1,10 @@
using System;
using System.ComponentModel;
+using System.Threading.Tasks;
using AImageView = Android.Widget.ImageView;
using AView = Android.Views.View;
using Android.Views;
+using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android.FastRenderers
{
@@ -17,30 +19,32 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
protected override void Dispose(bool disposing)
{
- base.Dispose(disposing);
-
if (_disposed)
return;
_disposed = true;
- if (!disposing)
- return;
-
- if (_visualElementTracker != null)
+ if (disposing)
{
- _visualElementTracker.Dispose();
- _visualElementTracker = null;
+ if (_visualElementTracker != null)
+ {
+ _visualElementTracker.Dispose();
+ _visualElementTracker = null;
+ }
+
+ if (_visualElementRenderer != null)
+ {
+ _visualElementRenderer.Dispose();
+ _visualElementRenderer = null;
+ }
+
+ if (_element != null)
+ {
+ _element.PropertyChanged -= OnElementPropertyChanged;
+ }
}
- if (_visualElementRenderer != null)
- {
- _visualElementRenderer.Dispose();
- _visualElementRenderer = null;
- }
-
- if (_element != null)
- _element.PropertyChanged -= OnElementPropertyChanged;
+ base.Dispose(disposing);
}
public override void Invalidate()
@@ -54,9 +58,9 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
base.Invalidate();
}
- protected virtual void OnElementChanged(ElementChangedEventArgs<Image> e)
+ protected virtual async void OnElementChanged(ElementChangedEventArgs<Image> e)
{
- this.UpdateBitmap(e.NewElement, e.OldElement);
+ await TryUpdateBitmap(e.OldElement);
UpdateAspect();
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
@@ -77,6 +81,11 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
{
+ if (_disposed)
+ {
+ return new SizeRequest();
+ }
+
Measure(widthConstraint, heightConstraint);
return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize());
}
@@ -114,7 +123,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
_element?.SendViewInitialized(Control);
}
-
+
void IVisualElementRenderer.SetLabelFor(int? id)
{
if (_defaultLabelFor == null)
@@ -144,18 +153,53 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
{
}
- protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ protected virtual async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Image.SourceProperty.PropertyName)
- this.UpdateBitmap(_element);
+ await TryUpdateBitmap();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
UpdateAspect();
ElementPropertyChanged?.Invoke(this, e);
}
+ protected virtual async Task TryUpdateBitmap(Image previous = null)
+ {
+ // By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
+ // the application; a custom renderer can override this method and handle exceptions from
+ // UpdateBitmap differently if it wants to
+
+ try
+ {
+ await UpdateBitmap(previous);
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+ }
+ finally
+ {
+ ((IImageController)_element)?.SetIsLoading(false);
+ }
+ }
+
+ protected async Task UpdateBitmap(Image previous = null)
+ {
+ if (_element == null || _disposed)
+ {
+ return;
+ }
+
+ await Control.UpdateBitmap(_element, previous);
+ }
+
void UpdateAspect()
{
+ if (_element == null || _disposed)
+ {
+ return;
+ }
+
ScaleType type = _element.Aspect.ToScaleType();
SetScaleType(type);
}
diff --git a/Xamarin.Forms.Platform.Android/Renderers/FileImageSourceHandler.cs b/Xamarin.Forms.Platform.Android/Renderers/FileImageSourceHandler.cs
index 93124fef..95d66fb7 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/FileImageSourceHandler.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/FileImageSourceHandler.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
+using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
@@ -17,10 +18,18 @@ namespace Xamarin.Forms.Platform.Android
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
{
string file = ((FileImageSource)imagesource).File;
+ Bitmap bitmap;
if (File.Exists (file))
- return !DecodeSynchronously ? (await BitmapFactory.DecodeFileAsync (file).ConfigureAwait (false)) : BitmapFactory.DecodeFile (file);
+ bitmap = !DecodeSynchronously ? (await BitmapFactory.DecodeFileAsync (file).ConfigureAwait (false)) : BitmapFactory.DecodeFile (file);
else
- return !DecodeSynchronously ? (await context.Resources.GetBitmapAsync (file).ConfigureAwait (false)) : context.Resources.GetBitmap (file);
+ bitmap = !DecodeSynchronously ? (await context.Resources.GetBitmapAsync (file).ConfigureAwait (false)) : context.Resources.GetBitmap (file);
+
+ if (bitmap == null)
+ {
+ Log.Warning(nameof(FileImageSourceHandler), "Could not find image or image file was invalid: {0}", imagesource);
+ }
+
+ return bitmap;
}
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ImageLoaderSourceHandler.cs b/Xamarin.Forms.Platform.Android/Renderers/ImageLoaderSourceHandler.cs
index 8d0ae3d9..b22ca485 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/ImageLoaderSourceHandler.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/ImageLoaderSourceHandler.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
+using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
@@ -11,12 +12,19 @@ namespace Xamarin.Forms.Platform.Android
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
{
var imageLoader = imagesource as UriImageSource;
- if (imageLoader != null && imageLoader.Uri != null)
+ Bitmap bitmap = null;
+ if (imageLoader?.Uri != null)
{
using (Stream imageStream = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false))
- return await BitmapFactory.DecodeStreamAsync(imageStream).ConfigureAwait(false);
+ bitmap = await BitmapFactory.DecodeStreamAsync(imageStream).ConfigureAwait(false);
}
- return null;
+
+ if (bitmap == null)
+ {
+ Log.Warning(nameof(ImageLoaderSourceHandler), "Could not retrieve image or image data was invalid: {0}", imageLoader);
+ }
+
+ return bitmap;
}
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
index 57937d89..43dac1fe 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs
@@ -1,10 +1,10 @@
using System;
using System.ComponentModel;
+using System.Threading.Tasks;
using Android.Graphics;
using Android.Views;
using AImageView = Android.Widget.ImageView;
using Xamarin.Forms.Internals;
-using static Xamarin.Forms.Platform.Android.ImageViewExtensions;
namespace Xamarin.Forms.Platform.Android
{
@@ -20,7 +20,6 @@ namespace Xamarin.Forms.Platform.Android
public ImageRenderer()
{
- System.Diagnostics.Debug.WriteLine(">>>>> Old Image Renderer");
AutoPackage = false;
}
@@ -39,7 +38,7 @@ namespace Xamarin.Forms.Platform.Android
return new FormsImageView(Context);
}
- protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
+ protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
@@ -50,28 +49,63 @@ namespace Xamarin.Forms.Platform.Android
}
_motionEventHelper.UpdateElement(e.NewElement);
-
- Control.UpdateBitmap(e.NewElement, e.OldElement);
+
+ await TryUpdateBitmap(e.OldElement);
UpdateAspect();
}
- protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Image.SourceProperty.PropertyName)
- Control.UpdateBitmap(Element);
+ await TryUpdateBitmap();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
UpdateAspect();
}
void UpdateAspect()
{
+ if (Element == null || Control == null || Control.IsDisposed())
+ {
+ return;
+ }
+
AImageView.ScaleType type = Element.Aspect.ToScaleType();
Control.SetScaleType(type);
}
+ protected virtual async Task TryUpdateBitmap(Image previous = null)
+ {
+ // By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
+ // the application; a custom renderer can override this method and handle exceptions from
+ // UpdateBitmap differently if it wants to
+
+ try
+ {
+ await UpdateBitmap(previous);
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+ }
+ finally
+ {
+ ((IImageController)Element)?.SetIsLoading(false);
+ }
+ }
+
+ protected async Task UpdateBitmap(Image previous = null)
+ {
+ if (Element == null || Control == null || Control.IsDisposed())
+ {
+ return;
+ }
+
+ await Control.UpdateBitmap(Element, previous);
+ }
+
public override bool OnTouchEvent(MotionEvent e)
{
if (base.OnTouchEvent(e))
diff --git a/Xamarin.Forms.Platform.Android/Renderers/StreamImagesourceHandler.cs b/Xamarin.Forms.Platform.Android/Renderers/StreamImagesourceHandler.cs
index 3128995d..a46e3abc 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/StreamImagesourceHandler.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/StreamImagesourceHandler.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
+using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
@@ -11,12 +12,19 @@ namespace Xamarin.Forms.Platform.Android
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
{
var streamsource = imagesource as StreamImageSource;
- if (streamsource != null && streamsource.Stream != null)
+ Bitmap bitmap = null;
+ if (streamsource?.Stream != null)
{
using (Stream stream = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false))
- return await BitmapFactory.DecodeStreamAsync(stream).ConfigureAwait(false);
+ bitmap = await BitmapFactory.DecodeStreamAsync(stream).ConfigureAwait(false);
}
- return null;
+
+ if (bitmap == null)
+ {
+ Log.Warning(nameof(ImageLoaderSourceHandler), "Image data was invalid: {0}", streamsource);
+ }
+
+ return bitmap;
}
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
index bf5344ff..5100b873 100644
--- a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
+++ b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
@@ -103,6 +103,7 @@
<Compile Include="AndroidApplicationLifecycleState.cs" />
<Compile Include="AndroidTitleBarVisibility.cs" />
<Compile Include="AppCompat\FrameRenderer.cs" />
+ <Compile Include="Extensions\JavaObjectExtensions.cs" />
<Compile Include="FastRenderers\AccessibilityProvider.cs" />
<Compile Include="FastRenderers\ButtonRenderer.cs" />
<Compile Include="AppCompat\FormsViewPager.cs" />
diff --git a/Xamarin.Forms.Platform.WinRT/ImageRenderer.cs b/Xamarin.Forms.Platform.WinRT/ImageRenderer.cs
index 8798b20e..56026d18 100644
--- a/Xamarin.Forms.Platform.WinRT/ImageRenderer.cs
+++ b/Xamarin.Forms.Platform.WinRT/ImageRenderer.cs
@@ -17,6 +17,7 @@ namespace Xamarin.Forms.Platform.WinRT
public class ImageRenderer : ViewRenderer<Image, Windows.UI.Xaml.Controls.Image>
{
bool _measured;
+ bool _disposed;
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
@@ -32,10 +33,20 @@ namespace Xamarin.Forms.Platform.WinRT
protected override void Dispose(bool disposing)
{
- if (Control != null)
+ if (_disposed)
{
- Control.ImageOpened -= OnImageOpened;
- Control.ImageFailed -= OnImageFailed;
+ return;
+ }
+
+ _disposed = true;
+
+ if (disposing)
+ {
+ if (Control != null)
+ {
+ Control.ImageOpened -= OnImageOpened;
+ Control.ImageFailed -= OnImageFailed;
+ }
}
base.Dispose(disposing);
@@ -55,7 +66,7 @@ namespace Xamarin.Forms.Platform.WinRT
SetNativeControl(image);
}
- await UpdateSource();
+ await TryUpdateSource();
UpdateAspect();
}
}
@@ -65,7 +76,7 @@ namespace Xamarin.Forms.Platform.WinRT
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Image.SourceProperty.PropertyName)
- await UpdateSource();
+ await TryUpdateSource();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
UpdateAspect();
}
@@ -94,7 +105,7 @@ namespace Xamarin.Forms.Platform.WinRT
Element?.SetIsLoading(false);
}
- void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
+ protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
{
Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}" );
Element?.SetIsLoading(false);
@@ -107,6 +118,11 @@ namespace Xamarin.Forms.Platform.WinRT
void UpdateAspect()
{
+ if (_disposed || Element == null || Control == null)
+ {
+ return;
+ }
+
Control.Stretch = GetStretch(Element.Aspect);
if (Element.Aspect == Aspect.AspectFill) // Then Center Crop
{
@@ -120,15 +136,40 @@ namespace Xamarin.Forms.Platform.WinRT
}
}
- async Task UpdateSource()
+ protected virtual async Task TryUpdateSource()
{
+ // By default we'll just catch and log any exceptions thrown by UpdateSource so we don't bring down
+ // the application; a custom renderer can override this method and handle exceptions from
+ // UpdateSource differently if it wants to
+
+ try
+ {
+ await UpdateSource().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+ }
+ finally
+ {
+ ((IImageController)Element)?.SetIsLoading(false);
+ }
+ }
+
+ protected async Task UpdateSource()
+ {
+ if (_disposed || Element == null || Control == null)
+ {
+ return;
+ }
+
Element.SetIsLoading(true);
ImageSource source = Element.Source;
IImageSourceHandler handler;
if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
{
- Windows.UI.Xaml.Media.ImageSource imagesource = null;
+ Windows.UI.Xaml.Media.ImageSource imagesource;
try
{
@@ -138,10 +179,6 @@ namespace Xamarin.Forms.Platform.WinRT
{
imagesource = null;
}
- catch (Exception ex)
- {
- Log.Warning("Image Loading", $"Error updating image source: {ex}");
- }
// In the time it takes to await the imagesource, some zippy little app
// might have disposed of this Image already.
@@ -155,7 +192,7 @@ namespace Xamarin.Forms.Platform.WinRT
else
{
Control.Source = null;
- Element?.SetIsLoading(false);
+ Element.SetIsLoading(false);
}
}
}
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs
index 6e11d96c..6bc28b4a 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs
@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using Foundation;
using UIKit;
+using Xamarin.Forms.Internals;
using RectangleF = CoreGraphics.CGRect;
namespace Xamarin.Forms.Platform.iOS
@@ -30,8 +31,6 @@ namespace Xamarin.Forms.Platform.iOS
{
bool _isDisposed;
- IElementController ElementController => Element as IElementController;
-
protected override void Dispose(bool disposing)
{
if (_isDisposed)
@@ -43,7 +42,6 @@ namespace Xamarin.Forms.Platform.iOS
if (Control != null && (oldUIImage = Control.Image) != null)
{
oldUIImage.Dispose();
- oldUIImage = null;
}
}
@@ -52,7 +50,7 @@ namespace Xamarin.Forms.Platform.iOS
base.Dispose(disposing);
}
- protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
+ protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
{
if (Control == null)
{
@@ -65,18 +63,18 @@ namespace Xamarin.Forms.Platform.iOS
if (e.NewElement != null)
{
SetAspect();
- SetImage(e.OldElement);
+ await TrySetImage(e.OldElement);
SetOpacity();
}
base.OnElementChanged(e);
}
- protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Image.SourceProperty.PropertyName)
- SetImage();
+ await TrySetImage();
else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName)
SetOpacity();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
@@ -85,11 +83,41 @@ namespace Xamarin.Forms.Platform.iOS
void SetAspect()
{
+ if (_isDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
Control.ContentMode = Element.Aspect.ToUIViewContentMode();
}
- async void SetImage(Image oldElement = null)
+ protected virtual async Task TrySetImage(Image previous = null)
{
+ // By default we'll just catch and log any exceptions thrown by SetImage so they don't bring down
+ // the application; a custom renderer can override this method and handle exceptions from
+ // SetImage differently if it wants to
+
+ try
+ {
+ await SetImage(previous).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+ }
+ finally
+ {
+ ((IImageController)Element)?.SetIsLoading(false);
+ }
+ }
+
+ protected async Task SetImage(Image oldElement = null)
+ {
+ if (_isDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
var source = Element.Source;
if (oldElement != null)
@@ -108,7 +136,8 @@ namespace Xamarin.Forms.Platform.iOS
Element.SetIsLoading(true);
- if (source != null && (handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
+ if (source != null &&
+ (handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
{
UIImage uiimage;
try
@@ -120,22 +149,30 @@ namespace Xamarin.Forms.Platform.iOS
uiimage = null;
}
+ if (_isDisposed)
+ return;
+
var imageView = Control;
if (imageView != null)
imageView.Image = uiimage;
- if (!_isDisposed)
- ((IVisualElementController)Element).NativeSizeChanged();
+ ((IVisualElementController)Element).NativeSizeChanged();
}
else
+ {
Control.Image = null;
+ }
- if (!_isDisposed)
- Element.SetIsLoading(false);
+ Element.SetIsLoading(false);
}
void SetOpacity()
{
+ if (_isDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
Control.Opaque = Element.IsOpaque;
}
}
@@ -151,12 +188,15 @@ namespace Xamarin.Forms.Platform.iOS
{
UIImage image = null;
var filesource = imagesource as FileImageSource;
- if (filesource != null)
+ var file = filesource?.File;
+ if (!string.IsNullOrEmpty(file))
+ image = File.Exists(file) ? new UIImage(file) : UIImage.FromBundle(file);
+
+ if (image == null)
{
- var file = filesource.File;
- if (!string.IsNullOrEmpty(file))
- image = File.Exists(file) ? new UIImage(file) : UIImage.FromBundle(file);
+ Log.Warning(nameof(FileImageSourceHandler), "Could not find image: {0}", imagesource);
}
+
return Task.FromResult(image);
}
}
@@ -167,7 +207,7 @@ namespace Xamarin.Forms.Platform.iOS
{
UIImage image = null;
var streamsource = imagesource as StreamImageSource;
- if (streamsource != null && streamsource.Stream != null)
+ if (streamsource?.Stream != null)
{
using (var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false))
{
@@ -175,6 +215,12 @@ namespace Xamarin.Forms.Platform.iOS
image = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
}
}
+
+ if (image == null)
+ {
+ Log.Warning(nameof(StreamImagesourceHandler), "Could not load image: {0}", streamsource);
+ }
+
return image;
}
}
@@ -185,7 +231,7 @@ namespace Xamarin.Forms.Platform.iOS
{
UIImage image = null;
var imageLoader = imagesource as UriImageSource;
- if (imageLoader != null && imageLoader.Uri != null)
+ if (imageLoader?.Uri != null)
{
using (var streamImage = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false))
{
@@ -193,6 +239,12 @@ namespace Xamarin.Forms.Platform.iOS
image = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
}
}
+
+ if (image == null)
+ {
+ Log.Warning(nameof(ImageLoaderSourceHandler), "Could not load image: {0}", imageLoader);
+ }
+
return image;
}
}
diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/FileImageSource.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/FileImageSource.xml
index 61fe4dcc..cdd27028 100644
--- a/docs/Xamarin.Forms.Core/Xamarin.Forms/FileImageSource.xml
+++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/FileImageSource.xml
@@ -183,5 +183,22 @@
<remarks>To be added.</remarks>
</Docs>
</Member>
+ <Member MemberName="ToString">
+ <MemberSignature Language="C#" Value="public override string ToString ();" />
+ <MemberSignature Language="ILAsm" Value=".method public hidebysig virtual instance string ToString() cil managed" />
+ <MemberType>Method</MemberType>
+ <AssemblyInfo>
+ <AssemblyVersion>2.0.0.0</AssemblyVersion>
+ </AssemblyInfo>
+ <ReturnValue>
+ <ReturnType>System.String</ReturnType>
+ </ReturnValue>
+ <Parameters />
+ <Docs>
+ <summary>To be added.</summary>
+ <returns>To be added.</returns>
+ <remarks>To be added.</remarks>
+ </Docs>
+ </Member>
</Members>
</Type>
diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms/UriImageSource.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms/UriImageSource.xml
index 889b02df..303717b8 100644
--- a/docs/Xamarin.Forms.Core/Xamarin.Forms/UriImageSource.xml
+++ b/docs/Xamarin.Forms.Core/Xamarin.Forms/UriImageSource.xml
@@ -114,6 +114,23 @@
<remarks>To be added.</remarks>
</Docs>
</Member>
+ <Member MemberName="ToString">
+ <MemberSignature Language="C#" Value="public override string ToString ();" />
+ <MemberSignature Language="ILAsm" Value=".method public hidebysig virtual instance string ToString() cil managed" />
+ <MemberType>Method</MemberType>
+ <AssemblyInfo>
+ <AssemblyVersion>2.0.0.0</AssemblyVersion>
+ </AssemblyInfo>
+ <ReturnValue>
+ <ReturnType>System.String</ReturnType>
+ </ReturnValue>
+ <Parameters />
+ <Docs>
+ <summary>To be added.</summary>
+ <returns>To be added.</returns>
+ <remarks>To be added.</remarks>
+ </Docs>
+ </Member>
<Member MemberName="Uri">
<MemberSignature Language="C#" Value="public Uri Uri { get; set; }" />
<MemberSignature Language="ILAsm" Value=".property instance class System.Uri Uri" />