summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorE.Z. Hart <hartez@users.noreply.github.com>2017-04-25 12:16:25 -0600
committerRui Marinho <me@ruimarinho.net>2017-04-25 19:16:25 +0100
commitcdc405512844671bc3b2c8bd28f583036e5530a2 (patch)
tree881d68b4a26eecbbc77e76b86b3f322cd41b3e3d
parent9631ec2d8bbac8b837955af238f322c1023af097 (diff)
downloadxamarin-forms-cdc405512844671bc3b2c8bd28f583036e5530a2.tar.gz
xamarin-forms-cdc405512844671bc3b2c8bd28f583036e5530a2.tar.bz2
xamarin-forms-cdc405512844671bc3b2c8bd28f583036e5530a2.zip
Better error handling for image loading errors on iOS/Android (#849)
* First run at removing async void image update methods Consistent error logging and IsLoading on Android,iOS,UWP Move error logging into image handlers for better messages Add demo of custom ImageRenderer error handling Update docs Make the test smaller so the results don't get pushed offscreen Fix namespace error * Update error handling for fast image renderer * Update 37625 test to use image we control * Add java disposed check to avoid ObjectDisposedException in async operations * Add disposed checks to legacy renderer; null check element before SetIsLoading * Check disposed on GetDesiredSize for fast renderer Use local disposed member where possible for disposed check * Check for disposal after async handlers in iOS * Add disposal checks after async methods in Windows * Reset linker settings on project; reduce redundant casts in ImageViewExtensions
-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" />