Image Source and Caching
Asked Answered
F

5

13

I use the following code to show images from a webserver:

   <Image Source="{Binding Url}" />

The image gets automatically downloaded, and I assume there is also some caching based on the Url.

My problem is, that when the app is offline, the assumably cached images are not shown.

Is there any way to change the caching behavior, so that images are also loaded when there is no network available? Pointers to documentation regarding the caching would be very helpful as well.

Fraktur answered 23/6, 2013 at 13:17 Comment(0)
A
25

BitmapImage automatically caches remote images by default. It's best used in conjunction with CreateOptions="BackgroundCreation" for the best performance.

<Image Height="100" Width="100" Margin="12,0,9,0">
  <Image.Source>
    <BitmapImage UriSource="{Binding ImgURL}" CreateOptions="BackgroundCreation"/>
  </Image.Source>
</Image>

This MSDN blog post, old but still relevant, lists and explains all the CreationOptions and that caching is automatic in most modes.

I use these options to display many news items with images and it works well. I can load the list of articles, exit the app and turn Flight Mode to On, then start a new instance of the app and the images still load up.

Manual Approach

If you'd like to control the caching yourself and cache HTTPS resources then there are few good examples...

Animate answered 24/6, 2013 at 14:40 Comment(4)
thank you, that blog post is great! I realized that http urls are cached, while https are not for whatever reasons. Do you have an idea why?Fraktur
that's possibly a legacy Silverlight reason - ie. don't cache HTTPS content in the browser. If you want to cache HTTPS resource, you'll have to write that yourself - I can add a couple of links to some good examples if you like?Animate
that silverlight argument sounds very reasonable, and regarding the examples, yes i like :)Fraktur
The links make this answer perfect :) Here are some additions: github.com/artem-zinnatullin/jet-image-loaderTenorite
H
5

I have got a solution for you. It is JetImageLoader, I created it for application, where we need to load, cache and show big amount of logos, icons and so on.

It can be used as binding converter, so you should not even change your code! Just update your XAMLs!

Please, check out samples in repository, you'll love it ;)

Features:

  • Caching on disk
  • Caching in memory
  • Fully asynchronous
  • Available as binding converter or programmatically from your code
  • Fully open source, fork and improve it!

Here is the example:

<Image Source="{Binding ImageUrl, Converter={StaticResource MyAppJetImageLoaderConverter}}"/>
Hackler answered 12/10, 2013 at 22:36 Comment(5)
What do you mean with "non-leaking"? Memory leaks? If so, memory cache based on WeakRefDictionary, so GC can collect objects when he wants, no memory leaks :) You can write me on GitHub if you got questionsHackler
Yep, but what about nulling unused images? There's famous leak in WP. I made this workaround some time ago: simca.codeplex.com. More details about leak: #9550480 #17572325 #13817069 #18127527France
Hm, I analyzed my wp8 app where we got huge amount of big and small images loading via JetImageLoader with memory profiler and I saw that after closing page with images GC cleaned memory to amount ~= memory amount what was before opening this page. May be this bug was fixed in wp8?Hackler
Anyway, thank you for that issue, I'll try to check it in nearest timeHackler
I believe, its wp7 issue. I didnt test wp8 :)France
R
1

I don't think there is a build in way to do it, but you could save the images in IsolatedStorage and use a Converter that checks the internet availability and either returns the online or offline url.

