Post

Xamarin.Forms UI/UX Challenges - Micuna Food - Part3

In this third part of the Challenge I will go beyond design, I will add functionality to the share icon making use of two of the most popular Xamarin.Forms libraries, which are Xamarin Community Toolkit and Xamarin.Essentials.

This publication is part of the Second Xamarin Advent Calendar in Spanish organized by Dr. Luis Beltrán, and of the third C# Advent Calendar in Spanish organized by Ing. Benjamín Camacho, thank you very much for being part of these great initiatives.


Screenshot and Toast Multiplatform

5-3-screenshot-and-toast-multiplatform Screenshot and Toast Multiplatform Design

Previously, the FoodDetailPage had the share button, that’s what the designer did, but if we think as users, the first thing that comes to mind would be ¿What am I going to share?, in the wake of that dilemma, it will start with a screenshot button to remove false perceptions.

Digging deeper into the code, in the Icons.xaml file located in the Styles folder, we will customize the screenshot icon with its respective properties, including its size and color.

1
2
3
4
5
6
7
8
<!--#region FontImageSource INTERFACES-->
<FontImageSource
    x:Key="icon_screenshot_solid"
    FontFamily="MonettelliFontIcons"
    Glyph="{x:Static FontAwesome:MonettelliFontIcons.icon_screenshot_solid}"
    Size="26"
    Color="{DynamicResource colPrim}" />
<!--#endregion-->

To change one icon to another in the same button, I use Visual State Manager, these icons are linked in the Source property of the TargetType “Image”, it is worth mentioning, that it is necessary to add an x:Key to use it in a control or layout similar to the TargetType.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--#region Visual States Manager-->
<Style
    x:Key="vsm_fontIconChanged"
    TargetType="Image">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup x:Name="CommonStates">

                <VisualState x:Name="Normal">
                    <VisualState.Setters>
                        <Setter Property="Source" Value="{StaticResource icon_screenshot_solid}" />
                    </VisualState.Setters>
                </VisualState>

                <VisualState x:Name="Selected">
                    <VisualState.Setters>
                        <Setter Property="Source" Value="{StaticResource icon_share_solid}" />
                    </VisualState.Setters>
                </VisualState>

            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>
<!--#endregion-->

In FoodDetailPage.xaml, I implement a non-visible CachedImage control, responsible for storing the image capture, and with x:Name “imageForScreenshot”, we will work on the code behind said xaml file.

1
2
3
4
5
6
7
<!--  Image for Screenshot  -->
<ffimageloading:CachedImage
    x:Name="imageForScreenshot"
    Grid.Row="3"
    Grid.Column="2"
    Grid.ColumnSpan="5"
    IsVisible="false" />

In Mode Xamarin.Essentials.Screenshot

Using GestureRecognizers, I create an event called ScreenshotAndShareFile_Tapped, and through the if statement, I indicate, that if the Source property of imageForScreenshot is null, execute the following process:

The screenshot variable is responsible for capturing the screen based on the dimensions of the physical device or emulator (Height and Width).

The stream variable will obtain the data flow of the screenshot through the OpenReadAsync() method.

Finally, the FromStream() method is the linchpin for create an image from the specified stream variable, all of this is stored in the Source property of imageForScreenshot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private async void ScreenshotAndShareFile_Tapped(object sender, EventArgs e)
{
    if (imageForScreenshot.Source == null)
    {
        // Mode Xamarin.Essentials.Screenshot
        var screenshot = await Screenshot.CaptureAsync();
        var stream = await screenshot.OpenReadAsync();
        imageForScreenshot.Source = ImageSource.FromStream(() => stream);

        // Visual State Manager here...

        // Toast Multiplatform here...
    }
    else
    {
        // File Share here...
    }
}

In Mode ImageFromXamarinUI

This library is so versatile, because we can create specific screenshots at the interface level, and it can be applied to different use cases. Very grateful to Dima Dimov for creating this amazing nuget package.

The following code fragment we can see that the x:Name called layoutMainParent is the parent GridLayout, therefore, my screenshot will be everything that encapsulates said layout.

1
2
3
4
5
6
7
8
9
10
11
12
13
<ContentPage.Content>

    <ScrollView>

        <Grid x:Name="layoutMainParent">

        <!--  Rest of the code here...  -->

        </Grid>

    </ScrollView>

</ContentPage.Content>

Continuing in the ScreenshotAndShareFile_Tapped event, the stream variable is in charge of adding the data flow, but now it will depend on the dimensions of the layoutMainParent, the parameter of the CaptureImageAsync() method assumes the background color, finally, the image is created with the same logic than Xamarin.Essentials.Screenshot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private async void ScreenshotAndShareFile_Tapped(object sender, EventArgs e)
{
    if (imageForScreenshot.Source == null)
    {
        // Mode ImageFromXamarinUI
        var stream = await layoutMainParent.CaptureImageAsync(Color.White);
        imageForScreenshot.Source = ImageSource.FromStream(() => stream);

        // Visual State Manager here...

        // Toast Multiplatform here...
    }
    else
    {
        // File Share here...
    }
}

