102
votes

Is there any way using Xamarin Forms (not Android or iOS specific) to have a pop-up, like Android does with Toast, that needs no user interaction and goes away after a (short) period of time?

From searching around all I'm seeing are alerts that need user clicks to go away.

20

20 Answers

192
votes

There is a simple solution for this. By using the DependencyService you can easily get the Toast-Like approach in both Android and iOS.

Create an interface in your common package.

public interface IMessage
{
    void LongAlert(string message);
    void ShortAlert(string message);
}

Android section

[assembly: Xamarin.Forms.Dependency(typeof(MessageAndroid))]
namespace Your.Namespace
{
    public class MessageAndroid : IMessage
    {
        public void LongAlert(string message)
        {
            Toast.MakeText(Application.Context, message, ToastLength.Long).Show();
        }

        public void ShortAlert(string message)
        {
            Toast.MakeText(Application.Context, message, ToastLength.Short).Show();
        }
    }
}

iOS section

In iOs there is no native solution like Toast, so we need to implement our own approach.

[assembly: Xamarin.Forms.Dependency(typeof(MessageIOS))]
namespace Bahwan.iOS
{
    public class MessageIOS : IMessage
    {
        const double LONG_DELAY = 3.5;
        const double SHORT_DELAY = 2.0;

        NSTimer alertDelay;
        UIAlertController alert;

        public void LongAlert(string message)
        {
            ShowAlert(message, LONG_DELAY);
        }
        public void ShortAlert(string message)
        {
            ShowAlert(message, SHORT_DELAY);
        }

        void ShowAlert(string message, double seconds)
        {
            alertDelay = NSTimer.CreateScheduledTimer(seconds, (obj) =>
            {
                dismissMessage();
            });
            alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
        }

        void dismissMessage()
        {
            if (alert != null)
            {
                alert.DismissViewController(true, null);
            }
            if (alertDelay != null)
            {
                alertDelay.Dispose();
            }
        }
    }
}

Please note that in each platform, we have to register our classes with DependencyService.

Now you can access out Toast service in anywhere in our project.

DependencyService.Get<IMessage>().ShortAlert(string message); 
DependencyService.Get<IMessage>().LongAlert(string message);
18
votes

Here's a version of Alex Chengalan's iOS code that avoids the UI sticking when multiple messages are shown...

public class MessageIOS : IMessage
    {
        const double LONG_DELAY = 3.5;
        const double SHORT_DELAY = 0.75;

        public void LongAlert(string message)
        {
            ShowAlert(message, LONG_DELAY);
        }

        public void ShortAlert(string message)
        {
            ShowAlert(message, SHORT_DELAY);
        }

        void ShowAlert(string message, double seconds)
        {
            var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);

            var alertDelay = NSTimer.CreateScheduledTimer(seconds, obj =>
            {
                DismissMessage(alert, obj);
            });

            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
        }

        void DismissMessage(UIAlertController alert, NSTimer alertDelay)
        {
            if (alert != null)
            {
                alert.DismissViewController(true, null);
            }

            if (alertDelay != null)
            {
                alertDelay.Dispose();
            }
        }
    }
12
votes

You can use Acr.UserDialogs Package from nuget and code like below,

Acr.UserDialogs.UserDialogs.Instance.Toast(Message, new TimeSpan(3));
9
votes

We'd normally use Egors Toasts plugin, but as it requires permissions on iOS for a current project we've gone a different route using Rg.Plugins.Popup nuget (https://github.com/rotorgames/Rg.Plugins.Popup).

I wrote a basic xaml/cs page of type PopupPage,

<?xml version="1.0" encoding="utf-8" ?>
<popup:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:popup="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
         x:Class="YourApp.Controls.ToastPage">
...

and have it created by a service, whose interface you register at app start or use Xamarin.Forms.DependencyService to fetch the service would be viable too.

The service news up the PopupPage derived page, and does

await PopupNavigation.PushAsync(newToastPage);
await Task.Delay(2000);
await PopupNavigation.PopAllAsync();

The Popup page can be dismissed by the user by tapping outside the page display (assuming it hasn't filled the screen).

This seems to work happily on iOS/Droid, but I'm open to correction if anyone knows what this is a risky way of doing it.

9
votes

Adding to Alex's answer, here's the UWP variant:

public class Message : IMessage {
  private const double LONG_DELAY = 3.5;
  private const double SHORT_DELAY = 2.0;

  public void LongAlert(string message) =>
    ShowMessage(message, LONG_DELAY);

  public void ShortAlert(string message) =>
    ShowMessage(message, SHORT_DELAY);

  private void ShowMessage(string message, double duration) {
    var label = new TextBlock {
      Text = message,
      Foreground = new SolidColorBrush(Windows.UI.Colors.White),
      HorizontalAlignment = HorizontalAlignment.Center,
      VerticalAlignment = VerticalAlignment.Center,
    };
    var style = new Style { TargetType = typeof(FlyoutPresenter) };
    style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Windows.UI.Colors.Black)));
    style.Setters.Add(new Setter(FrameworkElement.MaxHeightProperty, 1));
    var flyout = new Flyout {
      Content = label,
      Placement = FlyoutPlacementMode.Full,
      FlyoutPresenterStyle = style,
    };

    flyout.ShowAt(Window.Current.Content as FrameworkElement);

    var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(duration) };
    timer.Tick += (sender, e) => {
      timer.Stop();
      flyout.Hide();
    };
    timer.Start();
  }
}

