How to Make In-App Notifications

March 22, 2018
game unity top stick tutorial notification csharp in-app

If you want to add in-app notifications to let the user know what’s going on in the game/app, here is how. In the end, you are going to have something like this:

Result Notifications popping…



OK. Tutorial Shall Commence!. First, start off with designing a Notification Bar image. Since my game is about sticks (Top Stick), I left it with its default image and just changed the size and color of it. Your Notification Bar can be different based on the theme of your game. Anyways.

1. Create an Image object. Uncheck Raycast Target if you are not going to make it a Button (Canvas and EventSystem will be created after this step).
2. (Optional but necessary) Change Canvas’ UI Scale Mode to Scale With Screen Size.
3. Add a Text field to the Image as its child (Uncheck Raycast Target, Check Best Fit and Align with Geometry).
4. Adjust the position of the Image. I put it just below the canvas. It could be above the canvas also. Up to you.

So, finally, you should have a hierarchy like Canvas -> Image -> Text.
I changed their names to NotificationCanvas -> NotificationBar -> Message. This step looks like this:

Locating The Notification Bar Locate the notification bar just below the canvas.


Now it’s time to write some code. The next step is to make the notification go up, stay there for a while, and go down. Your animation can be different. It can come from the left and stay on the screen for a while and head to the right. Maybe it fades out or explodes a few secs later. Who knows.
(PS: This step may seem unnecessary for you because we could use Animation for this. But, meh, I like it this way.)

OK, What do we need to make this happen?:

1. offScreenPosition, onScreenPosition and lerpingPosition.
2. A waitTime for duration that it stays still for.
3. A notification message.
4. A callBack type (delegate) to notify that it has finished.

When a notification starts, it should go up, wait there for waitTime seconds and go back down. (To me) It’s better to make it go down faster than moving it up. Start() function sets the direction (lerpingPosition) and starts the coroutine. NotificationBarMovement() function makes the things explained above. Until the bar reaches ( < 2.0f) to the destination (onScreenPosition/lerpingPosition) it translates the Image. Then, waits for waitTime seconds, and translates the Image down. When this animations is completed, rectTrans.gameObject.SetActive(false) must be called. Also, we need SetNotificationBarFeatures() function to set the notification’s fields, such as the message, waitTime, and a callBack function. (You can add Color too) SetNotificationBarFeatures() function will be called by NotificationManager. Shhh, calm down! I will talk about the NotificationManager right after this.

The C# script is here:

NotificationBarMover.cs

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class NotificationBarMover : MonoBehaviour {

    private Vector2 onScreenPosition = new Vector2(0.0f, -180.0f); // Will Be Seen 
    private Vector2 offScreenPosition = new Vector2(0.0f, -270.0f); // Will Be Below The Screen

    private Vector2 lerpingPosition; // Where the bar goes to.
    private RectTransform rectTrans; // Transform for UI Components
    public Text notificationText; // Notification Text/Message Field

    public delegate void NotificationBarDestroyed(); // Callback Type

    private NotificationBarDestroyed destroyedCallBack; // Callback to Be Called When Destroyed

    private float waitTime = 2.0f;  // Visible Wait Time

    void Start()
    {
        rectTrans = GetComponent<RectTransform>();
        rectTrans.anchoredPosition = offScreenPosition;

        lerpingPosition = onScreenPosition;

        gameObject.SetActive(true);

        StartCoroutine(NotificationBarMovement());



    }

    private IEnumerator NotificationBarMovement()
    {

        // Go To The onScreenPosition 
        while (Vector2.Distance(rectTrans.anchoredPosition, onScreenPosition) > 2.0f)
        {
            rectTrans.anchoredPosition = Vector2.Lerp(rectTrans.anchoredPosition, lerpingPosition, Time.deltaTime * 2.8f);
            yield return null;
        }

        // Wait On The Screen
        yield return new WaitForSeconds(waitTime);

        lerpingPosition = offScreenPosition;// Change direction to offScrennPosition

        // Go To The offScreenPosition, but faster.
        while (Vector2.Distance(rectTrans.anchoredPosition, offScreenPosition) > 2.0f)
        {
            rectTrans.anchoredPosition = Vector2.Lerp(rectTrans.anchoredPosition, lerpingPosition, Time.deltaTime * 3.5f);
            yield return null;
        }

        // Before Destroying, make it Inactive
        rectTrans.gameObject.SetActive(false);

        yield break;
    }

    void OnDisable()
    {
        destroyedCallBack(); // Callback
        Destroy(gameObject.GetComponentInParent<RectTransform>().gameObject); // Destroy The Parent.
    }


    public void SetNotificationBarFeatures(string message, float waitTime ,NotificationBarDestroyed callBack)
    {
        notificationText.text = message;
        destroyedCallBack = callBack;
        this.waitTime = waitTime;
    }

}
Attach this script to NotificationBar/Image GameObject.

