Load Image from URL with a Referrer Header - xamarin.forms

I need to download static images from my CDN and display them in my Xamarin.Forms view. I'm new to Xamarin specifically and I'm having a hard time figuring out how to load an image from a byte array pulled from a HTTP request.
I have the following XAML in my View:
<ListView ItemsSource="{Binding Images}">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding .}">
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my code behind I'm fetching the image and trying to bind to a Collection of ImageSource instances.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
public ObservableCollection<ImageSource> Images { get; set; } = new ObservableCollection<ImageSource>();
protected override async void OnAppearing()
{
base.OnAppearing();
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Referer", "https://foo.bar/");
var response = await client.GetAsync("https://foo.bar/library/5.jpg");
byte[] image = await response.Content.ReadAsByteArrayAsync();
var imageSource = ImageSource.FromStream(() => new MemoryStream(image));
this.Images.Add(imageSource);
}
}
When I do this the Image within the StackLayout is empty. I know I can instead just bind to a URL and the ImageSource can download it for me but I'm required to pass a Referrer Header. I can't see anyway for me to force the XAML Image view to allow custom headers, nor can I see a way to do it if I instantiate an instance of ImageSource in my code behind.
When I read the documentation on ImageSource it sounds like it only supports URI, File, Stream and App Resources to load images from. Since I had an array of bytes I wrapped it in a MemoryStream and called ImageSource.FromStream. Nothing loads when the app runs though - the ImageCell is empty.
I know the image was downloaded. I converted the byte[] to a Base64 string then dropped it into Base64 to Image Decoder. The image loads no problem so I know it's being downloaded properly. What I don't understand is why I can't get the image to render on a ImageSource.
I've also tried to just use a normal Image control and have the same problem.
<ScrollView>
<ContentView>
<StackLayout BindableLayout.ItemsSource="{Binding Images}" Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"></Image>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ContentView>
</ScrollView>
Am I doing something completely wrong here or is dynamic downloading of images from the internet with special Headers something I can't do in Xamarin.Forms?

Try to add this.BindingContext = this; in page constructor.
public MainPage()
{
InitializeComponent();
this.BindingContext = this;
}

Related

Xamarin Forms picker on ios in Release Mode does not show text

I have a Xamarin Forms Applikation with a picker in a page defined like this:
<Picker ItemsSource="{Binding AvailableCultures}"
SelectedItem="{Binding SelectedCulture, Mode=TwoWay}"
ItemDisplayBinding="{Binding DisplayName}" />
The relevant parts in the viewmodel are:
private CultureInfo selectedCulture = CultureHelper.CurrentCulture;
public CultureInfo SelectedCulture
{
get => selectedCulture;
set
{
if(value == null)
return;
if(selectedCulture == value)
return;
selectedCulture = value;
settingsFacade.DefaultCulture = selectedCulture.Name;
CultureHelper.CurrentCulture = selectedCulture;
RaisePropertyChanged();
}
}
public ObservableCollection<CultureInfo> AvailableCultures { get; }
private async Task LoadAvailableCulturesAsync()
{
await dialogService.ShowLoadingDialogAsync();
CultureInfo.GetCultures(CultureTypes.AllCultures).OrderBy(x => x.Name).ToList().ForEach(AvailableCultures.Add);
SelectedCulture = AvailableCultures.First(x => x.Name == settingsFacade.DefaultCulture);
await dialogService.HideLoadingDialogAsync();
}
This works without issues on the simulator when I debug. It also works without problems on Android. But when I deploy the application on iOS the texts are empty.
I can scroll through them and select new ones and the value is changed. I thought it might be a problem with the binding, but it persists when I turn linking off completely.
What can cause that?
Repository Link: https://github.com/MoneyFox/MoneyFox
After testing between Debug and Release mode , we will see that DisplayName not shows in Release mode, however EnglishName always shows.
The Debug mode:
The Release mode:
Therefore, there is a workaround to make it works in Release mode.
Modify Xaml code of Picker with binding EnglishName for ItemDisplayBinding as follows:
<Picker x:Name="MyPicker"
ItemsSource="{Binding AvailableCultures}"
SelectedItem="{Binding SelectedCulture, Mode=TwoWay}"
ItemDisplayBinding="{Binding EnglishName}" />

How To Stop App Data From Growing When Working With Photos

