<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by José Pereira on Medium]]></title>
        <description><![CDATA[Stories by José Pereira on Medium]]></description>
        <link>https://medium.com/@zleao?source=rss-aef820fb9519------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*qc8TYkm4bje5ysefrdB1Xw.png</url>
            <title>Stories by José Pereira on Medium</title>
            <link>https://medium.com/@zleao?source=rss-aef820fb9519------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 04:28:58 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@zleao/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How to create a simple chat page using .NET MAUI]]></title>
            <link>https://medium.com/medialesson/how-to-create-a-simple-chat-page-using-net-maui-a345bc2afc96?source=rss-aef820fb9519------2</link>
            <guid isPermaLink="false">https://medium.com/p/a345bc2afc96</guid>
            <category><![CDATA[maui]]></category>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[net-maui]]></category>
            <category><![CDATA[collectionview]]></category>
            <category><![CDATA[chat]]></category>
            <dc:creator><![CDATA[José Pereira]]></dc:creator>
            <pubDate>Mon, 19 Aug 2024 08:38:25 GMT</pubDate>
            <atom:updated>2024-08-19T08:38:25.514Z</atom:updated>
            <content:encoded><![CDATA[<h3>How to create a simple chat page using .NET MAUI</h3><p>Learn how to create a simple chat page UI that could be used for real-time message exchange, with support for images.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wuHDtKCkIYFfGap2oAf7eg.png" /></figure><p><strong>TLDR:</strong> Click <a href="https://github.com/zleao/MauiChat">here</a> to check a fully working sample with support for Android, iOS and WinUI.</p><h3><strong>Objective</strong></h3><p>Create a chat like interaction, where we can send and receive images and text. Here are some ideas of what it could look like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IBNI9ErUDa6ad7HSdpTNHA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D26g2_Jfzcww0YEctsnSbQ.jpeg" /></figure><h3><strong>Approach used and why I used it</strong></h3><p>I tried reducing the external dependencies to a minimum, so I used the native controls offered out-of-the-box by .NET MAUI. I did however use the community toolkit nugets to be able to simplify the code and <a href="https://www.nuget.org/packages/zoft.MauiExtensions.Core/"><em>zoft.MauiExtensions.Core</em></a> nuget package for some useful extensions.<br>The initial project was created using the <a href="https://marketplace.visualstudio.com/items?itemName=MattLaceyLtd.MauiAppAccelerator">MAUI App Accelerator extension by Matt Lacey</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zIyScZLJxlKY6FFKkiiCOQ.png" /><figcaption>Nugets used in the project (MauiVersion =&gt; 8.0.80)</figcaption></figure><p>In a first approach I though about using the <a href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/collectionview/?view=net-maui-8.0">CollectionView</a> with the <a href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/collectionview/grouping?view=net-maui-8.0">grouping</a> feature but as it turned out, this approach works very well for Android, but has <a href="https://github.com/dotnet/maui/issues/20336">known UI rendering issues on iOS</a>. To workaround this problem, I choose to use a single list of items, that contain 2 types of objects (MessageGroupand MessageItem)</p><pre>public class MessageGroup(string name)<br>{<br>    public string Name { get; } = name;<br>}<br><br>public class MessageItem<br>{<br>    public string? Body { get; set; }<br><br>    public DateTime Created { get; set; }<br><br>    public bool IsMyMessage { get; set; }<br><br>    public List&lt;MediaItem&gt; Attachments { get; set; } = [];<br>}</pre><h3>Implementation</h3><p>Start by creating a new project (I used the <a href="https://marketplace.visualstudio.com/items?itemName=MattLaceyLtd.MauiAppAccelerator">MAUI App Accelerator extension by Matt Lacey</a>).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gE7RdET2KCdqyVJcoSAxmg.png" /><figcaption>Configuration for new project</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/468/1*6CxwOM4ohYcrC8hIhgVOSA.png" /><figcaption>Initial project structure</figcaption></figure><p>I’ll skip the formalities of adding the backbone code and keep focused on the UI of the chat itself. You can take a look at the different components added, in the the github repo <a href="https://github.com/zleao/MauiChat">here</a>. Here’s an overview of what was added:<br> — Converters to help format the time.<br> — Models that represent a <em>message</em>, a <em>message group</em> and a <em>media item</em> (image).<br> — Services that provides a mocked logic for sending and receiving messages.<br> — ViewModels to support the chat page functionality.<br> — Views that contain all the UI implementation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/443/1*wtyBJYBnP_KTLeenzqnuRg.png" /><figcaption>Final project structure</figcaption></figure><p>Now let’s talk about the UI. <br>The main piece will be the ChatPage. This page will have a CollectionView to show the messages. Bellow that list there will be an area for the controls to write a message, attach images and send the message. Finally at the bottom, there will be an area to show the images that are attached to the message that will be sent.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IBNI9ErUDa6ad7HSdpTNHA.jpeg" /><figcaption>Chat page UI sample</figcaption></figure><h4>Messages List</h4><p>The CollectionView configuration will be quite simple. It will have a list of messages, bound to the <em>ItemsSource</em> and an <em>ItemTemplate</em> bound to a MessageTemplateSelector.</p><pre>&lt;!-- Messages List --&gt;<br>&lt;CollectionView x:Name=&quot;MessagesList&quot; <br>                ItemsSource=&quot;{Binding GroupedMessages}&quot;<br>                ItemTemplate=&quot;{StaticResource MessageTemplateSelector}&quot;<br>                ItemsUpdatingScrollMode=&quot;KeepLastItemInView&quot;<br>                Grid.Row=&quot;0&quot;&gt;<br>    &lt;CollectionView.ItemsLayout&gt;<br>        &lt;LinearItemsLayout Orientation=&quot;Vertical&quot;<br>                           ItemSpacing=&quot;5&quot; /&gt;<br>    &lt;/CollectionView.ItemsLayout&gt;<br>&lt;/CollectionView&gt;</pre><p>The MessageTemplateSelector will have 3 templates to choose from: MessageGroupTemplate, MessageSentTemplate and MessageReceivedTemplate.</p><pre>&lt;DataTemplate x:Key=&quot;MessageGroupHeaderTemplate&quot;&gt;<br>    &lt;v:MessageGroupHeaderTemplate/&gt;<br>&lt;/DataTemplate&gt;<br><br>&lt;DataTemplate x:Key=&quot;MessageSentTemplate&quot;&gt;<br>    &lt;v:MessageSentTemplate/&gt;<br>&lt;/DataTemplate&gt;<br><br>&lt;DataTemplate x:Key=&quot;MessageReceivedTemplate&quot;&gt;<br>    &lt;v:MessageReceivedTemplate/&gt;<br>&lt;/DataTemplate&gt;<br><br>&lt;v:MessageTemplateSelector x:Key=&quot;MessageTemplateSelector&quot;<br>                           GroupHeader=&quot;{StaticResource MessageGroupHeaderTemplate}&quot;<br>                           MessageSent=&quot;{StaticResource MessageSentTemplate}&quot;<br>                           MessageReceived=&quot;{StaticResource MessageReceivedTemplate}&quot; /&gt;</pre><pre>public class MessageTemplateSelector : DataTemplateSelector<br>{<br>    public DataTemplate GroupHeader { get; set; } = null!;<br><br>    public DataTemplate MessageSent { get; set; } = null!;<br>    <br>    public DataTemplate MessageReceived { get; set; } = null!;<br><br><br>    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)<br>    {<br>        if (item is MessageGroup)<br>        {<br>            return GroupHeader;<br>        }<br><br>        return ((MessageItem)item).IsMyMessage ? MessageSent : MessageReceived;<br>    }<br>}</pre><pre>&lt;!-- MessageGroupHeaderTemplate --&gt;<br>&lt;Grid xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;<br>      xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;<br>      x:Class=&quot;MauiChat.Views.MessageGroupHeaderTemplate&quot;<br>      xmlns:m=&quot;clr-namespace:MauiChat.Models&quot;<br>      x:DataType=&quot;m:MessageGroup&quot;&gt;<br><br>    &lt;Grid.Resources&gt;<br><br>        &lt;Style x:Key=&quot;GroupBubbleStyle&quot; TargetType=&quot;Border&quot;&gt;<br>            &lt;Setter Property=&quot;BackgroundColor&quot; Value=&quot;#b7babb&quot; /&gt;<br>            &lt;Setter Property=&quot;StrokeShape&quot; Value=&quot;RoundRectangle 15&quot; /&gt;<br>            &lt;Setter Property=&quot;Padding&quot; Value=&quot;5&quot; /&gt;<br>            &lt;Setter Property=&quot;Margin&quot; Value=&quot;5&quot; /&gt;<br>            &lt;Setter Property=&quot;HorizontalOptions&quot; Value=&quot;Center&quot; /&gt;<br>            &lt;Setter Property=&quot;VerticalOptions&quot; Value=&quot;Center&quot; /&gt;<br>        &lt;/Style&gt;<br>        <br>    &lt;/Grid.Resources&gt;<br><br>    &lt;Border Style=&quot;{StaticResource GroupBubbleStyle}&quot;&gt;<br>        &lt;Label Text=&quot;{Binding Name}&quot;/&gt;<br>    &lt;/Border&gt;<br><br>&lt;/Grid&gt;</pre><pre>&lt;!-- MessageReceivedTemplate --&gt;<br>&lt;Grid xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;<br>      xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;<br>      x:Class=&quot;MauiChat.Views.MessageReceivedTemplate&quot;<br>      xmlns:converters=&quot;clr-namespace:MauiChat.Converters&quot;<br>      xmlns:m=&quot;clr-namespace:MauiChat.Models&quot;<br>      x:DataType=&quot;m:MessageItem&quot;<br>      ColumnDefinitions=&quot;10,.8*,.2*&quot;&gt;<br> <br>    &lt;Grid.Resources&gt;<br> <br>        &lt;converters:MessageDateConverter x:Key=&quot;MessageDateConverter&quot; /&gt;<br>        <br>        &lt;Color x:Key=&quot;PeerMessageBackgroundColor&quot;&gt;#DDDDDD&lt;/Color&gt;<br>        &lt;Color x:Key=&quot;MessagesTextColor&quot;&gt;#DE000000&lt;/Color&gt;<br>        <br>    &lt;/Grid.Resources&gt;<br> <br>    &lt;Grid Grid.Column=&quot;0&quot;<br>          Margin=&quot;0,0,0,5&quot;<br>          VerticalOptions=&quot;End&quot;&gt;<br>        <br>        &lt;Polygon Points=&quot;0,10 10,0 10,20&quot;<br>                 StrokeThickness=&quot;0&quot;<br>                 Fill=&quot;{StaticResource PeerMessageBackgroundColor}&quot; /&gt;<br>    &lt;/Grid&gt;<br> <br>    &lt;Border Grid.Column=&quot;1&quot;<br>            StrokeShape=&quot;RoundRectangle 5&quot;<br>            StrokeThickness=&quot;0&quot;<br>            BackgroundColor=&quot;{StaticResource PeerMessageBackgroundColor}&quot;&gt;<br>        <br>        &lt;Grid RowDefinitions=&quot;Auto,Auto,Auto&quot;<br>              ColumnDefinitions=&quot;*,Auto&quot;<br>              Padding=&quot;10,5,5,5&quot;&gt;<br> <br>            &lt;Label Text=&quot;Another person&quot;<br>                   TextColor=&quot;{StaticResource MessagesTextColor}&quot;<br>                   Grid.Row=&quot;0&quot;<br>                   FontAttributes=&quot;Bold&quot; /&gt;<br> <br>            &lt;Grid Grid.Row=&quot;1&quot;<br>                  RowDefinitions=&quot;Auto,Auto&quot;<br>                  RowSpacing=&quot;6&quot;&gt;<br>                <br>                &lt;Label Text=&quot;{Binding Body}&quot;<br>                       TextColor=&quot;{DynamicResource MessagesTextColor}&quot;<br>                       Grid.Row=&quot;0&quot;/&gt;<br> <br>                &lt;FlexLayout BindableLayout.ItemsSource=&quot;{Binding Attachments}&quot;<br>                            Grid.Row=&quot;1&quot;<br>                            Style=&quot;{StaticResource FlexLayoutImageContainerStyle}&quot;&gt;<br>                    &lt;BindableLayout.ItemTemplate&gt;<br>                        &lt;DataTemplate x:DataType=&quot;m:MediaItem&quot;&gt;<br>                            &lt;Image Source=&quot;{Binding Source}&quot; Style=&quot;{StaticResource ImageSmallStyle}&quot; /&gt;<br>                        &lt;/DataTemplate&gt;<br>                    &lt;/BindableLayout.ItemTemplate&gt;<br>                &lt;/FlexLayout&gt;<br>            &lt;/Grid&gt;<br> <br>            &lt;Label Grid.Row=&quot;2&quot;<br>                   Grid.Column=&quot;1&quot;<br>                   Text=&quot;{Binding Created, Converter={StaticResource MessageDateConverter}}&quot;<br>                   TextColor=&quot;{DynamicResource MessagesTextColor}&quot;/&gt;<br>        &lt;/Grid&gt;<br>    &lt;/Border&gt;<br> <br>&lt;/Grid&gt;</pre><pre>&lt;!-- MessageSentTemplate --&gt;<br>&lt;Grid xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;<br>      xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;<br>      x:Class=&quot;MauiChat.Views.MessageSentTemplate&quot;<br>      xmlns:converters=&quot;clr-namespace:MauiChat.Converters&quot;<br>      xmlns:m=&quot;clr-namespace:MauiChat.Models&quot;<br>      x:DataType=&quot;m:MessageItem&quot;<br>      ColumnDefinitions=&quot;.2*,.8*,15&quot;&gt;<br> <br>    &lt;Grid.Resources&gt;<br> <br>        &lt;converters:MessageDateConverter x:Key=&quot;MessageDateConverter&quot; /&gt;<br> <br>        &lt;Color x:Key=&quot;MyMessageBackgroundColor&quot;&gt;#5C809D&lt;/Color&gt;<br>        &lt;Color x:Key=&quot;MessagesTextColor&quot;&gt;#FFFFFF&lt;/Color&gt;<br>        <br>    &lt;/Grid.Resources&gt;<br>    <br>    &lt;Border Grid.Column=&quot;1&quot;<br>            StrokeShape=&quot;RoundRectangle 5&quot;<br>            StrokeThickness=&quot;0&quot;<br>            BackgroundColor=&quot;{StaticResource MyMessageBackgroundColor}&quot;&gt;<br>        <br>        &lt;Grid RowDefinitions=&quot;Auto,Auto,Auto&quot;<br>              ColumnDefinitions=&quot;*,Auto&quot;<br>              Padding=&quot;10,5,5,5&quot;&gt;<br> <br>            &lt;Label Text=&quot;Myself&quot;<br>                   TextColor=&quot;{StaticResource MessagesTextColor}&quot;<br>                   Grid.Row=&quot;0&quot;<br>                   FontAttributes=&quot;Bold&quot;/&gt;<br> <br>            &lt;Grid Grid.Row=&quot;1&quot;<br>                  RowDefinitions=&quot;Auto,Auto&quot;<br>                  RowSpacing=&quot;6&quot;&gt;<br>                &lt;Label Text=&quot;{Binding Body}&quot;<br>                       TextColor=&quot;{StaticResource MessagesTextColor}&quot;<br>                       Grid.Row=&quot;0&quot;/&gt;<br> <br>                &lt;FlexLayout BindableLayout.ItemsSource=&quot;{Binding Attachments}&quot;<br>                            Grid.Row=&quot;1&quot;<br>                            Style=&quot;{StaticResource FlexLayoutImageContainerStyle}&quot;&gt;<br>                    &lt;BindableLayout.ItemTemplate&gt;<br>                        &lt;DataTemplate x:DataType=&quot;m:MediaItem&quot;&gt;<br>                            &lt;Image Source=&quot;{Binding Source}&quot; Style=&quot;{StaticResource ImageSmallStyle}&quot; /&gt;<br>                        &lt;/DataTemplate&gt;<br>                    &lt;/BindableLayout.ItemTemplate&gt;<br>                &lt;/FlexLayout&gt;<br>            &lt;/Grid&gt;<br> <br>            &lt;Label Grid.Row=&quot;2&quot;<br>                   Grid.Column=&quot;1&quot;<br>                   Text=&quot;{Binding Created, Converter={StaticResource MessageDateConverter}}&quot;<br>                   TextColor=&quot;{StaticResource MessagesTextColor}&quot; /&gt;<br>        &lt;/Grid&gt;<br>    &lt;/Border&gt;<br> <br>    &lt;Grid Grid.Column=&quot;2&quot;<br>          Margin=&quot;0,0,0,5&quot;<br>          VerticalOptions=&quot;End&quot;&gt;<br>        &lt;Polygon Points=&quot;0,0 0,20 10,10&quot;<br>                 StrokeThickness=&quot;0&quot;<br>                 Fill=&quot;{StaticResource MyMessageBackgroundColor}&quot; /&gt;<br>    &lt;/Grid&gt;<br>    <br>&lt;/Grid&gt;</pre><h4>Controls Area</h4><p>This area provides the interaction controls to write a text message and to add attachments.</p><pre>&lt;!-- Message Input --&gt;<br>&lt;Grid Grid.Row=&quot;1&quot;<br>      ColumnDefinitions=&quot;*,Auto&quot;<br>      IsEnabled=&quot;{Binding IsSendingMessage, Converter={StaticResource InvertedBoolConverter}}&quot;&gt;<br><br>    &lt;Border Grid.Column=&quot;0&quot;<br>            StrokeShape=&quot;RoundRectangle 20&quot;<br>            Margin=&quot;6&quot;&gt;<br>        &lt;Grid ColumnDefinitions=&quot;*,Auto,Auto&quot;<br>              ColumnSpacing=&quot;6&quot;<br>              Margin=&quot;6,0,6,0&quot;&gt;<br><br>            &lt;Editor Grid.Column=&quot;0&quot;<br>                    Placeholder=&quot;Write your message...&quot;<br>                    Text=&quot;{Binding MessageBody, Mode=TwoWay}&quot;<br>                    AutoSize=&quot;TextChanges&quot;<br>                    MaximumHeightRequest=&quot;100&quot;<br>                    VerticalOptions=&quot;Center&quot;/&gt;<br><br>            &lt;Button Style=&quot;{StaticResource ActionButtonStyle}&quot;<br>                    Command=&quot;{Binding TakePhotoCommand}&quot;<br>                    IsVisible=&quot;{Binding IsTyping, Converter={StaticResource InvertedBoolConverter}}&quot;<br>                    Grid.Column=&quot;1&quot;&gt;<br>                &lt;Button.ImageSource&gt;<br>                    &lt;FontImageSource FontFamily=&quot;{x:Static root:IconFontFamily.MaterialIcons}&quot;<br>                                     Glyph=&quot;{x:Static root:IconFontGlyph.Photo_camera}&quot;<br>                                     Size=&quot;{StaticResource FontIconDefaultSize}&quot;<br>                                     Color=&quot;{StaticResource FontIconDefaultColor}&quot;/&gt;<br>                &lt;/Button.ImageSource&gt;<br>            &lt;/Button&gt;<br><br>            &lt;Button Style=&quot;{StaticResource ActionButtonStyle}&quot;<br>                    Command=&quot;{Binding PickPhotoCommand}&quot;<br>                    IsVisible=&quot;{Binding IsTyping, Converter={StaticResource InvertedBoolConverter}}&quot;<br>                    Grid.Column=&quot;2&quot;&gt;<br>                &lt;Button.ImageSource&gt;<br>                    &lt;FontImageSource FontFamily=&quot;{x:Static root:IconFontFamily.MaterialIcons}&quot;<br>                                     Glyph=&quot;{x:Static root:IconFontGlyph.Image}&quot;<br>                                     Size=&quot;{StaticResource FontIconDefaultSize}&quot;<br>                                     Color=&quot;{StaticResource FontIconDefaultColor}&quot;/&gt;<br>                &lt;/Button.ImageSource&gt;<br>            &lt;/Button&gt;<br>        &lt;/Grid&gt;<br>    &lt;/Border&gt;<br><br>    &lt;Button Style=&quot;{StaticResource SendButtonStyle}&quot;<br>            Command=&quot;{Binding SendMessageCommand}&quot;<br>            IsVisible=&quot;{Binding IsSendingMessage, Converter={StaticResource InvertedBoolConverter}}&quot;<br>            Grid.Column=&quot;1&quot;&gt;<br>        &lt;Button.ImageSource&gt;<br>            &lt;FontImageSource FontFamily=&quot;{x:Static root:IconFontFamily.MaterialIcons}&quot;<br>                             Glyph=&quot;{x:Static root:IconFontGlyph.Send}&quot;<br>                             Size=&quot;24&quot;/&gt;<br>        &lt;/Button.ImageSource&gt;<br>    &lt;/Button&gt;<br><br>    &lt;ActivityIndicator Style=&quot;{StaticResource ActivityIndicatorStyle}&quot;<br>                       IsVisible=&quot;{Binding IsSendingMessage}&quot;<br>                       IsRunning=&quot;True&quot;<br>                       Grid.Column=&quot;1&quot; /&gt;<br>&lt;/Grid&gt;</pre><h4>Attachments</h4><p>This area will show a list of the attached images.</p><pre>&lt;!-- Attachments List --&gt;<br>&lt;Grid Grid.Row=&quot;2&quot; RowDefinitions=&quot;Auto&quot;<br>      IsEnabled=&quot;{Binding IsSendingMessage, Converter={StaticResource InvertedBoolConverter}}&quot;<br>      IsVisible=&quot;{Binding MessageAttachments, Converter={StaticResource IsListNotNullOrEmptyConverter}}&quot;<br>      MaximumHeightRequest=&quot;150&quot;&gt;<br>    &lt;ScrollView VerticalOptions=&quot;Fill&quot; HorizontalOptions=&quot;Fill&quot; HeightRequest=&quot;150&quot;&gt;<br>        &lt;FlexLayout BindableLayout.ItemsSource=&quot;{Binding MessageAttachments}&quot;<br>                    Style=&quot;{StaticResource FlexLayoutImageContainerStyle}&quot;&gt;<br>            &lt;BindableLayout.ItemTemplate&gt;<br>                &lt;DataTemplate x:DataType=&quot;m:MediaItem&quot;&gt;<br>                    &lt;Image Source=&quot;{Binding Source}&quot;<br>                           Style=&quot;{StaticResource ImageStyle}&quot;&gt;<br>                        &lt;Image.GestureRecognizers&gt;<br>                            &lt;TapGestureRecognizer Command=&quot;{Binding Source={Reference self},<br>                                                            Path=BindingContext.RemoveAttachmentCommand}&quot; <br>                                                  CommandParameter=&quot;{Binding .}&quot;/&gt;<br>                        &lt;/Image.GestureRecognizers&gt;<br>                    &lt;/Image&gt;<br>                &lt;/DataTemplate&gt;<br>            &lt;/BindableLayout.ItemTemplate&gt;<br>        &lt;/FlexLayout&gt;<br>    &lt;/ScrollView&gt;<br>    <br>&lt;/Grid&gt;</pre><h4><strong>ScrollToBottom behaviour</strong></h4><p>As an extra behavior, I also wanted to had the possibility to <em>Scroll-To-Bottom</em>. This interaction should be done via a button that is shown, when we’re not already at the bottom of the messages list.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D26g2_Jfzcww0YEctsnSbQ.jpeg" /><figcaption>Scroll-To-Bottom UI sample</figcaption></figure><p>The button UI implementation is quite simple. We just show an ImageButton on top of the messages list.</p><pre>&lt;!-- GoToBottom Button --&gt;<br>&lt;ImageButton Grid.Row=&quot;0&quot;<br>             Style=&quot;{StaticResource ScrollToBottomImageButtonStyle}&quot;<br>             Clicked=&quot;ButtonScrollToBottom_Clicked&quot;<br>             x:Name=&quot;ButtonScrollToBottom&quot;&gt;<br>    &lt;ImageButton.Source&gt;<br>        &lt;FontImageSource FontFamily=&quot;{x:Static root:IconFontFamily.MaterialIcons}&quot;<br>                         Glyph=&quot;{x:Static root:IconFontGlyph.Keyboard_double_arrow_down}&quot;<br>                         Size=&quot;{StaticResource FontIconDefaultSize}&quot;/&gt;<br>    &lt;/ImageButton.Source&gt;<br>&lt;/ImageButton&gt;</pre><p>To control the visibility and functionality of the button, we use the CollectionView.Scrolled event to check if the last item of the list is visible and act accordingly. We then use the CollectionView.ScrollTo method to force the list to show a specific item (in this case, the last one)</p><pre><br>private void MessagesList_Scrolled(object? sender, ItemsViewScrolledEventArgs e)<br>{<br>    ButtonScrollToBottom.IsVisible = e.LastVisibleItemIndex != ViewModel.GroupedMessages.Count - 1;<br>}<br><br>private void ButtonScrollToBottom_Clicked(object sender, EventArgs e)<br>{<br>    MessagesList.ScrollTo(ViewModel.GroupedMessages.Count - 1);<br>}</pre><p>And that’s it! The chat page is ready to be used!</p><p>After the amazing posts written during this year’s <a href="https://x.com/hashtag/MAUIUIJuly">#MAUIUIJULY</a> I felt that I could also give my small contribution the the .NET MAUI community. Let me know if you liked the approach and what would you change.</p><p>Happy Coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a345bc2afc96" width="1" height="1" alt=""><hr><p><a href="https://medium.com/medialesson/how-to-create-a-simple-chat-page-using-net-maui-a345bc2afc96">How to create a simple chat page using .NET MAUI</a> was originally published in <a href="https://medium.com/medialesson">Medialesson</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to use a DrawingView inside a scrollable layout, in .NET MAUI]]></title>
            <link>https://medium.com/medialesson/how-to-use-a-drawingview-inside-a-scrollable-layout-in-net-maui-2a080e32cdb4?source=rss-aef820fb9519------2</link>
            <guid isPermaLink="false">https://medium.com/p/2a080e32cdb4</guid>
            <category><![CDATA[drawingview]]></category>
            <category><![CDATA[net-maui]]></category>
            <category><![CDATA[maui-community-toolkit]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[José Pereira]]></dc:creator>
            <pubDate>Sat, 25 May 2024 15:13:09 GMT</pubDate>
            <atom:updated>2024-05-25T15:13:09.336Z</atom:updated>
            <content:encoded><![CDATA[<h3>How to use a DrawingView inside a scrollable layout, in .NET MAUI</h3><p>Learn how to solve the interactivity issues, on iOS, when using the .NET MAUI Community Toolkit DrawingView inside a scrollable layout.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3ipffmTu7gjWZsZmpWlv1g.png" /></figure><p><strong>TLDR</strong>: Click <a href="https://github.com/zleao/iOSMauiDelayTouches">here</a> to check a fully working sample that has the fix for the problem explained and solved on this post.</p><h3><strong>The problem</strong></h3><p>If you have used the DrawingView control, from .NET MAUI Community Toolkit in your iOS apps, you might have come across an issue where it becomes difficult to interact with it, because the touch event is being captured by another control. <strong>This issue happens when the </strong><strong>DrawingView control is wrapped by a scrollable layout</strong> (eg. ScrollView, TableView). (See <a href="https://github.com/CommunityToolkit/Maui/issues/1807">GitHub issue</a>)</p><h3><strong>The explanation</strong></h3><p>This issue is caused by a specific behavior that exists on iOS. There is an implicit timer when a touch gesture begins in an UIScrollView, that is used by the platform to decide what to do with that gesture based on user action (see <a href="https://learn.microsoft.com/en-us/dotnet/maui/ios/platform-specifics/scrollview-content-touches?view=net-maui-8.0">here</a>). This timer is causing the gesture to be passed on to the ScrollView instead of the DrawingView control.</p><h3><strong>The solution</strong></h3><p>Good news is, that the UIScrollView control on iOS has a property called DelaysContentTouches<em> </em>that allows the developer to decide if such a delay timer should be used or not. By default this property is set to <em>true</em>, so we’ll need to explicitly disable it.</p><p><strong>NOTE:</strong> I’ll show you how you can solve this issue when using a ScrollView and TableView. If you’re using a different type of scrollable layout in your .NET MAUI app, the solution might be similar, so continue reading !</p><p><strong>Fix for ScrollView<br></strong>You can use an <a href="https://learn.microsoft.com/en-us/dotnet/maui/ios/platform-specifics/scrollview-content-touches?view=net-maui-8.0">extension</a> provided by .NET MAUI that allows you to directly control the value of the DelaysContentTouches<em>.</em></p><pre>&lt;ContentPage<br>    xmlns:ios=&quot;clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls&quot;&gt;<br><br>    &lt;ScrollView ios:ScrollView.ShouldDelayContentTouches=&quot;false&quot;&gt;<br><br>        &lt;DrawingView /&gt;<br><br>    &lt;/ScrollView&gt;<br><br>&lt;/ContentPage&gt;</pre><p><strong>Fix for TableView<br></strong>The extension to manipulate the DelaysContentTouches works specifically for ScrollView, so we’ll need to do a bit more work to solve the issue for TableView. <br>There are two possible solutions I can think of: <br> - create a specific extension for TableView<br>OR <br> - override the renderer of the TableView</p><p>I’ll show you the renderer approach, so that will get you going.</p><p>If you take a closer look at the <a href="https://github.com/dotnet/maui/blob/a7705a9ace36b375fcec23cabf98d28a71a08ecd/src/Controls/src/Core/Compatibility/Handlers/TableView/iOS/TableViewRenderer.cs#L12">implementation</a> of the TableViewRenderer for iOS, you’ll notice that the TableView is mapped to a UITableView. Furthermore, the UITableView is a subclass of UIScrollView, which means that it also has the same DelaysContentTouches<em> </em>property!</p><p>We just need to override the renderer and set this property to <em>true</em>!</p><pre>public class MyTableViewRenderer : TableViewRenderer<br>    {<br>        protected override void OnElementChanged(ElementChangedEventArgs&lt;TableView&gt; e)<br>        {<br>            base.OnElementChanged(e);<br><br>            if (Control != null)<br>            {<br>                // Apply the fix for the TableViewRenderer on iOS<br>                Control.DelaysContentTouches = false;<br>            }<br>        }<br>    }</pre><p>Don’t forget to register the custom renderer!</p><pre>public static MauiApp CreateMauiApp()<br> {<br>  var builder = MauiApp.CreateBuilder();<br>  builder<br>   .UseMauiApp&lt;App&gt;()<br>   .UseMauiCommunityToolkit()<br>   .ConfigureFonts(fonts =&gt;<br>   {<br>    fonts.AddFont(&quot;OpenSans-Regular.ttf&quot;, &quot;OpenSansRegular&quot;);<br>    fonts.AddFont(&quot;OpenSans-Semibold.ttf&quot;, &quot;OpenSansSemibold&quot;);<br>   })<br>            .ConfigureMauiHandlers((handlers) =&gt;<br>            {<br>#if IOS<br>                handlers.AddHandler(typeof(TableView), typeof(Platforms.iOS.Renderers.MyTableViewRenderer));<br>#endif<br>            });</pre><p><strong>Conclusion</strong><br>I’ve shown you how to avoid the annoying interaction delay when using a DrawingView is inside a scrollable layout, in .NET MAUI for iOS. Hope it’s helpful and feel free to share your thoughts about this post.</p><p><a href="https://github.com/zleao/iOSMauiDelayTouches"><strong>Here</strong></a><strong> </strong>you can find a working sample with the implementation suggestion s described on this post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a080e32cdb4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/medialesson/how-to-use-a-drawingview-inside-a-scrollable-layout-in-net-maui-2a080e32cdb4">How to use a DrawingView inside a scrollable layout, in .NET MAUI</a> was originally published in <a href="https://medium.com/medialesson">Medialesson</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Working with MongoDB On-Premises and Cloud environments]]></title>
            <link>https://medium.com/medialesson/working-with-mongodb-on-premises-and-cloud-environments-4551ca284b76?source=rss-aef820fb9519------2</link>
            <guid isPermaLink="false">https://medium.com/p/4551ca284b76</guid>
            <category><![CDATA[sharding]]></category>
            <category><![CDATA[mongodb]]></category>
            <category><![CDATA[cosmosdb]]></category>
            <category><![CDATA[c-sharp-programming]]></category>
            <category><![CDATA[azure]]></category>
            <dc:creator><![CDATA[José Pereira]]></dc:creator>
            <pubDate>Wed, 15 Jul 2020 09:52:14 GMT</pubDate>
            <atom:updated>2020-07-15T09:52:14.927Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xArjla1N3x1TKYUxPgAo5A.png" /></figure><h4>How to work with MongoDB in hybrid environments using On-Premises and Azure Cloud.</h4><h4><strong>Introduction</strong></h4><p>When creating a system that involves accessing a database, it is very common to use a local database environment during development. In some cases, a local database engine might not have the same characteristics as the production one. One example is a <strong>On-Premises </strong><a href="https://www.mongodb.com/"><strong>MongoDB</strong></a> versus an <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/introduction"><strong>Azure Cosmos DB</strong></a><strong> account</strong>.</p><p>One of the main differences we’ll find between these two environments (On-Premises and Cloud) is the fact that in Azure Cosmos DB we need to take the <strong>t</strong><a href="https://docs.microsoft.com/en-us/azure/cosmos-db/request-units"><strong>hroughput</strong></a> into account when creating databases and collections. Throughput determines the amount of resources that an Azure Cosmos DB instance will have available to operate. To make things a bit more dramatic, managing the throughput correctly is imperative to avoid having to pay for unnecessary resources at the end of the month. In an On-Premises environment, this is usually not something we need to be concerned about.</p><p>In this story I’ll show you an approach to handle both environments (On-Premises and Cloud), taking into account that the throughput and configuration is needed for each one.</p><h4><strong>Getting ready</strong></h4><p>The complete source code of this solution (including a small usage sample), can be found <a href="https://github.com/zleao/PortableMongoDB">here</a>.<br><strong>To run it you need</strong>: <br> - VS2017+<br> - MongoDB 3.2 (for On-Premises instalation click <a href="https://docs.mongodb.com/v3.2/tutorial/install-mongodb-on-windows/">here</a>) <br> - Azure Cosmos DB 3.2 (to create a new account click <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/create-cosmosdb-resources-portal">here</a>)<br><strong>Important nuget packages</strong>:<br> - Microsoft.Azure.DocumentDB.Core<br> - MongoDB.Driver</p><p><strong>NOTE:</strong> If you’re creating a new Azure Cosmos DB account, be sure to select the correct API and Version</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*URT3E3iObkLv5cFECwCBPg.png" /><figcaption>Azure Cosmos DB API and Version</figcaption></figure><h4><strong>Implementation</strong></h4><p>The <a href="https://github.com/zleao/PortableMongoDB">full source code</a> includes all the basic implementation expected in a database access service (create, delete, update), so feel free to explore it. In this section I’ll focus only on the main parts related to the handling of the different database environments.</p><p><strong>Environments configuration</strong><br>Since we are dealing with different environments, each with its own particularities, we´ll need to have a way to switch between them. For that we use a <strong>configuration </strong>class.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b0da76441afb0062a65b3493b4651006/href">https://medium.com/media/b0da76441afb0062a65b3493b4651006/href</a></iframe><p>The configuration class holds several properties, some specific to a particular environment, but here are three properties that are common to all environments:<br>- <strong>DatabaseLocation</strong>:<em> Defined by the DbLocation enum.<br>- </em><strong>DatabaseName</strong>: <em>Name of the database to create/connect.<br>- </em><strong>Connection string</strong>: <em>Connection string to the database instance.</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e26c80d8aa165fde8bddf28a1ccfc3c7/href">https://medium.com/media/e26c80d8aa165fde8bddf28a1ccfc3c7/href</a></iframe><p><strong>NOTE:</strong> The connection string for the Azure Cosmos DB service can be obtained after the account is created by going to the menu option “Connection Strings”. There you´ll find the connection string that you need to include in the configuration for the Cloud environment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ooGmRx5S3vySW1sXoFGDxQ.png" /><figcaption>How to get Azure Cosmos DB account ‘Connection String’</figcaption></figure><p>In the provided sample code we have a <a href="https://github.com/zleao/PortableMongoDB/blob/master/src/MongoDb/MongoDbFactory.cs">MongoDbFactory</a> class that handles the database creation. This class receives the configuration as a constructor parameter and uses it to define which code to execute depending on the defined DatabaseLocation.</p><p>Continue to read to take a look at how we deal with the database creation in the different environments!</p><p><strong>Database Creation</strong><br>With the configuration in place, the second step is the database creation. <br>For <em>DbLocation.Local</em> environments, the creation is fairly straightforward. In fact, creating a database in an On-Premises <em>pure</em> MongoDB instance it’s as simple as calling the <em>GetDatabase </em>method found in <em>MongoDB.Driver.MongoClient</em>. Check out <a href="https://www.codementor.io/@pmbanugo/working-with-mongodb-in-net-1-basics-g4frivcvz">this post</a> for a more detailed explanation on how to use MongoDB driver in On-Premises instances.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4d72da4a826abd626ee4916b951c6a48/href">https://medium.com/media/4d72da4a826abd626ee4916b951c6a48/href</a></iframe><p>Creating a database in Azure Cosmos DB involves also defining the throughput value. Although this is not a mandatory step, we wanted to be sure of the throughput that was assigned to a new database.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1b5c01243a51ccf34c3fbcf5129b310e/href">https://medium.com/media/1b5c01243a51ccf34c3fbcf5129b310e/href</a></iframe><p>There are 2 major steps in the above code, to create a database on Azure Cosmos DB:</p><ul><li><strong>First step</strong> is to create a connection to the Azure Cosmos DB service, using the <strong>DocumentClient</strong> class provided in the <em>Microsoft.Azure.DocumentDB.Core</em> nuget package. This is done in the <em>GetDocumentClient</em> method.</li><li><strong>Second step</strong> is to create the database, using the <em>DocumentClient.CreateDatabaseIfNotExistAsync</em> method. The special part in this call is the <strong>RequestOptions</strong> class that allows you to define the throughput to assign to the database when creating it. More info on how to manually provision throughput in Azure Cosmos DB, can be found <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/how-to-provision-database-throughput?tabs=dotnetv2">here</a>.</li></ul><p><strong>Creating Collections</strong><br>So far so good! We have a database created and now we’ll add some collections. In our case, we used <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/set-throughput">shared throughput</a> which means that we had to create <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/partition-data">sharded/partitioned collections</a>.</p><p>In layman´s terms, creating a sharded collection means that you must explicitly define the partition key of the collection. You can do that by sending a command to the database. MongoDB commands and queries are all JSON documents, so we can use the driver’s <strong>BsonDocument</strong> class to construct the command (click <a href="https://blog.jeremylikness.com/blog/2018-07-23_what-the-shard/">here</a> to get more info on this topic). The code to create a sharded collection looks like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b6ca6741bf056d5d640995896c8d8500/href">https://medium.com/media/b6ca6741bf056d5d640995896c8d8500/href</a></iframe><p>The <em>shardCollection</em> is the fully qualified database and collection name followed by the hashed key.</p><p>The more generic way of using the MongoDb Driver to create a collection is by calling the <em>CreateCollectionAsync</em> method that takes the collection name. To be sure that we also support On-Premises pure MongoDB instances, we should ensure that it has a fallback method to the shard collection creation. The code would look something like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e419f09ebdf146e84890574f76a12fef/href">https://medium.com/media/e419f09ebdf146e84890574f76a12fef/href</a></iframe><p><strong>Throughput management</strong><br>And finally we reach the <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/set-throughput">throughput management</a>. Our use case forced us to have a higher throughput at startup to be able to import large quantity of data. Once that data was imported, the throughput is scaled down to reduce unnecessary costs.</p><p>We calculate the final shared throughput based on the number of existing collections, so the first step is to query the database to get the count of existing collections. The code for that looks like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/806d2e9bd08bebadfb585561a2a5bb21/href">https://medium.com/media/806d2e9bd08bebadfb585561a2a5bb21/href</a></iframe><p>To actually change the throughput, we use the method <em>ReplaceOfferAsync</em>. This method comes from the<em> </em><strong>DocumentClient</strong> class provided in the <em>Microsoft.Azure.DocumentDB.Core</em> nuget package. The code to make the throughput change looks like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c81669eeb1403922a93a30032080591a/href">https://medium.com/media/c81669eeb1403922a93a30032080591a/href</a></iframe><p><strong>Note</strong> that there´s a <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/set-throughput#comparison-of-models">minimium limit for the defined throughput</a> (in our case 400), so we must ensure that the value of the offer is above that limit. To make that calculation we assign an arbitrary value of 100 for each existing collection and we make sure that the final value is not bellow 400:</p><ul><li><em>Math.Max(400, (collectionCount * 100))</em></li></ul><h4><strong>Summary</strong></h4><p>In the end we get an ‘abstraction’ layer that allows us to seamlessly change between different MongoDB database environments. The environment swap is achieved by using an environment specific configuration that will provide the necessary information to decide on how to interact with the database service.</p><p>This implementation targets MongoDB v3.2, but currently Azure Cosmos DB supports version v3.6, meaning that as a future implementation, one could expand this approach to also support v3.6 databases.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4551ca284b76" width="1" height="1" alt=""><hr><p><a href="https://medium.com/medialesson/working-with-mongodb-on-premises-and-cloud-environments-4551ca284b76">Working with MongoDB On-Premises and Cloud environments</a> was originally published in <a href="https://medium.com/medialesson">Medialesson</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>