Hi,
In our chat application we receive Author avatars as byte arrays. I see that in the chat control the Avatar property on Author is a string and it isn't clear how one would use a dynamic image, is there any guidance on achieving this?
thanks
8 Answers, 1 is accepted
The Avatar property uses the file (or url) path approach for the image rather than an ImageSource that you can load a byte[] into a MemoryStream. The solution is to save the image to the local folder of the app and use the Path to the file.
If all the images for the chat participants are only byte arrays, I'm assuming you're retrieving them via HttpClient? In this case, you can just save to disk at that time and not pass around a byte[]. Doing this will also give you a small performance gain as file IO is usually faster than awaiting a network request.
For example:
private
async Task SetupUserAuthorAsync()
{
base
.OnAppearing();
var username =
"John Doe"
;
var localFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var avatarFilePath = Path.Combine(localFolder, $
"
{username}.png
"
);
// If the image hasn't been downloaded and saved yet, do it now. This will only have to be done once
if
(!File.Exists(avatarFilePath))
{
var url =
"https://myApiRoot/users/GetProfileImage?"
+ username;
using
(var client =
new
HttpClient())
{
var imageBytes = await client.GetByteArrayAsync(url);
File.WriteAllBytes(avatarFilePath, imageBytes);
}
}
var userAuthor =
new
Author();
userAuthor.Name = username;
userAuthor.Avatar = avatarFilePath;
}
If you do want to check for image changes, you could occasionally force a new download and save cycle.
Regards,
Lance | Technical Support Engineer, Principal
Progress Telerik

Hi Lance,
Thanks for your reply, that's much appreciated. In terms of the the performance gain this wouldn't be a concern for us as we use Akavache to perform caching of the HTTP requests and have cache expiry rules etc. built into the app logic; this is because we see calls to the avatar download endpoint as simply another in a much longer list of API calls our app makes.
This does mean however, that we will need to now have a separate file-based caching mechanism just for avatars which is slightly frustrating as it means we are making specialisations on the app backend to support the limitation of a single UI widget which isn't desirable.
We can probably work around this for now, but out of interest why is the control limited in this way? Was it an explicit design decision or a technical limitation? Is there a chance that in a future version this could be extended to allow ImageSource too, or an extensibility point put in so the Avatar image can be templated and bound up a different way?
many thanks
That approach is only for the basic built-in template. You can always define your own message ItemTemplate that has a Xamarin.Forms Image (or an FFImageloading CachedImage). We have a full example of this approach in the RadChat ItemTemplateSelector documentation article.
The only thing you would do differently from that example is change the Source property of the Image to use a "profile pic" property on your message model
For example:
<
Style
x:Key
=
"MessageImageStyle"
TargetType
=
"Image"
>
<!-- Old -->
<!-- <Setter Property="Source" Value="{Binding Author.Avatar}" />-->
<!-- New -->
<
Image
Source
=
"{Binding Data.ProfileImageSource}"
/>
<
Setter
Property
=
"WidthRequest"
Value
=
"30"
/>
<
Setter
Property
=
"HeightRequest"
Value
=
"30"
/>
</
Style
>
<
DataTemplate
x:Key
=
"ImportantMessageTemplate"
>
<
Grid
Margin
=
"0, 2, 0, 10"
>
<
Image
Style
=
"{StaticResource MessageImageStyle}"
/>
<
telerikPrimitives:RadBorder
CornerRadius
=
"0, 7, 7, 7"
Margin
=
"45, 0, 50, 0"
HorizontalOptions
=
"Start"
BackgroundColor
=
"#FF0000"
>
<
StackLayout
Orientation
=
"Horizontal"
Margin
=
"20, 0, 20, 0"
>
<
Label
Text
=
"! "
FontAttributes
=
"Bold"
FontSize
=
"Medium"
/>
<
Label
Text
=
"{Binding Text}"
FontSize
=
"Medium"
/>
</
StackLayout
>
</
telerikPrimitives:RadBorder
>
</
Grid
>
</
DataTemplate
>
Regarding changing the Author.Avatar property, you can ask the development team to switch to using a more commonly accepted ImageSource type that has implicit conversion for string paths. For your convenience, I have created this feedback on your behalf here: https://feedback.telerik.com/xamarin/1410330-use-imagesource-type-for-author-avatar-property
Regards,
Lance | Technical Support Engineer, Principal
Progress Telerik