Posted this on the Xamarin forum and haven't gotten feedback yet.
Hoping someone here might be able to assist.
I'm writing an app (Xamarin.Forms) that allows the user to take photo's (using Xam.Plugin.Media) and then display them in a list view.
I noticed the app data growing with each photo taken. And I'm hoping someone could shed some light on what the issue is.
I do not wish to save the photo on the device.
I've only tested this on android at the moment.
I wrote a sample app to simulate the issue. Steps taken as well as the XAML and view model code to follow.
Install app.
Go to Android / Settings / Apps / {AppName} / Storage
The app data is now approximately 10Mb and cache less than 1Mb.
Start the app, take a photo but DO NOT press 'OK' to confirm the photo taken.
Note that the app data has now increased with approximately the size of the photo in memory. (Let's use 5Mb, which brings the app data to 15Mb)
Press 'OK' to confirm the photo taken which then adds the photo to the list view.
Note that the app data has again increased with approximately the size of the photo in memory. (Again 5Mb, which brings the app data to 20Mb)
So with each photo taken the app data increases with approximately double the size of the photo taken, and as a result the app will end up using most of the device storage after a month of use.
I'm not sure if I'm missing something simple, or if this is a result of poor coding.
Any advice would be greatly appreciated.
Thanks in advance.
XAML:
<ContentPage.Content>
<Grid RowSpacing="0" Margin="10,10,10,0">
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Text="Take Photo" Command="{Binding TakePhotoCommand}"/>
<Label Grid.Row="1" Text="Photo List" HorizontalOptions="Center"/>
<ListView Grid.Row="2" ItemsSource="{Binding PhotoList}" RowHeight="250">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Image Source="{Binding ImageSource}" Aspect="Fill" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage.Content>
View Model:
public class CameraVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<MyPhoto> photoList {get;set;}
public ObservableCollection<MyPhoto> PhotoList
{
get { return photoList; }
set
{
if(value != photoList)
{
photoList = value;
OnPropertyChanged("PhotoList");
}
}
}
public ICommand TakePhotoCommand { get; set; }
public async void TakePhoto()
{
try
{
await CrossMedia.Current.Initialize();
var mediaFile = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions() { });
if (mediaFile == null)
{
return;
}
PhotoList.Add(new MyPhoto()
{
ImageSource = ImageSource.FromStream(() =>
{
Stream stream = mediaFile.GetStream();
mediaFile.Dispose();
return stream;
})
});
}
catch (Exception ex)
{
throw ex;
}
}
public CameraVM()
{
PhotoList = new ObservableCollection<MyPhoto>();
TakePhotoCommand = new Command(() =>
{
TakePhoto();
});
}
}
After some further investigation I found that when setting the SaveMetaData and SaveToAlbum properties on the StoreCameraMediaOptions object to false, and then deleting the photo from the device after using it, the app data still increased, but not as much. When going through the device general memory usage, I noticed that the memory used for the default camera app was also quite large. After clearing the camera's memory, my app's data when back to a few kilobytes. I proceeded to take some more photos on my app, the app data grew (as expected), but after a couple of hours it was back to a few kilobytes. I suppose the device clears the memory for unused apps after a while.
Here is my working code. My app uploads the photo taken to a server and then displays this photo in a list view using the relevant URL. This is why i was able to delete the photo from the device afterwards. I suspect that if you try and display the photo taken using an ImageSource gathered from the photo taken it won't display after deleting the photo.
Only tested on android (Galaxy S8)
await CrossMedia.Current.Initialize();
StoreCameraMediaOptions storeCameraMediaOptions = new StoreCameraMediaOptions()
{
SaveMetaData = false,
SaveToAlbum = false
};
MediaFile mediaFile = await CrossMedia.Current.TakePhotoAsync(storeCameraMediaOptions);
if (mediaFile != null)
{
string base64String = Convert.ToBase64String(ULHelper.ToByteArray(mediaFile.GetStream()));
// Logic to upload photo to server using base64String
if (File.Exists(mediaFile.Path))
{
File.Delete(mediaFile.Path);
}
mediaFile.Dispose();
}

Xamarin.Forms Listview

