0

I am working on mobile app using Xamarin Forms (not Android or iOS specific) and SQLite. To display toasts I am using "Xamarin.CommunityToolkit" library. I want to know how to work with toast or snackbars messages for example after updating or creating items in database with maintaining the MVVM pattern correctly. From where the toast should be created - view or viewmodel?

Actually I create toast in my viewmodel and I'm not sure that is correct. I want to make it universal and easy to use from any place in my project. For example I have a few items in my database and after delete each position I want to display toast message depending on the action result - if deleting was complete then info toast message that is ok, in other case display toast that was error during deleting item.

What I tried is code below - but I think this is ruining MVVM pattern.

Part of the view code:

<Frame.GestureRecognizers>
   <TapGestureRecognizer NumberOfTapsRequired="1"
      Command="{Binding Source={RelativeSource AncestorType={x:Type local:ReceiptViewModel}},
      Path=ReceiptTappedDelete}" CommandParameter="{Binding .}">
   </TapGestureRecognizer>
</Frame.GestureRecognizers>

Part of the viewmodel code:

public class ReceiptViewModel : BaseReceiptsViewModel
    {
        public Command LoadReceiptCommand { get; }
        public Command ReceiptTappedDelete { get; }
        public Command ClearAllReceiptsCommand { get; }
        public ObservableCollection<ReceiptInfo> ReceiptInfos { get; }

        public MessageOptions messageOptions;
        public ToastOptions toastOptions;

        public ReceiptViewModel(INavigation _navigation) {
            LoadReceiptCommand = new Command(async () => await ExecuteLoadReceiptCommand());
            ReceiptInfos = new ObservableCollection<ReceiptInfo>();
            ReceiptTappedDelete = new Command<ReceiptInfo>(OnDeleteReceipt);
            Navigation = _navigation;
        }   


        public void OnAppearing()
        {
            IsBusy = true;
        }

        async Task ExecuteLoadReceiptCommand()
        {
            IsBusy = true;
            try
            {
                ReceiptInfos.Clear();
                var receiptList = await App.ReceiptService.GetReceiptsAsync();

                foreach (var receipt in receiptList)
                {
                    ReceiptInfos.Add(receipt);
                }
            }
            catch (Exception) 
            {

            }
            finally 
            {
                IsBusy = false; 
            }
        }

        private async void OnDeleteReceipt(ReceiptInfo receipt)
        {
            if (receipt == null)
            {
                return;
            }

            var result = await App.ReceiptService.DeleteReceiptAsync(receipt.Receipt_ID);
            await ExecuteLoadReceiptCommand();


            if (result == true)
            {
                messageOptions = new MessageOptions
                {
                    Message = "Delete completed!",
                    Foreground = Color.White,
                    Font = Font.OfSize("Arial", 14)
                };

                toastOptions = new ToastOptions
                {
                    MessageOptions = messageOptions,
                    Duration = TimeSpan.FromMilliseconds(2000),
                    CornerRadius = new Thickness(10, 20, 30, 40),
                    BackgroundColor = Color.Green
                };
            } else
            {
                messageOptions = new MessageOptions
                {
                    Message = "Something goes wrong...",
                    Foreground = Color.Red
                };

                toastOptions = new ToastOptions
                {
                    MessageOptions = messageOptions,
                    Duration = TimeSpan.FromMilliseconds(2000),
                    CornerRadius = new Thickness(10, 20, 30, 40),
                    BackgroundColor = Color.LightBlue
                };
            }

            await Application.Current.MainPage.DisplayToastAsync(toastOptions);
        }
    }

Moreover I tried to display message on "Tapped" event in View:

<Frame.GestureRecognizers>
   <TapGestureRecognizer NumberOfTapsRequired="1"
      Command="{Binding Source={RelativeSource AncestorType={x:Type local:ReceiptViewModel}},
      Path=ReceiptTappedDelete}" CommandParameter="{Binding .}" Tapped="TapGestureRecognizer_Tapped">
   </TapGestureRecognizer>
</Frame.GestureRecognizers>

        private async void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            try
            {
                Image image = (Image)sender;
                TapGestureRecognizer tapGesture = (TapGestureRecognizer)image.GestureRecognizers[0];

                var messageOptions = new MessageOptions
                {
                    Message = "Anchored toast",
                    Foreground = Color.Red
                };

                var options = new ToastOptions
                {
                    MessageOptions = messageOptions,
                    Duration = TimeSpan.FromMilliseconds(5000),
                    CornerRadius = new Thickness(10, 20, 30, 40),
                    BackgroundColor = Color.LightBlue
                };

                await this.DisplayToastAsync(options);
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error", ex.Message.ToString(), "Ok");
            }
        }

Can someone tell me how to correctly use Toast messages with MVVM pattern?

  • 1
    MVVM is a pattern, not a hard and fast set of rules you must obey. If what you have works for you I would not get hung up over how "MVVM" it is. However, if it really bugs you, you could create a `IToast` interface and use DI to inject the implementation into your VM. That way your VM is not tied to a concrete implementation of Toast. – Jason Apr 27 '23 at 15:34

0 Answers0