Once you’ve added the code to the Image/NotificationBar object, assign its notificationText field. Drag & drop the Text/Message field under the NotificationBar to that spot.
If you don’t have a Prefabs folder, create it. And drag this NotificationCanvas to that folder. In other words, make the NotificationCanvas a Prefab.
Like this:

Add Script And Make Prefab
Create a Canvas prefab



Having the prefab first is going to make the testing easier. That’s done. Now let’s create another script named NotificationManager. We are going to call the functions of this class to pop up a notification. This means that we need to be able to access it throughout the game. For that reason, we are going make it Singleton. Also, to make it persistent, we have to add DontDestroyOnLoad(gameObject).
NotificationManager has to show the notifications one by one if there is more than one notification at the time. For example, say the user tried to sign in but there was no Internet connection, then you would say ‘Sign In Failed’ and the reason ‘No Connection’ back-to-back. Keeping notifications in a Queue serves this purpose. The Queue holds objects, so we need a Notification type. A struct is more than enough to keep track of the notification data. Basically, a notification holds the message and how long it is going to display. You can add some other stuff too, like Color or maybe animation type (left to right, up to bottom etc).
I made an enum for duration options. enum NotificationLength{ SHORT, MID, LONG };
Also, I have another enum to know what this notification about. NotificationType{ LOGIN_FAILED, NO_CONNECTION, SUCCESS, CUSTOM};. You can add more to this. These are just examples. Depending on the NotificationLength, you need to decide the actual time length. And depending on the NotificationType, you can customize the notification message.

Create a function named PushNotification with type, length, and additional message parameters. Also, create a coroutine to display the notifications. PushNotification is easy, let’s talk about ShowNotification coroutine. Here is the logic:
While there is notification waiting in the queue
* Wait until the current notification destroys itself (of course, if there is a notification displaying already).
* Then create notification based on the next Notification in the Queue.

PushNotification function, however, doesn’t do much. It just creates and adds the new Notification to the queue. And If the coroutine is now running, it runs it (again). The function NotificationDestroyed is called by NotificationBarMover class, whenever it disables itself. The rest of the code explains itself, so I shall no longer talk!!.

Here is the code:

NotificationManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;



public class NotificationManager : MonoBehaviour {

    public enum NotificationType{ LOGIN_FAILED, NO_CONNECTION, SUCCESS, CUSTOM }; // Notification Types For Different Causes. Can Be Altered.
    public enum NotificationLength{ SHORT, MID, LONG }; // Notification Durations.

    public static NotificationManager Instance;
    public GameObject notificationBar; // Notification Bar Prefab
    

    private struct Notification // Notification Struct To Store The Information
    {
        public string message;
        public float time;
        public Notification(string m, float t) { message = m; time = t; }
    }

    private bool isShowNotificationActive; // Is ShowNotification Function Running Now?
    private bool isDisplaying; // Is There A Notification Showing Already?
    private Queue<Notification> notificationBuffer; // Queue For Notifications. (For Not Showing Them At The Same Time)


    void Awake()
    {
		if (Instance == null){
			Instance = this;
		}
		else if(Instance != this){
			Destroy(gameObject);
		}
        
		
		notificationBuffer = new Queue<Notification>();
        isDisplaying = isShowNotificationActive = false;
        DontDestroyOnLoad(gameObject);// This GameObject Is Persistent Thru The Scenes.
    }