I'm making vocabulary app and trying to implement favorite word in my Xamarin.Forms Listview.
Please have a look below screenshots
For now i use hard code instead of MVVM
Here is my model
namespace Mamtil.Models
{
public class Word
{
public int Id { get; set; }
public string word { get; set; }
public string translation { get; set; }
public string audio { get; set; }
public bool favorite { get; set; }
public string groupBy => translation[0].ToString().ToUpper();
}
}
XAML
...
<ViewCell>
<StackLayout Orientation="Horizontal">
<Button x:Name="FavoriteButton" Image="gray_star.png" BackgroundColor="#F5F5F5" Clicked="Favorite" BorderRadius="0" WidthRequest="45" CommandParameter="{Binding .}" />
<Switch IsToggled="{Binding favorite}" WidthRequest="50"/>
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" Margin="5, 0, 0, 0">
<Label Text="{Binding translation}">
<Label.FontSize>
<OnPlatform x:TypeArguments="x:Double" iOS="11" Android="18" />
</Label.FontSize>
</Label>
<Label Text="{Binding word}" TextColor="#1565C0">
<Label.FontSize>
<OnPlatform x:TypeArguments="x:Double" iOS="11" Android="13" />
</Label.FontSize>
</Label>
</StackLayout>
<Button x:Name="SpeechButton" Image="ic_mic_black_24dp.png" BackgroundColor="#F5F5F5" Clicked="GoToSpeach" BorderRadius="0" WidthRequest="45" CommandParameter="{Binding Id}" />
</StackLayout>
</ViewCell>
So the question is when i load data from database how do i initialize Image of FavoriteButton like
<!-- Here switch is toggled or not depending on Binding value. I want to do something like this but with image of Button -->
<Switch IsToggled="{Binding favorite}">
in code above it is hard coded but i want to change depending Word.favorite value. At run time i managed to change Image like this
async void Favorite(object sender, EventArgs e)
{
var b = (Button)sender;
Word t = (Word)b.CommandParameter;
//update in database
await App.MamtilRepo.UpdateFavoriteAsync(t.Id);
if (t.favorite)
b.Image = "gray_star.png";
else
b.Image = "yellow_star.png";
t.favorite = !t.favorite;
}
for sake of examle:
...
// Some thing like
if(Word.favorite)
Image = yellow_star.png;
else
Image = gray_star.png;
should i do it some where in code or in XAML
I hope could clearly describe my question. thanks in advance
You'd have to use the OnPropertyChanged() method so that it picks up the new value for the image Url, and you should also set up the binding context.
private bool _imageUrl;
public bool ImageUrl
{
get { return _imageUrl; }
set { _imageUrl = value; OnPropertyChanged(); }
}
And use BindingContext = this; in your constructor.
an old post, but here's another way to do this.
Classically one would use a converter for this.
So your button XAML would now look something like this:
<Button x:Name="FavoriteButton" Image="{Binding favorite, Converter={x:Static converter:FavoriteButton.Instance}}" BackgroundColor="#F5F5F5" Clicked="Favorite" BorderRadius="0" WidthRequest="45" CommandParameter="{Binding .}" />
you'd need to add a reference for where your converter lives in the top of your XAML file, i.e.
xmlns:converter="clr-namespace:Mamtil.Converter;assembly=Interact.Client"
With your converter looking something like this:
namespace Mamtil.Converter {
public class FavoriteButton : IValueConverter {
// this Instance property just means your converter only needs to get created once, I saw it in a Xamarin sample.
public static FavoriteButton Instance = new FavoriteButton();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value == null) {
// only doing this because I've had encounters where an ImageSource has taken Exception at having null returned.
return ImageSource.FromFile("gray_star.png");
}
var fav = (bool)value;
if (fav == true) { // redundant ==, but it makes it quite explicit for any future reader glancing over your code what you intend here.
return ImageSource.FromFile("yellow_star.png");
}
return ImageSource.FromFile("gray_star.png");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}
If you're using embedded resources rather than local files then refer to the link Rohit posted in his answer on Aug 24 '16 for how guidance on how to use those in your converter instead.
You can Bind your Image's Source to File, Uri or Resource, which sets the image to display.
FromFile - Requires a filename or filepath that can be resolved on each platform.
FromUri - Requires a Uri object, eg. new Uri("http://server.com/image.jpg") .
FromResource - Requires a resource identifier to an image file embedded in the application or PCL, with a Build Action:EmbeddedResource .
XAML :
<Image Source="{Binding MyImage}" />
ViewModel :
public ImageSource MyImage {get; set; }
You can read more about working with Images here.
DataTrigger or Converter are best solution for your problem.
<Button Image="gray_star.png">
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding favorite}" Value="True">
<Setter Property="Image" Value="yellow_star.png" />
</DataTrigger>
</Button.Triggers>
</Button>

xamarin.forms Handle Clicked event on WebView