Coloring and styling is up to you, the MaxHeightis actually required to keep the height at the minimum.

6
votes

You can use IUserDialog NuGet and simply use it's toastAlert

var toastConfig = new ToastConfig("Toasting...");
toastConfig.SetDuration(3000);
toastConfig.SetBackgroundColor(System.Drawing.Color.FromArgb(12, 131, 193));

UserDialogs.Instance.Toast(toastConfig);
5
votes

You can use SnackBar from Xamarin Community toolkit package, which uses native implementation in platforms where natively supported, because Toast is deprecated in API level 30, a SnackBar without an Action is equivalent to a Toast.

This method was deprecated in API level 30. Custom toast views are deprecated. Apps can create a standard text toast with the makeText(android.content.Context, java.lang.CharSequence, int) method, or use a Snackbar when in the foreground. Starting from Android Build.VERSION_CODES#R, apps targeting API level Build.VERSION_CODES#R or higher that are in the background will not have custom toast views displayed. (source).

Starting with Xamarin Community toolkit

  1. Install the Package on all your projects
  2. include the namespace using Xamarin.CommunityToolkit.Extensions;
  3. In your page code-behind show a SnackBar upon an event
await this.DisplayToastAsync("This is a Toast Message");
await this.DisplayToastAsync("This is a Toast Message for 5 seconds", 5000);

You may specify a duration for the SnackBar to disappear (in milliseconds) or leave the default one which equals 3 seconds.

enter image description here


Resources

SnackBar Sample

Official Repo https://github.com/xamarin/XamarinCommunityToolkit

Official Docs https://docs.microsoft.com/en-us/xamarin/community-toolkit/


EDIT

  1. Anchored Toast: You may anchor the toast above a view (like screenshot above) by simply calling the extension method DisplayToastAsync() from that view (anchor) object instead from the page instance (this):
<Button x:name="floatingButton" .../>

await floatingButton.DisplayToastAsync("This is a Toast Message for 5 seconds", 5000);
  1. Padding and corner radius: (starting from xct version 1.3.0 preview-1)

You can set the corner radius and the padding for your Toast like the following example:

var messageOptions = new MessageOptions
{
    Message = "Toast with Padding and round corner",
    Foreground = Color.White,
    Font = Font.SystemFontOfSize(16),
    Padding = new Thickness(20)
};

var options = new ToastOptions
    {
        MessageOptions = messageOptions,
        CornerRadius = new Thickness(40, 40, 0, 0),
        BackgroundColor = Color.FromHex("#CC0000")
    };

await this.DisplayToastAsync(options);

enter image description here

enter image description here

PS: The same properties could be applied for the SnackBar view.

4
votes

Here is a code snippet that I am using to show the toast in Xamarin.iOS

  public void ShowToast(String message, UIView view)
    {
        UIView residualView = view.ViewWithTag(1989);
        if (residualView != null)
            residualView.RemoveFromSuperview();

        var viewBack = new UIView(new CoreGraphics.CGRect(83, 0, 300, 100));
        viewBack.BackgroundColor = UIColor.Black;
        viewBack.Tag = 1989;
        UILabel lblMsg = new UILabel(new CoreGraphics.CGRect(0, 20, 300, 60));
        lblMsg.Lines = 2;
        lblMsg.Text = message;
        lblMsg.TextColor = UIColor.White;
        lblMsg.TextAlignment = UITextAlignment.Center;
        viewBack.Center = view.Center;
        viewBack.AddSubview(lblMsg);
        view.AddSubview(viewBack);
        roundtheCorner(viewBack);
        UIView.BeginAnimations("Toast");
        UIView.SetAnimationDuration(3.0f);
        viewBack.Alpha = 0.0f;
        UIView.CommitAnimations();
    }