Nice, thanks Lance! I'll give that a shot.
I can understand if this is overwhelming at first, so I thought I would spend a couple hours to put together a clear example of how these things work together, along with the customization to use a custom template and a more elegant way of using a byte[] on the author model.
First, let's create an ExtendedAuthor model that inherits from Author. There are other ways you could do this, but I think this is probably the simplest and still allows you to use our examples.
public
class
ExtendedAuthor : Author
{
public
ImageSource ProfileImageSource {
get
;
set
; }
}
Now, let's use the MVVM Documentation's tutorial as a base lesson because it has two Authors in the view model. You will want to complete that tutorial to ctach up, it has the models and data as a base to move forward. With done, you can make the changes below.
Now, in my demo, you're going to use your ExtendedAuthor class instead of Author and set the ProfileImageSource property (note: in the example I use SkiaSharp to create a byte[] for the image, this is where you would use your byte[] source):
public
class
MainPageViewModel : NotifyPropertyChangedBase
{
public
MainPageViewModel()
{
// Both authors have a byte[] as the profile image source
this
.Me =
new
ExtendedAuthor
{
Name =
"human"
,
ProfileImageSource = ImageSource.FromStream(() =>
new
MemoryStream(
GenerateProfileImageBytes()
))
};
this
.Bot =
new
ExtendedAuthor
{
Name =
"Bot"
,
ProfileImageSource
= ImageSource.FromStream(() =>
new
MemoryStream(GenerateProfileImageBytes()))
};
this
.Items =
new
ObservableCollection<SimpleChatItem>();
// Simulate async data loading
Device.StartTimer(TimeSpan.FromMilliseconds(500), () =>
{
this
.Items.Add(
new
SimpleChatItem { Author =
this
.Bot, Text =
"Hi."
});
this
.Items.Add(
new
SimpleChatItem { Author =
this
.Bot, Text =
"How can I help you?"
});
return
false
;
});
}
public
ExtendedAuthor Me {
get
;
set
; }
public
ExtendedAuthor Bot {
get
;
set
; }
public
ObservableCollection<SimpleChatItem> Items {
get
;
set
; }
private
byte
[] GenerateProfileImageBytes()
{
using
(var bmp =
new
SKBitmap(36, 36))
{
var rand =
new
Random();
var r = (
byte
)rand.Next(0,
byte
.MaxValue);
var g = (
byte
)rand.Next(0,
byte
.MaxValue);
var b = (
byte
)rand.Next(0,
byte
.MaxValue);
var color =
new
SKColor(r, g, b, 255);
for
(
int
i = 0; i < bmp.Width; i++)
{
for
(
int
j = 0; j < bmp.Height; j++)
{
bmp.SetPixel(i, j, color);
}
}
using
(SKImage image = SKImage.FromPixels(bmp.PeekPixels()))
{
var data = image.Encode(SKEncodedImageFormat.Png, 100);
return
data.ToArray();
}
}
}
}
Next, we need to define an "out of the box" ChatItemTemplateSelector that will let us use our own DataTemplate for the chat items. This can initially appear complicated with the number of templates, but it's actually straightforward. I've written in some code comments to clarify
<
ContentPage.Resources
>
<
converters:SimpleChatItemConverter
x:Key
=
"SimpleChatItemConverter"
/>
<!-- Image is on the left side and it's source is bound to the ExtendedAuthor's ImageSource property-->
<
DataTemplate
x:Key
=
"NormalIncomingMessageTemplate"
>
<
Grid
Margin
=
"10"
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
Width
=
"Auto"
/>
<
ColumnDefinition
Width
=
"*"
/>
</
Grid.ColumnDefinitions
>
<
Image
Source
=
"{Binding Author.ProfileImageSource}"
WidthRequest
=
"30"
HeightRequest
=
"30"
Grid.Column
=
"0"
/>
<
primitives:RadBorder
CornerRadius
=
"7"
Margin
=
"20,0,0,0"
Padding
=
"5"
Grid.Column
=
"1"
>
<
Label
Text
=
"{Binding Text}" HorizontalTextAlignment
=
"Start"
/>
</
primitives:RadBorder
>
</
Grid
>
</
DataTemplate
>
<!-- In this template, the image is on the right side, and the margins adjusted appropriately -->
<
DataTemplate
x:Key
=
"NormalOutgoingMessageTemplate"
>
<
Grid
Margin
=
"10"
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
Width
=
"*"
/>
<
ColumnDefinition
Width
=
"Auto"
/>
</
Grid.ColumnDefinitions
>
<
primitives:RadBorder
CornerRadius
=
"7"
Margin
=
"0,0,20,0"
Padding
=
"5"
Grid.Column
=
"0"
>
<
Label
Text
=
"{Binding Text}" HorizontalTextAlignment
=
"End"
/>
</
primitives:RadBorder
>
<
Image
Source
=
"{Binding Author.ProfileImageSource}"
WidthRequest
=
"30"
HeightRequest
=
"30"
Grid.Column
=
"1"
/>
</
Grid
>
</
DataTemplate
>
<!--
Here is the logic we use to decide which template is going to be applied:
-
If there's just one message from that author, we use the "Single" template
-
If there are two messages from that author, we use the "First" and "Last" template
-
If there are three or more, we use the "First", "Middle" and "Last" templates
To keep this demo simple, I use the same template for all incoming messages, and the same template for all outgoing messages.
You could use clever styling to add rounded corners for different messages
-->
<
telerikConversationalUI:ChatItemTemplateSelector
x:Key
=
"SimpleChatItemTemplateSelector"
IncomingSingleTextMessageTemplate
=
"{StaticResource NormalIncomingMessageTemplate}"
IncomingFirstTextMessageTemplate
=
"{StaticResource NormalIncomingMessageTemplate}"
IncomingMiddleTextMessageTemplate
=
"{StaticResource NormalIncomingMessageTemplate}"
IncomingLastTextMessageTemplate
=
"{StaticResource NormalIncomingMessageTemplate}"
OutgoingSingleTextMessageTemplate
=
"{StaticResource NormalOutgoingMessageTemplate}"
OutgoingFirstTextMessageTemplate
=
"{StaticResource NormalOutgoingMessageTemplate}"
OutgoingMiddleTextMessageTemplate
=
"{StaticResource NormalOutgoingMessageTemplate}"
OutgoingLastTextMessageTemplate
=
"{StaticResource NormalOutgoingMessageTemplate}"
/>
</
ContentPage.Resources
>
With that in place, you can now set the selector to the RadChat instance:
<
Grid
>
<
telerikConversationalUI:RadChat
x:Name
=
"chat"
Author
=
"{Binding Me}"
ItemsSource
=
"{Binding Items}"
ItemConverter
=
"{StaticResource SimpleChatItemConverter}"
ItemTemplateSelector
=
"{StaticResource SimpleChatItemTemplateSelector}"
/>
</
Grid
>
Here's the result at runtime:
I hope this helps.
Regards,
Lance | Technical Support Engineer, Principal
Progress Telerik