When the button is touched, it changes to the “Selected” state (share icon), this process is done by the GoToState() method of VisualStateManager.

Now I enter the Xamarin Community Toolkit grounds, and I am excited that they add Toast Multiplatform, with a few lines of code we can indicate the message and the duration of the notification, all thanks to the DisplayToastAsync() method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<StackLayout
    Grid.Row="1"
    Grid.Column="6"
    Grid.ColumnSpan="2"
    xct:TouchEffect.NativeAnimation="True"
    xct:TouchEffect.AnimationDuration="300">
    <StackLayout.GestureRecognizers>
        <TapGestureRecognizer Tapped="ScreenshotAndShareFile_Tapped" />
    </StackLayout.GestureRecognizers>

    <Image
        x:Name="stateFontIcon"
        Style="{StaticResource vsm_fontIconChanged}"
        HorizontalOptions="Center"
        VerticalOptions="CenterAndExpand" />
</StackLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private async void ScreenshotAndShareFile_Tapped(object sender, EventArgs e)
{
    if (imageForScreenshot.Source == null)
    {
        // Mode ImageFromXamarinUI here...

        // Mode Xamarin.Essentials.Screenshot here...

        VisualStateManager.GoToState(stateFontIcon, "Selected");

        await this.DisplayToastAsync(message: "Successful screenshot", durationMilliseconds: 2000);
    }
    else
    {
        // File Share here...
    }
}

Share File

5-3-share-file Share File Design

Xamarin.Essentials has another great API called Share, which allows you to send different types of files to the installed applications of the physical device or emulator.

Now that we have our image capture and the share button, we proceed with the file submission flow:

The fileName is in charge of naming the image.

The fullPath combines fileName with the CacheDirectory property, that gets the location where temporary data can be stored, and also helps to send the file faster.

The byteArray variable stores the weight of the image in bytes, it should be noted, that CachedImage has two methods to obtain png and jpg images (in this case I use the GetImageAsPngAsync() method), thank you very much Daniel Luberda for creating this fantastic library.

To encapsulate byteArray in fullPath it is done with the WriteAllBytes() method, with this, we already have a fully packed file.

In the RequestAsync() method I generate a new instance of ShareFileRequest(), there we add the title and corresponding file, it should be noted that “image/png” is only to highlight that it is a png file, it is not necessary to add it.

And WHOLAA!!!, we can now send our image, all thanks to Xamarin.Essentials.Share.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private async void ScreenshotAndShareFile_Tapped(object sender, EventArgs e)
{
    if (imageForScreenshot.Source == null)
    {
        // Mode ImageFromXamarinUI here...

        // Mode Xamarin.Essentials.Screenshot here...

        // Visual State Manager here...

        // Toast Multiplatform here...
    }
    else
    {
        string fileName = "MicunaFood - " + foodDetailViewModel.SelectedFood.Name_Food + ".png";
        string fullPath = Path.Combine(FileSystem.CacheDirectory, fileName);

        var byteArray = await imageForScreenshot.GetImageAsPngAsync();
        File.WriteAllBytes(fullPath, byteArray);

        await Share.RequestAsync(new ShareFileRequest
        {
            Title = "Data Transfer " + foodDetailViewModel.SelectedFood.Name_Food,
            File = new ShareFile(fullPath, "image/png")
        });
    }
}

Send and Save File

5-3-send-and-save-file Send and Save File Design

In the image above, you can see that the Android device sends the file through Microsoft Outlook application, while on the iPhone, it saves the screenshot, both ways are feasible.

The idea is for the user to share and save the different foods that MicunaFood offers for future purchases.


Other Features

Repository class

To improve the management of the MicunaFood data to the different ViewModels, they were migrated to the static class called repository.

Performance Tip in FFImageLoading

The video by Daniel Luberda shows the importance of adding the CacheType property to improve images performance.

Using the CacheType property in FFImageLoading


The Result


Get the Code

All the code is open source, you can see it by clicking the following image:

5-x-github-repository


Resources


Conclusions

  • I love Xamarin.Essentials.Screenshot and ImageFromXamarinUI, different features, same goal.
  • In a single line of code you can add Toast Multiplatform, my respects to Xamarin.Community.Toolkit.
  • This post demonstrates that Xamarin.Essentials.Share doesn’t just send text (like the example in the documentation), but any type of file.
  • State changes can also be done in a single control, and Visual State Manager helps me achieve it.

Remember that you can give feedback and with the help of my repository draw your own conclusions, if you have any questions or constructive criticism write to me below this publication, thank you very much.

This post is licensed under CC BY 4.0 by the author.