A quick search yielded this which might be exactly what you are looking for (it's compatible to Windows Phone 7 and might not be the best solution for Windows Phone 8)

Recorder answered 23/6, 2013 at 14:15 Comment(1)
thanks, I already saw that link, I really thought such a basic feature would be built in the control...Fraktur
K
1

You could also use FFImageLoading (https://github.com/molinch/FFImageLoading/)

Features

  • Xamarin.iOS (min iOS 7), Xamarin.Android (min Android 4), Xamarin.Forms and Windows (WinRT, UWP) support
  • Configurable disk and memory caching
  • Deduplication of similar download/load requests
  • Error and loading placeholders support
  • Images can be automatically downsampled to specified size (less memory usage)
  • WebP support
  • Image loading Fade-In animations support
  • Can retry image downloads (RetryCount, RetryDelay)
  • On Android transparency is disabled by default (configurable). Saves 50% of memory
  • Transformations support
    • BlurredTransformation
    • CircleTransformation, RoundedTransformation, CornersTransformation
    • ColorSpaceTransformation, GrayscaleTransformation, SepiaTransformation
    • FlipTransformation
    • Supports custom transformations (native platform ITransformation implementations)

It's just as simple as:

<ff:MvxCachedImage Name="image"
    VerticalAlignment="Stretch" 
    HorizontalAlignment="Stretch"
    LoadingPlaceholder="loading.png"
    ErrorPlaceholder="error.png"
    RetryCount="3"
    RetryDelay="250"
    DownsampleHeight="300"
    ImagePath="http://lorempixel.com/output/city-q-c-600-600-5.jpg">
</ff: MvxCachedImage >

Sample projects here: https://github.com/molinch/FFImageLoading/tree/master/samples/

Kibbutz answered 24/11, 2015 at 17:13 Comment(3)
the line CacheDuration="30" has the same effect than doingCacheDuration = TimeSpan.FromDays(30) in C#, right?Toenail
Hi Thanks for excellent sample. I am using binding for insert image in to the ffimageloading in android. If I set cache duration to specific time for that ffimage, is it working fine?Donia
FFImage is now depreciated, you should use MvxCachedImageView. I'll update my answer. It works on all platforms. You should set CacheDuration property which is of Timestamp type.Kibbutz
F
1

My solution: (save image from web to local storage and bind saved image to page)

XAML

<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
    <DataTemplate>
        <!--Some code removed-->
        <Image Source="{Binding Img_Thumb.Result}" />
    </DataTemplate>
</ListView.ItemTemplate>

DataModel

public class DataModel_ListOfEvents
{
    public DataModel_ListOfEvents(String img_thumb)
    {
        this.Img_Thumb = new NotifyTaskCompletion<string>(JsonCached.ImageFromCache2(img_thumb));
    }
    public NotifyTaskCompletion<string> Img_Thumb { get; private set; }
}

public sealed class SampleData_ListOfEvents
{
    private static SampleData_ListOfEvents _sampleDataSource = new SampleData_ListOfEvents();

    private ObservableCollection<DataModel_ListOfEvents> _items = new ObservableCollection<DataModel_ListOfEvents>();
    public ObservableCollection<DataModel_ListOfEvents> Items { get { return this._items; } }
}

Magic

public class JsonCached
{
    public static async Task<string> ImageFromCache2(string path)
    {
        int ru = path.IndexOf(".ru") + 4;// TODO: .com .net .org
        string new_path = path.Substring(ru).Replace("/", "\\");

        StorageFolder localFolder = ApplicationData.Current.LocalFolder;
        try
        {
            Stream p = await localFolder.OpenStreamForReadAsync(new_path);
            p.Dispose();
            System.Diagnostics.Debug.WriteLine("From cache");
            return localFolder.Path + "\\" + new_path;
        }
        catch (FileNotFoundException)
        {

        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine("{0}", e.Message);
        }

        StorageFile storageFile = await localFolder.CreateFileAsync(new_path, CreationCollisionOption.OpenIfExists);

        Uri Website = new Uri(path);
        HttpClient http = new HttpClient();
        // TODO: Check connection. Return message on fail.
        System.Diagnostics.Debug.WriteLine("Downloading started");
        byte[] image_from_web_as_bytes = await http.GetByteArrayAsync(Website);

        MakeFolders(localFolder, path.Substring(ru));

        Stream outputStream = await storageFile.OpenStreamForWriteAsync();
        outputStream.Write(image_from_web_as_bytes, 0, image_from_web_as_bytes.Length);
        outputStream.Position = 0;

        System.Diagnostics.Debug.WriteLine("Write file done {0}", outputStream.Length);

        outputStream.Dispose();
        return localFolder.Path + "\\" + new_path;
    }

    private static async void MakeFolders(StorageFolder localFolder, string path)
    {
        //pics/thumbnail/050/197/50197442.jpg
        int slash = path.IndexOf("/");
        if (slash <= 0) // -1 Not found
            return;

        string new_path = path.Substring(0, slash);
        StorageFolder opened_folder = await localFolder.CreateFolderAsync(new_path, CreationCollisionOption.OpenIfExists);
        string very_new_path = path.Remove(0, new_path.Length + 1);
        MakeFolders(opened_folder, very_new_path);
    }
}

NotifyTaskCompletion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace App2.NotifyTask
{
    public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
    {
        public NotifyTaskCompletion(Task<TResult> task)
        {
            Task = task;
            if (!task.IsCompleted)
            {
                var _ = WatchTaskAsync(task);
            }
        }
        private async Task WatchTaskAsync(Task task)
        {
            try
            {
                await task;
            }
            catch
            {
            }
            var propertyChanged = PropertyChanged;
            if (propertyChanged == null)
                return;
            propertyChanged(this, new PropertyChangedEventArgs("Status"));
            propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
            propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
            if (task.IsCanceled)
            {
                propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
            }
            else if (task.IsFaulted)
            {
                propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
                propertyChanged(this, new PropertyChangedEventArgs("Exception"));
                propertyChanged(this,
                  new PropertyChangedEventArgs("InnerException"));
                propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
            }
            else
            {
                propertyChanged(this,
                  new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
                propertyChanged(this, new PropertyChangedEventArgs("Result"));
            }
        }
        public Task<TResult> Task { get; private set; }
        public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } }
        public TaskStatus Status { get { return Task.Status; } }
        public bool IsCompleted { get { return Task.IsCompleted; } }
        public bool IsNotCompleted { get { return !Task.IsCompleted; } }
        public bool IsSuccessfullyCompleted { get { return Task.Status == TaskStatus.RanToCompletion; } }
        public bool IsCanceled { get { return Task.IsCanceled; } }
        public bool IsFaulted { get { return Task.IsFaulted; } }
        public AggregateException Exception { get { return Task.Exception; } }
        public Exception InnerException { get { return (Exception == null) ? null : Exception.InnerException; } }
        public string ErrorMessage { get { return (InnerException == null) ? null : InnerException.Message; } }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
Fontenot answered 15/6, 2016 at 6:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.