Lance,
That was a fantastic response, thankyou! This is the pretty much the exact path I was taking when I saw your post so it's great to get validation on the approach. The only difference I have is that the additional properties on the extended Author class are bindable as in our app the imagesource may already be available, or might require an HTTP request if there is a cache miss so the process is asynchronous and the image may not always be available upfront; beyond that it works the same way.
At first glance I thought it would be a real pain to replicate the original look and feel, but using ilSpy I was able to comb through the compiled resource dictionary and rip enough of the original border radii, colors, margins etc. that it looks perfect, and I'm better placed to make further changes.
The way you deal with the template's wasn't obvious to me, but once you made how that works clear I see it is very straightforward, I can see the template converter simply scans around a bit and compares the authors of the surrounding messages to determine whether this message is a single, first, middle or last which makes complete sense.
regards,

Hi Lance,
Quick question;
I'm getting some rendering glitches which I thought were my problem, but looking more closely it appears that it happens with the default templates too (especially on iOS).
The first/middle/last template switching is not always correctly respected, and I'm not sure it can work properly this way unless you've done some Telerik magic;
The Xamarin docs about data templates mention under the "limitations" section that a data template selector should always return the same template for the same data item (https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/selector#limitations). From what I can see the ChatItemTemplateSelector class potentially breaks this rule.
Have you guys worked around this somehow with the chat control? The Xamarin guys describe the limitation as;
"The DataTemplateSelector subclass must always return the same template for the same data if queried multiple times."
The template selector used by the chat UI doesn't seem to adhere to this, and may be the reason why it sometimes gets a bit glitchy.
Correct, that rule is meant for one-to-one comparison for individual data items without consideration for adjacent items. The feature the development team built for the RadChat is an extra level of customization you can use
You don't have to use our base and can make your own TemplateSelector that either returns a left-aligned template or a right-aligned template. As long as the base is DataTemplateSelector, it should work.
Problem
Ultimately, the guideline is still followed because when certain conditions are met, you'll get the expected template for specific items in that consecutive run.
What you might be seeing is a timing issue. If the logic doesn't have all the items at once, then it may apply the wrong template thinking there are only X numer of consecutive items. Try using an ObservableRangeCollection and when you have multiple items from the author, use AddRange() to add the items all at once so that CollectionChanged event is invoke once (this is used internally to render items).
Further Investigation
If this doesn't help, this will require a Support Ticket so that you can work directly with the support team. Please go to your Support Tickets page and start a new Support Ticket for the RadChat. That will put you directly in contact with the team and they'll get back to you within the 24 hour time frame.
Tip - I highly recommend making sure that you share all the code that you can so we can reproduce the problem. This will drastically reduce the chance that the engineer will need to reply asking for the code (which may cost you more time).
I'm always watching the threads and will leave a note for the team to make sure they review our previous conversations, if needed.
Regards,
Lance | Technical Support Engineer, Principal
Progress Telerik