4
votes

I would recommend Plugin.Toast library from nuget. It works well.

CrossToastPopUp.Current.ShowToastMessage("my toast message");

or from ACR.UserDialogs Nuget libriary

UserDialogs.Instance.ShowLoading("Loading");
4
votes

@MengTim, to fix the multiple toast issue in @alex-chengalan's solution, I simply wrapped everything within ShowAlert() with a check to see if alert and alertDelay are null, then within DismissMessage, nulled out alert and alertDelay.

void ShowAlert(string message, double seconds)
    {
        if(alert == null && alertDelay == null) {
            alertDelay = NSTimer.CreateScheduledTimer(seconds, (obj) =>
            {
                DismissMessage();
            });
            alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
        }
    }

    void DismissMessage()
    {
        if (alert != null)
        {
            alert.DismissViewController(true, null);
            alert = null;
        }
        if (alertDelay != null)
        {
            alertDelay.Dispose();
            alertDelay = null;
        }
    }

That seemed to at least clear up the UI hang, if you are looking for a quick fix. I was trying to display the toast on navigation to a new page, and believe that the PresentViewController being set was essentially cancelling out my navigation. Sorry I did not comment within the thread, my reputation is too low :(

3
votes

This is my improved ShowAlert version of Ian Warburton's version to ensure that the toast is displayed even on popup page. Furthermore, the toast is dissmissed if the user click outside the toast. I used UIAlertControllerStyle.ActionSheet that look likes toast but it also work with UIAlertControllerStyle.Alert

    void ShowAlert(string message, double seconds)
    {
        var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.ActionSheet);

        var alertDelay = NSTimer.CreateScheduledTimer(seconds, obj =>
        {
            DismissMessage(alert, obj);
        });

        var viewController = UIApplication.SharedApplication.KeyWindow.RootViewController;
        while (viewController.PresentedViewController != null)
        {
            viewController = viewController.PresentedViewController;
        }
        viewController.PresentViewController(alert, true, () =>
        {
            UITapGestureRecognizer tapGesture = new UITapGestureRecognizer(_ => DismissMessage(alert, null));
            alert.View.Superview?.Subviews[0].AddGestureRecognizer(tapGesture);
        });
    }

I hope this will help someone !

1
votes

There is no built-in mechanism in Forms, but this nuget package supplies something similar

https://github.com/EgorBo/Toasts.Forms.Plugin

Note: These are not Android style toasts as requested in the question but UWP style toasts which are system wide notifications.

1
votes

I use https://github.com/ishrakland/Toast/ In https://www.nuget.org/packages/Plugin.Toast/

Example CrossToastPopUp.Current.ShowToastMessage ("Loading", Plugin.Toast.Abstractions.ToastLength.Short);

Try it, it's great

1
votes

I customised a custom popup with Rg.Plugins.Popup NuGet this is an example:

 <pages:PopupPage.Animation>
    <animations:ScaleAnimation 
        PositionIn="Center"
        PositionOut="Center"
        ScaleIn="1.2"
        ScaleOut="0.8"
        DurationIn="600"
        DurationOut="600"
        EasingIn="Linear"
       EasingOut="Linear"/>
</pages:PopupPage.Animation>

<Frame CornerRadius="10"  
    HeightRequest="30"
       VerticalOptions="End"
       HorizontalOptions="Fill"
       HasShadow="False"
        Padding="0" Margin="40,50"
       OutlineColor="LightGray">
    <StackLayout 
    Opacity="0.4"
       BackgroundColor="White">
    <Label
        x:Name="lbl"
        LineBreakMode="WordWrap"
        HorizontalTextAlignment="Center"
                    VerticalTextAlignment="Center"

        VerticalOptions="CenterAndExpand"
        HorizontalOptions="Center" TextColor="Black" FontSize="12">
                <Label.FontFamily>
                    <OnPlatform x:TypeArguments="x:String">
                        <On Platform="iOS" Value="NewJuneMedium" />
                    </OnPlatform>
                </Label.FontFamily>
            </Label>