I want to handle the click/tap event on a WebView control
I've tried the GestureRecognizers but nothing happens, i think maybe the WebView has some sort of making the event handled "true".
<WebView>
<WebView.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnWebViewClick"
NumberOfTapsRequired="1" />
</WebView.GestureRecognizers>
</WebView>
And I've tried it using c# code behind too but no luck :/
I am using this hack: put the WebView in a Grid and insert a BoxView in the same cell. Then put a handler on the BoxView.
You can use the HybridWebView from XLabs, and use javascript injection to invoke and handle clicks in your Xamarin control. The injected javascript code can add a click-event listener at capture stage. When a click is detected it uses Native callback to send information back to C# code.
For example - you can define a custom control like this:
public class ClickEventArgs : EventArgs
{
public string Element { get; set; }
}
public class ClickableWebView : XLabs.Forms.Controls.HybridWebView
{
public event EventHandler<ClickEventArgs> Clicked;
public static readonly BindableProperty ClickCommandProperty =
BindableProperty.Create("ClickCommand", typeof(ICommand), typeof(ClickableWebView), null);
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
public ClickableWebView()
{
LoadFinished += (sender, e) =>
InjectJavaScript(#"
document.body.addEventListener('click', function(e) {
e = e || window.event;
var target = e.target || e.srcElement;
Native('invokeClick', 'tag='+target.tagName+' id='+target.id+' name='+target.name);
}, true /* to ensure we capture it first*/);
");
this.RegisterCallback("invokeClick", (string el) => {
var args = new ClickEventArgs { Element = el };
Clicked?.Invoke(this, args);
ClickCommand?.Execute(args);
});
}
}
Sample XAML usage
<local:ClickableWebView
Uri="https://google.com"
Clicked="Handle_Clicked"
/>
and sample code-behind
void Handle_Clicked(object sender, CustomWebView.ClickEventArgs e)
{
Xamarin.Forms.Application.Current.MainPage.DisplayAlert("WebView Clicked", e.Element, "Dismiss");
}
** Output **
Alternatively, you can also bind ClickCommand property to implement this using MVVM pattern.
Another option is to handle the click in html and do a navigation that doesn't go anywhere. You can put something like this in your html
<div onclick="window.location.href='#click#'">...</div>
So a click anywhere inside there would cause a navigation. If you only have a button, you could just use
<a href='#click'>...</a>
Then in your WebView control wire up the Navigating event, and check if the new url contains "#click". If so, do your click handling code and call e.Cancel=true in the event to prevent the browser from completing the navigation.
Note that onclick handlers don't work on body or document elements in Xamarin Webview. At least not on iOS.
I've found the simplest approach is to use a Grid with two controls, the WebView and a Button
<Grid>
<WebView
Grid.Column="0"
Grid.Row="0"
HeightRequest="100"
WidthRequest="1000" />
<Button
Grid.Column="0"
Grid.Row="0"
BackgroundColor="Transparent"
HorizontalOptions="Fill"
VerticalOptions="Fill"
Clicked="OnWebViewTapped"/>
</Grid>
The button covers the WebView and intercepts taps.
Gesture recognizer doesn't work with WebView. You can try using MR.Gestures
To get all the features you will have to purchase a license.
If you forget to configure the license key properly or the key does not match your app name, then all the events will still be raised, but the properties of the EventArgs will be empty. That may be enough for the tap and long press events, but not for the more complicated ones.
An easier workaround is to use the 'Focused' event on your webview. You can implement it as below:
var wb = new WebView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Source = "https://stackoverflow.com/questions/56320611/webview-gesturerecognition-not-working-in-xamarin-forms",
};
wb.Focused += (s, e) =>
{
//Handle your logic here!
wb.Unfocus();
};

Silverlight Image binding Issue

I am trying to bind web address of an image (BigImageURL) with a image control. It works fine mostly but for some images i am getting http 403 error (found out using fiddler) and obviously the image does not get displayed. I want to display a static image in case the http url is not resolved.
<Image x:Name="HoverImage" Source="{Binding BigImageURL}" />
I tried to write a converter
public class UriToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType,object parameter, CultureInfo culture)
{
BitmapImage image = null;
try
{
image = new BitmapImage(new Uri(value.ToString()));
}
catch (Exception ex)
{
image= new BitmapImage(new Uri("..<mydefaultimageUrl>.."));
}
return image;
}
...
}
<Image x:Name="HoverImage" Source="{Binding BigImageURL,Converter={StaticResource myUriToImageSourceConverter}" />
didn't work !!
Even though the image url was not accessible, the converter didn't throw any exception. I don't think it tries to resolve the address or read image stream while creating the BitmapImage
Tried setting up a FallbackValue but it didn't work either.
<Image x:Name="HoverImage" Source="{Binding BigImageURL,FallbackValue=DefaultUrl}"/>
Any pointers ??
Thanks in advance
Actually you have to do something like this
<Image x:Name="HoverImage" Source="{Binding BigImageURL}"
ImageFailed="HoverImage_ImageFailed" />
and add event handler
private void HoverImage_ImageFailed(object sender, ExceptionRoutedEventArgs e)
{
var expection = e.ErrorException; // Here we could know what happend
HoverImage.Source = someDefaultUrl; // And here we add default Url...
}
In silverligth you have to handle not loader image and image exceptions with help of events... do not use databinding for that case..

Resources