    public void PushNotification(NotificationType type, NotificationLength len, string message = "")
    {
        Notification n = new Notification(GetMessageFromType(type) + message, GetTimeFromLength(len)); // Create A Notification Object
        notificationBuffer.Enqueue(n); // Add The Notification To The Queue
        
        if ( ! isShowNotificationActive) // If Coroutine Is Not Running, Start It
        {
            StartCoroutine(ShowNotification());
        }
    }


    private IEnumerator ShowNotification()
    {
        isShowNotificationActive = true; // The Function Started

        while (notificationBuffer.Count != 0) // While There Is Notification Object Waiting In The Queue
        {
            
            while (isDisplaying)    // While There Is Already A Notification Showing.
            {
                yield return new WaitForSeconds(1.0f);  // Wait, Until NotificationDestroyed Gets Called By NotificationBarMover
            }

            if (notificationBuffer.Count == 0) break; // notificationBuffer may become empty while this function was waiting in isDisplaying loop above.

            isDisplaying = true; // The Notification Is Showing
            GameObject nb = Instantiate(notificationBar); // Crate A NotificationBar (Prefab)
            Notification n = notificationBuffer.Peek(); // Get The Top Notification
            nb.GetComponentInChildren<NotificationBarMover>().SetNotificationBarFeatures(n.message, n.time, NotificationDestroyed);// Set The Members of NotificationBar

        }
        isShowNotificationActive = false; // The Function Finished
        yield break;
    }


    private void NotificationDestroyed()
    {
        notificationBuffer.Dequeue(); // Pop the Notification
        isDisplaying = false; // And It Is Not Displaying Anymore
    }

    
    private string GetMessageFromType(NotificationType type) // Get The Initial String For The Notification Type
    {
        switch (type)
        {
            case NotificationType.NO_CONNECTION:
                return "No Connection";
            case NotificationType.LOGIN_FAILED:
                return "Login Failed";
            case NotificationType.SUCCESS:
                return "Success";
            case NotificationType.CUSTOM:
                return "";
            default:
                return "";
        }
    }

    private float GetTimeFromLength(NotificationLength len) // Get The Length Based On The Length Type. Values Can Be Changed
    {
        switch (len)
        {
            case NotificationLength.LONG:
                return 3.0f;
            case NotificationLength.MID:
                return 2.0f;
            case NotificationLength.SHORT:
                return 1.0f;
            default:
                return 1.5f;
        }
    }
}
Create a GameObject named NotificationManager, and attach this script to it.



While the manager is making notification work as slaves, let us go to the next step: TESTING, yaayy!
I added 3 buttons, one is for short notification and the others are for mid and long notifications. Now, just create a GameObject and attach the script.
Also assign your buttons’ OnClick() functions accordingly.

Here is the test script:

NotificationTest.cs

using UnityEngine;
public class NotificationTest : MonoBehaviour {

    private int count = 1;

    public void ShortNotification()
    {
        NotificationManager.Instance.PushNotification(
            NotificationManager.NotificationType.NO_CONNECTION,
            NotificationManager.NotificationLength.SHORT,
            " "+count.ToString()
            );
        count++;
    }

    public void MidNotification()
    {
        NotificationManager.Instance.PushNotification(
           NotificationManager.NotificationType.LOGIN_FAILED,
           NotificationManager.NotificationLength.MID,
           " "+count.ToString()
           );
        count++;
    }

    public void LongNotification()
    {
        NotificationManager.Instance.PushNotification(
           NotificationManager.NotificationType.CUSTOM,
           NotificationManager.NotificationLength.LONG,
           "This is Long "+count.ToString()
           );
        count++;
    }
}
Create a GameObject named NotificationTest, and attach this script to it.


This step looks like this:
Test Notifications Buttons for testing notifications.



Now you can click the buttons until you break it. You’ll see that notifications are not lost, and showing one at a time.
Like this:

Result Notifications popping…



And The Developer Lived Happily Ever After… The End.