</StackLayout>
    </Frame>

then in your basecontentpage you can add the following code, to show and hide the "toast" after a while:

public async void showpopup(string msg)
    {
        await Navigation.PushPopupAsync(new Toast(msg));
        await Task.Delay(3000);
        await Navigation.PopPopupAsync(true);   
    }
0
votes

The iOS answers above worked for me but for one little problem -- a warning: Attempt to present UIAlertController ... whose view is not in the window hierarchy!

After some search, I came across this unrelated answer which helped. The poster commented "This looks stupid but works", which is right on both counts.

So, I modified the ShowAlert() function above with these lines, which seem to work:

    var rootVC = UIApplication.SharedApplication.KeyWindow.RootViewController;
    while ( rootVC.PresentedViewController != null) {
        rootVC = rootVC.PresentedViewController;
    }
    rootVC.PresentViewController( alert, true, null);
0
votes

For UWP

public void ShowMessageFast(string message)
    {
        ToastNotifier ToastNotifier = ToastNotificationManager.CreateToastNotifier();
        Windows.Data.Xml.Dom.XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
        Windows.Data.Xml.Dom.XmlNodeList toastNodeList = toastXml.GetElementsByTagName("text");
        toastNodeList.Item(0).AppendChild(toastXml.CreateTextNode("Test"));
        toastNodeList.Item(1).AppendChild(toastXml.CreateTextNode(message));
        Windows.Data.Xml.Dom.IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
        Windows.Data.Xml.Dom.XmlElement audio = toastXml.CreateElement("audio");
        audio.SetAttribute("src", "ms-winsoundevent:Notification.SMS");

        ToastNotification toast = new ToastNotification(toastXml);
        toast.ExpirationTime = DateTime.Now.AddSeconds(4);
        ToastNotifier.Show(toast);
    }
0
votes

Install nuget Acr.UserDialogs. It contains Toasts exactly what you are looking for.

ToastEvent toastEvent = new ToastEvent();
var toastConfig = new ToastConfig(toastEvent,"Toasting...","");
toastConfig.SetDuration(2000);

UserDialogs.Instance.Toast(toastConfig);
0
votes

Currently use xamarin essential in android:

//access mainthread
MainThread.BeginInvokeOnMainThread(() =>
{
     Toast.MakeText(Application.Context, message, ToastLength.Short).Show();       
});
0
votes

Adding Alex's code, for UWP variant, I found a great implementation here https://www.c-sharpcorner.com/article/xamarin/

Just come and leave a clap for him :)

[assembly:Xamarin.Forms.Dependency(typeof(Toast_UWP))]  
namespace ToastMessage.UWP  
{  
    class Toast_UWP : Toast  
    {  
        public void Show(string message)  
        {  
            ToastTemplateType toastTemplate = ToastTemplateType.ToastImageAndText01;  
            XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);  
  
            XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");  
            toastTextElements[0].AppendChild(toastXml.CreateTextNode(message));  
              
            XmlNodeList toastImageAttributes = toastXml.GetElementsByTagName("image");  
            ((XmlElement)toastImageAttributes[0]).SetAttribute("src", "ms-appx:///Assets/Logo.scale-240.png");  
            ((XmlElement)toastImageAttributes[0]).SetAttribute("alt", "logo");  
  
            IXmlNode toastNode = toastXml.SelectSingleNode("/toast");  
            ((XmlElement)toastNode).SetAttribute("duration", "short");  
  
            var toastNavigationUriString = "#/MainPage.xaml?param1=12345";  
            var toastElement = ((XmlElement)toastXml.SelectSingleNode("/toast"));  
            toastElement.SetAttribute("launch", toastNavigationUriString);  
  
            ToastNotification toast = new ToastNotification(toastXml);  
  
            ToastNotificationManager.CreateToastNotifier().Show(toast);  
        }  
    }  
}   

By default, your messages will be queued and notified one after one, delayed based on the message duration. If you want to replace the existing message by the new one immediately just add more code like below

ToastNotificationManager.History.Remove("YOUR_TAG");

// Code to create Toast message, like the above method

toast.Tag = "YOUR_TAG";

If you want to add audio to your toast message, add this to your code

var audio = toastXml.CreateElement("audio");
audio.SetAttribute("src", "ms-winsoundevent:Notification.Default");
-5
votes

You can use DisplayAlert("", "", "", "" );