Last Updated: 6/13/2022, 2:58:04 AM

# Friends

# Overview

AccelByte Cloud’s Friends service allows players to connect socially with other players. This service uses WebSocket to ensure all players get real-time updates about their Friend List. Players can manage their friends in several different ways, including:

  • Request Friend allows players to add another player as a friend using that player’s User ID. Players will be notified when they receive a friend request.
  • Accept Friend Request allows players to accept friend requests they’ve received. After the player accepts the request, the requester will be notified and the two players will be linked as friends.
  • Reject Friend Request allows players to deny a friend request from a player they don’t want to be friends with.
  • Cancel Friend Request allows players to cancel and remove a pending friend request they have sent to another player, in case they have changed their mind or sent the request to the wrong player.
  • Get Friendship Status shows players their friendship status with another player. Possible statuses include:
    • Not Friend indicates that the players are not connected as friends.
    • Outgoing Friend indicates that the friend request sent by the player to another player is still pending.
    • Incoming Friend indicates that the player has received a friend request that is still pending.
    • Friend indicates that the two players are connected as friends.
  • List of Friends shows the player all of the players they’re currently friends with.
  • List of Incoming Friends shows the player a list of players that have requested to be their friend. Players can accept or reject these requests.
  • List of Outgoing Friends shows players the list of pending friend requests they have sent to other players.
  • Unfriend allows players to stop being friends with other players. Unfriending a player will remove that player from their friend list. If the player wants to be friends with an unfriended player, they will have to send that player another friend request.
  • Bulk Synchronization Third-party Platform Friends allows players to connect their friends from third-party platforms to the AccelByte Cloud platform.

# Permissions

Permissions (opens new window) are used to grant access to specific resources within our services. Make sure your account has the following permissions before you attempt to manage friends in the Admin Portal.

Usage Resource Action
Add Friends Without Confirmation NAMESPACE:{namespace}:USER:{userId}:FRIENDS Create
Get List of Friends NAMESPACE:{namespace}:USER:{userId}:FRIENDS Read
Get Blocked Players by User ID ADMIN:NAMESPACE:{namespace}:USER:{userId}:PLAYER:BLOCK Read
Get Players Who Blocked This Player by User ID ADMIN:NAMESPACE:{namespace}:USER:{userId}:PLAYER:BLOCK Read
Configure a New 3rd Party Integration ADMIN:NAMESPACE:{namespace}:THIRDPARTY:CONFIG Create

Permissions work slightly differently depending on whether they are assigned to IAM Clients (opens new window) or Roles (opens new window) assigned to users. For more information, read the Authentication and Authorization (opens new window).

# Implementing Friends using the SDK

# Search for a Player

You can search for a player’s account information using their Display Name or Username as the query. You can configure the type of search by setting the enum of SearchType for Unity and EAccelByteSearchType for Unreal to DisplayName or Username.

# Friend Interactions

These are all features for how players can manage their friends.

# Retrieve a Player’s Public Code

You can retrieve a player’s public code using the PublicId parameter.

# Retrieve the Friend List

Use the following function to retrieve a friends list.

# Send a Friend Request using a User ID

The first step in making a friend is sending a friend request to another player. Use this code to send a friend request using a User ID.

# Send a Friend Request using a Public Code

The first step in making a friend is sending a friend request to another player. Use this code to send a friend request using a Public Code.

# Retrieve a List of Incoming Friend Requests

Use this function to retrieve all the information about incoming friend requests. This function retrieves user ID which you can use to accept or reject each request.

# Accept Incoming Friend Requests

After a friend request has been sent, the player who received the request can either accept or reject it. Use the following function to accept a friend request.

NOTE

To see a list of incoming friend requests, you can retrieve the incoming friend request list.

# Reject Incoming Friend Requests

You can reject the incoming friend request by their User ID. To get the user ID, you need to retrieve a list of incoming friend requests, copy the User ID, and store it somewhere safe for use in the following function.

# Retrieve a List of Outgoing Friend Requests

Use the following function to retrieve a list of outgoing friend requests.

# Cancel Outgoing Friend Requests

You can cancel outgoing friend requests using User ID. To get the user ID, you need to retrieve a list of outgoing friend requests, copy the User ID, and store it somewhere safe for the following function.

# Unfriend

Use the following function to unfriend another player.

# Friend Notifications

# Incoming Friend Notifications

When a player sends a friend request to another player, the receiver will get an incoming friend request notification. You’ll need to set up a delegate to enable this notification. Use the following function to implement the delegate for incoming friend request notifications.

# Accepted Friend Request Notification

Use the following function to set up a notification for when your friend accepts your friend request.

# Rejected Friend Request Notification

Use the following function to set up a notification for when a player rejects your friend request.

# Canceled Outgoing Friend Request Notification

Use the following function to set up a notification when another player cancels their friend request.

# Unfriend Notification

Use this function to set up a notification when a player unfriends another player.

# Block a Player

Blocking allows players to restrict other players from interacting with them. When one player blocks another, both players are prevented from:

  • Adding the other as a friend. If the players are already friends, they will be unfriended.
  • Sending or receiving messages to one another.
  • Seeing each other’s messages in global chat rooms.
  • Inviting each other to parties.
  • Meeting each other in matches.
  • Being placed in the same party. (Unless they are both invited by a third player. When this occurs, the two blocked players will be able to interact with each other in the party chat.)
  • Inviting each other to join groups.
  • Seeing each other’s player profiles in the group members lists. Blocked players cannot see the profile of the blocking player, and blocked players receive no notification regarding the block. See the tutorials below to implement player blocking using the SDKs.

# Block Player Request

Use the following function to block a player.

# Listen to Player Blocking Events

The game needs to know if a player has been blocked by another player to avoid matching blocked players together and other similar tasks. Blocking events can be tracked by subscribing to the below event. The event will be raised on the blocked player side and will pass data that contains both the blocking player and blocked player’s user IDs.

# Unblock a Player

Use the following function to unblock a player

# Listen to Player Unblocked Events

Just like the Player Blocked Event, this event will be raised on the unblock player’s side and will pass data that contains both the unblocking player and unblocked player’s User IDs.

# Retrieve List of Blocked Players

Use the following function to retrieve a list of currently blocked players. This method has a callback that returns an array of data containing the blocked players’ User IDs.

# Retrieve List of Blocked Players by User ID

Use the following function to retrieve a list of blocked players specific to a certain user, based on their User ID.

# Retrieve List of Blocking Players

Use the following function to retrieve a list of players that have blocked the current player. This method has a callback that returns an array of data that contains the blocking player’s User ID.

# Retrieve List of Blocking Players by User ID

Use the following function to retrieve a list of players that have been blocked by a specific player, using that player’s User ID.

# Bulk Synchronization of Third-party Platform Friends

This process is used to connect players’ friends from third-party platforms to AccelByte Cloud’s IAM service. This process works in the background and is triggered automatically every time your game starts, or manually by interaction between a player and the game client.

To allow your players to sync their third-party platform friends lists with their AccelByte Cloud friends list, you must call user.BulkGetUserByOtherPlatformUserIds() and lobby.BulkRequestFriend() consecutively.

# Implementing the Friends Service using the Unity SDK

# Quick Reference

References
using AccelByte.Api;
using AccelByte.Core;
using AccelByte.Models;
Friend Notification Events
AccelBytePlugin.GetLobby().FriendsStatusChanged += result =>{};    
AccelBytePlugin.GetLobby().FriendRequestAccepted += result =>{};   
AccelBytePlugin.GetLobby().OnIncomingFriendRequest += result =>{};
AccelBytePlugin.GetLobby().OnUnfriend += result =>{};              
AccelBytePlugin.GetLobby().FriendRequestCanceled += result =>{};   
AccelBytePlugin.GetLobby().FriendRequestRejected += result =>{};  
Request Friend
AccelBytePlugin.GetLobby().RequestFriend(userInfo.userId, result =>
{
    if (result.IsError)
    {
        Debug.Log($"Failed to send a friends request: error code: {result.Error.Code} message: {result.Error.Message}");
    }
    else
    {
        Debug.Log("Sent Friends Request");
    }
});
Unfriend
AccelBytePlugin.GetLobby().Unfriend(userId, result =>
{
    // Check this is an error
    if (result.IsError)
    {
        Debug.Log($"Failed to unfriend a friend: code: {result.Error.Code}, message: {result.Error.Message}");
    }
    else
    {
        Debug.Log("Successfully unfriend a friend!");
    }
});
Load Friend List
AccelBytePlugin.GetLobby().LoadFriendsList(result =>
{
    // Check this is not an error
    if (!result.IsError)
    {    
        foreach (string friendID in result.Value.friendsId)
        {
            Debug.Log($"Friend : {friendID}");
        }    
    }
    else
    {
        Debug.LogWarning("Error in Getting Friends");
    }
});
List Incoming Friend Requests
// Get all Incoming Friend Requests
AccelBytePlugin.GetLobby().ListIncomingFriends(result =>
{
    // Check for an Error
    if (result.IsError)
    {
        Debug.LogWarning($"Unable to get Incoming Requests Code: {result.Error.Code}, Message: {result.Error.Message}");
    }
    else
    {
        // List incoming friends
        Debug.Log("Get list incoming friends is successfully");
    }
});
List Outgoing Friend Requests
// Get all Outgoing Friend Requests
AccelBytePlugin.GetLobby().ListOutgoingFriends(result =>
{
    // Check for an Error
    if (result.IsError)
    {
        Debug.LogWarning($"Unable to get Outgoing Friend Lists Code: {result.Error.Code}, Message: {result.Error.Message}");
    }
    else
    {
        // List outgoing friends
        Debug.Log("Get list outgoing friends is successfully");
    }
});
Get UserData by UserID
AccelBytePlugin.GetUser().GetUserByUserId(friendID, x =>
{
    //Do something with the user Data
});
Get List of Players by Searching
AccelBytePlugin.GetUser().SearchUsers(query, result =>
{
    if (!result.IsError)
    {
        // List UserID's Found using Query
    }
    else
    {
        Debug.LogWarning($"Unable to Query Users Code: {result.Error.Code}, Message: {result.Error.Message}");
    }
});

# Quickstart Guide

In this tutorial, you will learn how to use the Friend service. This guide assumes that you have already implemented the Lobby Services (opens new window).

This guide will be more abstract than the previous guides as the specifics for how each game will implement Friends can vary dramatically. Skip to the Step by Step (opens new window) section to see how we implemented each feature in our tutorial project.

There are a handful of concepts and classes to familiarize yourself with (you can find most of these classes within the UserModels.cs file inside the plugin SDK).

AccelByte’s SDK uses a mixture of Websocket and HTTP requests, generally presented to you through a wrapper layer that uses callbacks and actions to return your data. Because of this, you must design your UI to accommodate delayed callbacks and, sometimes, several layers of callbacks (e.g., requesting a list of Friends > Requesting specific data about friends > Requesting the avatar of a friend).

We will start by adding simple friend logic into the game.

  1. Create a new script called FriendManagementHandler.cs and attach it to the AccelByteHandler gameObject.

  2. Add the following AccelByte Libraries to the top of the script:

using AccelByte.Api;
using AccelByte.Models;
using AccelByte.Core;
  1. In the AccelByte SDK, the UserID is used for most functions. For example, to add friends, the function needs a User ID as a parameter. In the real UI, the player rarely uses a User ID to add a friend because a User ID is not easily memorized. Instead, you can use the Search function to find a User ID by using a display name, and then add that player as a friend, as in the example below:
void AddFriend(string displayName)
{
    AccelBytePlugin.GetUser().SearchUsers(displayName, result =>
    {
        // If not an error
        if (!result.IsError)
        {
            foreach (PublicUserInfo userInfo in result.Value.data)
            {
                // Check if display name text has a same value as the result
                if (userInfo.displayName == query)
                {
                    AccelBytePlugin.GetLobby().RequestFriend(userInfo.userId, addResult =>
                    {
                        if (addResult.IsError)
                        {
                            Debug.Log($"Failed to send a friends request: error code: {addResult.Error.Code} message: {addResult.Error.Message}");
                        }
                        else
                        {
                            Debug.Log("Sent Friends Request");
                        }
                    });
                }
                else
                {
                    Debug.Log("User is not found");
                }
            }
        }
        else
        {
            Debug.LogWarning($"Unable to Query Users Code: {result.Error.Code}, Message: {result.Error.Message}");
        }
    });
}

  1. You can see the status of the Outgoing Friend Request by using the ListOutgoingFriends function. Any Incoming Friend Requests can be found by using the ListIncomingFriends function. These two functions will return the UserID, not the display name. Use the following code to obtain the Incoming/Outgoing Friend Request lists with display names shown:
private void DisplayPending()
{
    // Get all Incoming Friend Requests
    AccelBytePlugin.GetLobby().ListIncomingFriends(result =>
    {
        // Check for an Error
        if (result.IsError)
        {
            Debug.LogWarning($"Unable to get Incoming Requests Code: {result.Error.Code}, Message: {result.Error.Message}");
        }
        else
        {
            // Loop through all the UserID's returned by the Friends callback and get their PublicUserData
            foreach (string userID in result.Value.friendsId)
            {
                // Request the PublicUserData for the specific Friend
                AccelBytePlugin.GetUser().GetUserByUserId(userID, userResult =>
                {
                    // If it's an Error, report it and do nothing else
                    if (userResult.IsError)
                    {
                        Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
                    }
                    // If we have valid data, log the display name
                    else
                    {
                        Debug.Log($"Friend request from : {userResult.Value.displayName}");
                    }
                });
            }
        }
    });

    // Get all Outgoing Friend Requests
    AccelBytePlugin.GetLobby().ListOutgoingFriends(result =>
    {
        // Check for an Error
        if (result.IsError)
        {
            Debug.LogWarning($"Unable to get Outgoing Friend Lists Code: {result.Error.Code}, Message: {result.Error.Message}");
        }
        else
        {
            // Loop through all the UserID's returned by the Friends callback and get their PublicUserData
            foreach (string userID in result.Value.friendsId)
            {
                // Request the PublicUserData for the specific Friend
                AccelBytePlugin.GetUser().GetUserByUserId(userID, userResult =>
                {
                    // If it's an Error, report it and do nothing else
                    if (userResult.IsError)
                    {
                        Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
                    }
                    // If we have valid data, log the display name
                    else
                    {
                        Debug.Log($"Pending Friend Request to : {userResult.Value.displayName}");
                    }
                });
            }
        }
    });
}

  1. Use the following code to load the Friend List with the current activity and availability:
private void GetFriends()
{
    AccelBytePlugin.GetLobby().LoadFriendsList(result =>
    {
        //Check this is not an error
        if (!result.IsError)
        {
            //Check if no friends were returned
            if (result.Value.friendsId.Length <= 0)
            {
                return;
            }

            //Fire off Requests to create UI for each friend
            Debug.Log("Loaded Friends List Successfully");

            List<string> userIds = new List<string>();
            foreach (string friendID in result.Value.friendsId)
            {
                userIds.Add(friendID);
            }

            AccelBytePlugin.GetLobby().BulkGetUserPresence(userIds.ToArray(), bulkResult =>
            {
                if (!bulkResult.IsError)
                {
                    foreach (var friend in bulkResult.Value.data)
                    {
                        AccelBytePlugin.GetUser().GetUserByUserId(friend.userID, x =>
                        {
                            Debug.Log($"Friend : {x.Value.displayName}, Status : {friend.activity}, and Availability : {friend.availability.ToString()}");
                        });
                    }
                }
            });
        }
        else
        {
            Debug.LogWarning("Error in Getting Friends");
        }
    });
}

  1. You can also Unfriend the currently displayed friend with the following code:
private void Unfriend(string userId)
{
    AccelBytePlugin.GetLobby().Unfriend(userId, result =>
    {
        if (result.IsError)
        {
            Debug.Log($"Failed to unfriend a friend: code: {result.Error.Code}, message: {result.Error.Message}");
        }
        else
        {
            Debug.Log("Successfully unfriend a friend!");
        }
    });
}

  1. The Friends service features some notification events that will help make your Friends List more responsive. In the following steps, we will show you how to add events in LobbyHandler.cs and on the ConnectToLobby() function, and then how to use the Debug.Log to check that each notification is working correctly.
  • FriendsStatusChanged

This event is triggered if a friend’s activity or availability status changes.

AccelBytePlugin.GetLobby().FriendsStatusChanged += result =>
{
        Debug.Log($"Friend status change into status : {result.Value.activity} and availability : {result.Value.availability} from user : {result.Value.userID}");
};

  • FriendRequestAccepted

This event is triggered if a pending Friend Request is accepted by another player.

AccelBytePlugin.GetLobby().FriendRequestAccepted += result =>
{
        Debug.Log($"Accepted a Friend Request from user {result.Value.friendId}");
};

  • OnIncomingFriendRequest

This event is triggered if someone adds the player as a Friend.

AccelBytePlugin.GetLobby().OnIncomingFriendRequest += result =>
{
        Debug.Log($"Received a Friend Request from user {result.Value.friendId}");
};

  • OnUnfriend

This event is triggered if the player is unfriended by a Friend.

AccelBytePlugin.GetLobby().OnUnfriend += result =>
{
        Debug.Log($"Unfriended User {result.Value.friendId}");
};  

  • FriendRequestCanceled

This event is triggered if an Incoming Friend Request is canceled by the requesting player.

AccelBytePlugin.GetLobby().FriendRequestCanceled += result =>
{
        Debug.Log($"Cancelled a Friend Request from user {result.Value.userId}");
};

  • FriendRequestRejected

This event is triggered if the player’s Friend Request is rejected by another player.

AccelBytePlugin.GetLobby().FriendRequestRejected += result =>
{
        Debug.Log($"Rejected a Friend Request from user {result.Value.userId}");
};

Congratulations! You have successfully implemented the basics of the Friends service!

Continue on for a step by step example of the UI and code implementation. Otherwise, you are now ready to move on to Party services (opens new window).

# Step by Step Guide

UI Implementation
  1. For the Friends Page, create a new panel or scene with the following objects:
  • Header text
  • Exit button
  • An Empty panel, for Friends Page content (optional)
  1. Since the Friends Page will have a lot of items to display, we have divided the page into three sections: Friends, Pending, and Blocked.

To do this in your own game, add the following UI objects:

  • 3 Buttons: one each for the Friends tab, Pending tab, and Blocked tab.
  • 3 Panels for each tab’s button. To avoid confusion, only set the first panel (Friends panel) as active.

You can add the UIs into separate panels, such as in the example below:

Friends

  1. Move to the tab panels. Here, we will prepare the page to list all the Friends items. Add the following to each tab panel:

  2. Friends tab panel:

    • Friends button which will be used to open the Search Friends panel
    • Friends List panel
    • Friends Display placeholder text
    • Friends Display prefab

Once completed, create a new panel, parent it to Friends List panel, and add the following UIs:

    * **Profile** image
    * **Status Indicator** image
    * **Display Name** text
    * **Online Status** text
    * **Chat** button
    * **Invite to Party** button
    * **Unfriend** button
    * **Block** button

Friends

Below is an example view of the Friends tab panel, along with its hierarchy:

Friends

  1. Pending tab panel:

Incoming Request List:

* **Scroll View** with **Scrollbar Vertical** only
* **Incoming Pending Requests** list panel, and parent it under the **Scroll View**’s content
* **Incoming** placeholder text
* **Incoming Request Display** prefab

Create a new panel, parent it to the Incoming Pending Requests list panel, and add the following UI elements:

    * **Profile** image
    * **Display Name** text
    * **Accept** button
    * **Decline** button
    * **Block** button

Friends

Outgoing Request List:

* **Sent Request List** panel, and parent it under the **Scroll View**’s content
* **Outgoing** placeholder text
* **Outgoing Request Display** prefab

Create a new panel, parent it to the Incoming Pending Requests list panel, and add the following UI elements:

    * **Profile** image
    * **Display Name** text
    * **Cancel** button

Friends

Below is an example view of the Pending tab panel, along with its hierarchy:

Friends

  1. Blocked tab panel:
    • Blocked Players List panel
    • Blocked placeholder text
    • Blocked Player Display prefab

Create a new panel, parent it to the Incoming Blocked Players list panel, and add the following UI elements:

    * **Profile** image
    * **Display Name** text
    * **Unblock** button

Friends

Below is an example view of the Blocked tab panel, along with its hierarchy:

Friends

  1. Lastly, for the Search Friends pop-up, create a new panel, and add the following UI elements:
  • Exit button
  • Friend Search input field
  • Scroll View with Scrollbar Vertical only
  • Search placeholder text
  • Add Friends Display prefab

Create a new panel, parent it to the Incoming Blocked Players list panel, and add the following UI elements:

    * **Profile** image
    * **Display Name** text
    * **Add Friend** button

Friends

Below is an example view of the Search Friends pop-up, along with its hierarchy:

Friends

  1. To make it easier to move to another panel, create a new menu panel and add the following UI elements:
  • Lobby button
  • Friends button
  • Quit button

Friends

Code Implementation
  1. Go to the FriendsManagementHandler.cs script. Remove the AddFriend and Unfriend functions that you have previously created. We'll add these functionalities later to another function with additional improvement.

  2. Add the following AccelByte and UnityEngine libraries to the top of the script:

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine.UI;
using UnityEngine.Serialization;

To avoid ambiguous references, add this code to the top of the script:

using Button = UnityEngine.UI.Button;
  1. Create a new dictionary that will hold Friend UserIDs.
private Dictionary<string, FriendStatusPanel> friendUIDictionary = new Dictionary<string, FriendStatusPanel>();
  1. Add the following UI references to FriendsManagementHandler.cs:
[Header("Panels")]
public GameObject FriendsManagementWindow;

[FormerlySerializedAs("FriendsPanel")]
[SerializeField]
private RectTransform friendsPanel;
[SerializeField]
private RectTransform requestsPanel;
[SerializeField]
private RectTransform blockedPanel;

#region Buttons
[Header("Buttons")]

[SerializeField]
private Button friendsTabButton;
[SerializeField]
private Button pendingTabButton;
[SerializeField]
private Button blockedTabButton;

[SerializeField]
private Button exitButton;

#endregion

[Header("Friends Panel")]
[SerializeField]
private Transform friendsDisplayPanel;

[FormerlySerializedAs("FriendsDisplayPrefab")]
[SerializeField]
private GameObject friendsDisplayPrefab;

[SerializeField]
private Button friendsSearchButton;

[SerializeField]
private Transform friendsDisplayPlaceholder;

#region Search
[Header("Search Panel")]
[SerializeField]
private GameObject addFriendsPrefab;
[SerializeField]
private Transform friendsSearchPanel;

[SerializeField]
private InputField friendsSearchInputField;
[SerializeField]
private RectTransform friendsSearchScrollView;
[SerializeField]
private Button friendsSearchQuitButton;

[SerializeField]
private Transform friendsSearchPlaceholder;

#endregion

#region Pending
[Header("Pending Panel")]
[SerializeField]
private RectTransform pendingIncomingRequestsContent;
[SerializeField]
private RectTransform pendingOutgoingRequestsContent;

[SerializeField]
private GameObject pendingIncomingRequestsPrefab;
[SerializeField]
private GameObject pendingOutgoingRequestsPrefab;

[SerializeField]
private Transform pendingIncomingRequestPlaceholder;
[SerializeField]
private Transform pendingOutgoingRequestPlaceholder;

[SerializeField]
private Text pendingIncomingRequestText;
[SerializeField]
private Text pendingOutgoingRequestText;

#endregion

#region Blocked
[Header("Blocked")]
[SerializeField]
private RectTransform blockedContent;

[SerializeField]
private GameObject blockedUserPrefab;

[SerializeField]
private Transform blockedDisplayPlaceholder;

#endregion
  1. Create a function that will destroy all the children of the parent transform to reset the UI.
/// A utility function to Destroy all Children of the parent transform. Optionally do not remove a specific Transform
/// <param name="parent">Parent Object to destroy children</param>
/// <param name="doNotRemove">Optional specified Transform that should NOT be destroyed</param>
private static void LoopThroughTransformAndDestroy(Transform parent, Transform doNotRemove = null)
{
    //Loop through all the children and add them to a List to then be deleted
    List<GameObject> toBeDeleted = new List<GameObject>();
    foreach (Transform t in parent)
    {
        //except the Do Not Remove transform if there is one
        if (t != doNotRemove)
        {
            toBeDeleted.Add(t.gameObject);
        }
    }
    //Loop through list and Delete all Children
    for (int i = 0; i < toBeDeleted.Count; i++)
    {
        Destroy(toBeDeleted[i]);
    }
}
  1. Create a function to display Blocked players in FriendsManagementHandler.cs:
private void DisplayBlocked()
{
    //Cleanup First
LoopThroughTransformAndDestroy(blockedContent.transform,blockedDisplayPlaceholder);

    //Get Blocked List
    AccelBytePlugin.GetLobby().GetListOfBlockedUser(result =>
    {
        //Check for an Error
        if (result.IsError)
        {
            Debug.LogWarning($"Unable to get Blocked Player List Code: {result.Error.Code}, Message: {result.Error.Message}");

            blockedDisplayPlaceholder.gameObject.SetActive(true);        
        }
        else
        {
            blockedDisplayPlaceholder.gameObject.SetActive(false);
            //Loop through all the UserID's returned by the callback and get their PublicUserData
            foreach (BlockedData blockedUser in result.Value.data)
            {
                //Request the PublicUserData for the specific User
                AccelBytePlugin.GetUser().GetUserByUserId(blockedUser.blockedUserId, userResult =>
                {
                    //If it's an Error, report it and do nothing else
                    if (userResult.IsError)
                    {
                        Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
                    }
                });        
            }
        }
    });  
}
  1. To change the tab view, prepare an enum variable that will hold the current tab value.
public enum FriendsMode
{
    Default,
    Friends,
    Pending,
    Blocked
};

private FriendsMode _displayMode = FriendsMode.Default;
  1. Create a function that states what will happen if we change the tab UIs based on the current FriendsMode.
private FriendsMode DisplayMode
{
    get => _displayMode;
    set
    {
        switch (value)
        {
            case FriendsMode.Default:
                friendsPanel.gameObject.SetActive(true);
                requestsPanel.gameObject.SetActive(false);
                blockedPanel.gameObject.SetActive(false);
                break;

            case FriendsMode.Friends:
                friendsPanel.gameObject.SetActive(true);
                requestsPanel.gameObject.SetActive(false);
                blockedPanel.gameObject.SetActive(false);
                GetFriends();
                break;

            case FriendsMode.Pending:
                requestsPanel.gameObject.SetActive(true);
                friendsPanel.gameObject.SetActive(false);
                blockedPanel.gameObject.SetActive(false);
                DisplayPending();
                break;

            case FriendsMode.Blocked:
                blockedPanel.gameObject.SetActive(true);
                friendsPanel.gameObject.SetActive(false);
                requestsPanel.gameObject.SetActive(false);
                DisplayBlocked();
                break;
        }
    }
}
  1. Create a function to search for a player in FriendsManagementHandler.cs:
private void SearchForFriends(string query)
{
    AccelBytePlugin.GetUser().SearchUsers(query, result =>
    {
        if (!result.IsError)
        {
            ListQueriedusers(result.Value);
        }
        else
        {
            Debug.LogWarning($"Unable to Query Users Code: {result.Error.Code}, Message: {result.Error.Message}");
        }
    });
}
  1. Create a function to update the Friends query result in the UI:
private void ListQueriedusers(PagedPublicUsersInfo pagedInfo)
{
    //Cleanup First
    LoopThroughTransformAndDestroy(friendsSearchScrollView.transform, friendsSearchPlaceholder);

    if (pagedInfo.data.Length <=0)
    {
        friendsSearchPlaceholder.gameObject.SetActive(true);
    }
    else
    {
        friendsSearchPlaceholder.gameObject.SetActive(false);
        foreach (PublicUserInfo info in pagedInfo.data)
        {
            FriendsAddPanel addPanel = Instantiate(addFriendsPrefab,friendsSearchScrollView).GetComponent<FriendsAddPanel>();
            addPanel.Create(info);
        }
    }
}
  1. Create a function to open the AddFriendPanel prefab.
private void DisplaySearch()
{
    friendsSearchPanel.gameObject.SetActive(true);  
    LoopThroughTransformAndDestroy(friendsSearchScrollView.transform, friendsSearchPlaceholder);
    friendsSearchPlaceholder.gameObject.SetActive(true);
}
  1. Create a function to update the Friends Data in the UI.
public void UpdateFriends(FriendsStatusNotif notification)
{
    //Find the friend and update it's UI
    if (friendUIDictionary.ContainsKey(notification.userID))
    {
        friendUIDictionary[notification.userID].UpdateUser(notification);
    }
    //Otherwise We should handle this in some way, possibly creating a Friend UI Piece
    else
    {
        Debug.Log("Unregistered Friend received a Notification");
    }
}
  1. Use the Lobby’s notification event to call the UpdateFriends() function every time a friend’s status updates. To do this, create a function in NotificationHandler.cs:
/// Called when friend status is changed
public void OnFriendsStatusChanged(Result<FriendsStatusNotif> result)
{
    GetComponent<FriendsManagementHandler>().UpdateFriends(result.Value);
}

You can create more functions for other events and use the debug log to verify if there are any updates.

/// Called when friend request is accepted
public void OnFriendRequestAccepted(Result<Friend> result)
{
    Debug.Log($"Accepted a Friend Request from user {result.Value.friendId}");
}

/// Called when there is incoming friend request
public void OnIncomingFriendRequest(Result<Friend> result)
{
    Debug.Log($"Received a Friend Request from user {result.Value.friendId}");
}

/// Called when friend is unfriend
public void OnUnfriend(Result<Friend> result)
{
    Debug.Log($"Unfriended User {result.Value.friendId}");
}

/// Called when friend request is canceled
public void OnFriendRequestCanceled(Result<Acquaintance> result)
{
    Debug.Log($"Cancelled a Friend Request from user {result.Value.userId}");
}

/// Called when friend request is rejected
public void OnFriendRequestRejected(Result<Acquaintance> result)
{
    Debug.Log($"Rejected a Friend Request from user {result.Value.userId}");
}
  1. Create a script called MenuHandler.cs to handle the menu panel and attach it to the AccelByteHandler gameObject, then add the following code to the MenuHandler.cs:
public Transform Menu;

public Button FriendsButton;

private bool initialize = false;

public void Create()
{
    if (initialize) return;

    initialize = true;
}
  1. Open LobbyHandler.cs and navigate to the ConnectToLobby() function, then add the following code:
public void ConnectToLobby()
{
    ...

    //Init menu handler
    GetComponent<MenuHandler>().Create();
    GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);

    ...

    //Friends
    _lobby.FriendsStatusChanged += notificationHandler.OnFriendsStatusChanged;
    _lobby.FriendRequestAccepted += notificationHandler.OnFriendRequestAccepted;
    _lobby.OnIncomingFriendRequest += notificationHandler.OnIncomingFriendRequest;
    _lobby.FriendRequestCanceled += notificationHandler.OnFriendRequestCanceled;
    _lobby.FriendRequestRejected += notificationHandler.OnFriendRequestRejected;
    _lobby.OnUnfriend += notificationHandler.OnUnfriend;  
    ...
}
  1. While still in LobbyHandler.cs, add the following code to the RemoveLobbyListeners() function to reset the Friends Notification delegate:
public void RemoveLobbyListeners()
{
    ...
    //Friends
    _lobby.FriendsStatusChanged -= notificationHandler.OnFriendsStatusChanged;
    _lobby.FriendRequestAccepted -= notificationHandler.OnFriendRequestAccepted;
    _lobby.OnIncomingFriendRequest -= notificationHandler.OnIncomingFriendRequest;
    _lobby.FriendRequestCanceled -= notificationHandler.OnFriendRequestCanceled;
    _lobby.FriendRequestRejected -= notificationHandler.OnFriendRequestRejected;
    _lobby.OnUnfriend -= notificationHandler.OnUnfriend;
}
  1. Once completed, you can create a function that will set up your Friends UI state and listener in FriendsManagementHandler.cs.
private bool setupStatus = false;
...

public void Setup()
{
    // reset the exit button's listener, then add the listener based on the exit screen type
    exitButton.onClick.RemoveAllListeners();
    exitButton.onClick.AddListener(() =>
    {
        FriendsManagementWindow.SetActive(false);
        GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);
    });

    // Check whether the FriendsPanel already set up or not
    if (setupStatus)
    {
        DisplayMode = FriendsMode.Default;
        return;
    }
    // Run the setup if it still hasn't
    else
    {
        setupStatus = true;

        DisplayMode = FriendsMode.Friends;

        // reset listeners, so it won't triggered more than once
        friendsTabButton.onClick.RemoveAllListeners();
        pendingTabButton.onClick.RemoveAllListeners();
        blockedTabButton.onClick.RemoveAllListeners();

        // add the listeners
        friendsTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Friends);
        pendingTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Pending);
        blockedTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Blocked);
        friendsSearchPanel.gameObject.SetActive(false);
        friendsDisplayPlaceholder.gameObject.SetActive(true);
        friendsSearchQuitButton.onClick.AddListener(() => friendsSearchPanel.gameObject.SetActive(false));
        friendsSearchButton.onClick.AddListener(DisplaySearch);
        friendsSearchInputField.onEndEdit.AddListener(SearchForFriends);
        friendsSearchButton.onClick.AddListener(() => friendsSearchPanel.gameObject.SetActive(true));
    }
}
  1. To prepare the Friends panel so it can be opened from the Menu, add the following code to the MenuHandler.cs:
public void Create()
{
    ...

    FriendsButton.onClick.AddListener(() =>
    {
GetComponent<FriendsManagementHandler>().Setup();
        Menu.gameObject.SetActive(false);
GetComponent<FriendsManagementHandler>().FriendsManagementWindow.SetActive(true);
    });
}
  1. In the Unity Editor, navigate to the AccelByteHandler gameObject and drag the following objects to their references:

Friends

  1. Create a new script called FriendStatusPanel.cs and attach it to the FriendDisplay prefab.

  2. Add the following AccelByte and UnityEngine libraries to the top of the script:

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;

To avoid ambiguous references, add this code to the top of the script:

using Image = UnityEngine.UI.Image;
  1. Add the following references to the necessary UIs in FriendStatusPanel.cs:
[SerializeField]
private Image profilePicture;
[SerializeField]
private Image statusDisplay;

[SerializeField]
private Button chatButton;
[SerializeField]
private Button inviteToPartyButton;
[SerializeField]
private Button unfriendButton;
[SerializeField]
private Button blockButton;

[SerializeField]
private Text displayNameText;
[SerializeField]
private Text onlineStatusText;

Once completed, create a variable that will hold the User Data:

PublicUserData _userData;
  1. Create a function to update the Friends list online statuses UI in FriendStatusPanel.cs.
public void SetOnlineStatus(FriendsStatusNotif notification)
{
        switch (notification.availability)
        {
            case "offline":
                onlineStatusText.text = "Offline";
                statusDisplay.color = Color.black;
                break;
            case "online":
                onlineStatusText.text = "Online";
                statusDisplay.color = Color.green;
                break;
            case "busy":
                onlineStatusText.text = "Busy";
                statusDisplay.color = Color.yellow;
                break;
            case "invisible":
                onlineStatusText.text = "Offline";
                statusDisplay.color = Color.black;
                break;
            default:
                onlineStatusText.text = $"INVALID UNHANDLED {notification.availability}";
                statusDisplay.color = Color.magenta;
                break;
        }

        Debug.Log($"Friend Status for {notification.userID} changed to {notification.availability}");
}
  1. While still in FriendStatusPanel.cs, create functions to update user data in the UI and the player’s profile picture (avatar).
public void Create(PublicUserData pud)
{
        _userData = pud;
        displayNameText.text = _userData.displayName;
}
  1. While still in FriendStatusPanel.cs, create a new function that will handle all the buttons in the display prefab, and then add a listener to Unfriend with the SDK service.
public void SetupButton()
{
    unfriendButton.onClick.AddListener(() =>
    {
        AccelBytePlugin.GetLobby().Unfriend(_userData.userId, result =>
        {
            if (result.IsError)
            {
                Debug.Log($"Failed to unfriend a friend: code: {result.Error.Code}, message: {result.Error.Message}");
            }
            else
            {
                Debug.Log("Successfully unfriend a friend!");
                LobbyHandler.Instance.GetComponent<FriendsManagementHandler>().RefreshFriendsList();
            }
        });
    });
}
  1. In the Unity Editor, on your FriendsDisplay prefab, drag the following objects to the corresponding references:

Friends

  1. Create a script called FriendIncomingPanel.cs.

  2. Add the following AccelByte and UnityEngine Libraries to the top of the script:

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;
  1. Add some UI references and a variable to hold the User Data:
[SerializeField]
private Image profilePicture;

[SerializeField]
private Text displayNameText;
[SerializeField]
private Button acceptFriendButton;
[SerializeField]
private Button declineFriendButton;
[SerializeField]
private Button blockFriendButton;

private PublicUserData _userData;
  1. Create functions to Accept and Reject a Friend Request.
/// An initialisation Function required to be called to Populate the UI appropriately
public void Create(PublicUserData userData)
{
    //Cache the PublicUserData
    _userData = userData;
    //Set the Display Name
    displayNameText.text = _userData.displayName;
    //Setup the Button to Accept a Friend Request
    acceptFriendButton.onClick.AddListener(() =>
    {
        //Make the Call to Accept the Friend Request using the cached PublicUserData UserID
        AccelBytePlugin.GetLobby().AcceptFriend(_userData.userId, result =>
        {
            if (result.IsError)
            {
                //We would probably want to display some user-feedback if the request is not successful
                Debug.Log($"Failed to accept a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
            }
            else
            {
                Debug.Log("Accepted Friend Request");
                //Destroy the UI Piece if the request was successful to remove it from the list
                Destroy(gameObject);
            }
        });

    });
    //Setup the Button to Decline a Friend Request
    declineFriendButton.onClick.AddListener(() =>
    {
        //Make the Call to Decline the Friend Request using the cached PublicUserData UserID
        AccelBytePlugin.GetLobby().RejectFriend(_userData.userId, result =>
        {
            if (result.IsError)
            {
                //We would probably want to display some user-feedback if the request is not successful
                Debug.Log($"Failed to decline a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
            }
            else
            {
                Debug.Log("Declined Friend Request");
                //Destroy the UI Piece if the request was successful to remove it from the list
                Destroy(gameObject);
            }
        });

    });
    //Setup the Button to Block a Player
    blockFriendButton.onClick.AddListener(() =>
    {
        //Make the Call to Block a Player using the cached PublicUserData UserID
        AccelBytePlugin.GetLobby().BlockPlayer(_userData.userId, result =>
        {
            if (result.IsError)
            {
                //We would probably want to display some user-feedback if the request is not successful
                Debug.Log($"Failed to block a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
            }
            else
            {
                Debug.Log("Block Friend Request");
                //Destroy the UI Piece if the request was successful to remove it from the list
                Destroy(gameObject);
            }
        });

    });
}
  1. In the Unity Editor, in your IncomingRequestDisplay prefab, drag the following objects to the exposed variables in the script:

Friends

  1. Create a script called FriendsOutgoingPanel.cs.

  2. Add the following AccelByte and UnityEngine libraries to the top of the script:

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;
  1. Add the following references for the UI and a variable that will hold the User Data:
[SerializeField]
private Image profilePicture;

[SerializeField]
private Text displayNameText;
[SerializeField]
private Button cancelRequestButton;

private PublicUserData _userData;
  1. Create a function to cancel a Friend Request and obtain the player’s avatar, then update it in the UI.
public void Create(PublicUserData userData)
{
    //Cache the PublicUserData
    _userData = userData;
    //Set the Display Name
    displayNameText.text = _userData.displayName;
    //Setup the Button to Cancel A Friend Request
    cancelRequestButton.onClick.AddListener(() =>
    {
        //Make the Call to Reject the Friend Request using the cached PublicUserData UserID
        AccelBytePlugin.GetLobby().CancelFriendRequest(_userData.userId, result =>
        {
            if (result.IsError)
            {
                //We would probably want to display some user-feedback if the request is not successful
                Debug.Log($"Failed to cancel a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
            }
            else
            {
                Debug.Log("Cancelled Friend Request");
                //Destroy the UI Piece if the request was successful to remove it from the list
                Destroy(gameObject);
            }
        });
    });
}
  1. In the Unity Editor, on your OutgoingRequestDisplay prefab, drag the following objects to the variable references in the script:

Friends

  1. Duplicate the FriendsOutgoingPanel.cs and rename the duplicate FriendsBlockedPanel.cs. Revise the UI references in this script as follows:
[SerializeField]
private Image profilePicture;
[SerializeField]
private Text displayNameText;
[SerializeField]
private Button unblockButton;

private PublicUserData _userData;
  1. Delete any button setups for AddListener and add the following code:
public void Create(PublicUserData userData)
{
        ...
        //Setup the Button to Unblock a User
        unblockButton.onClick.AddListener(() =>
        {
            //Make the Call to Unblock the given User using the cached PublicUserData UserID
            AccelBytePlugin.GetLobby().UnblockPlayer(_userData.userId, result =>
            {
                if (result.IsError)
                {
                        //We would probably want to display some user-feedback if the request is not successful
                        Debug.Log($"Failed to unblock a player: error code: {result.Error.Code} message: {result.Error.Message}");
                }
                else
                {
                        Debug.Log("Unblocked a Player");
                        //Destroy the UI Piece if the request was successful to remove it from the list
                        Destroy(gameObject);
                }
            });
        });
        ...
}
  1. In the Unity Editor, open the BlockedDisplay prefab and drag the following objects to the public variable in the script:

Friends

  1. Duplicate the FriendsOutgoingPanel.cs and rename the duplicate to FriendsAddPanel.cs. Revise the UI references in the script as follows:
[SerializeField]
private Image profilePicture;
[SerializeField]
private Text displayNameText;
[SerializeField]
private Button addFriendButton;

private PublicUserData _userData;
  1. Delete any button setups for AddListener and add the following code:
public void Create(PublicUserData userData)
{
        ...
        //Setup the Button to Request a Friend
        addFriendButton.onClick.AddListener(() =>
        {
            //Make the call to initiate the Friend Request
            AccelBytePlugin.GetLobby().RequestFriend(_userData.userId, result =>
            {
                if (result.IsError)
                {
                        Debug.Log($"Failed to send a friends request: error code: {result.Error.Code} message: {result.Error.Message}");
                }
                else
                {
                        Debug.Log("Sent Friends Request");
                        //If we were successful, set the button to be non-intractable and change the Text
                        addFriendButton.interactable = false;
                        addFriendButton.GetComponentInChildren<Text>().text = "Request Sent";
                }
            });
        });
        ...
}
  1. In the Unity Editor, open the AddFriendsDisplay prefab and drag the following objects to the variable in the script component:

Friends

  1. Now, in FriendsManagementHandler.cs, create a function that will create a new FriendStatusPanel prefab in accordance with the userData.
private void CreateFriendUI(PublicUserData userData)
{
    FriendStatusPanel panel = Instantiate(friendsDisplayPrefab, friendsDisplayPanel).GetComponent<FriendStatusPanel>();
    panel.Create(userData);
    if (!friendUIDictionary.ContainsKey(userData.userId))
    {
        friendUIDictionary.Add(userData.userId, panel);
    }

    // set up the any FriendStatusPanel's related button
    panel.SetupButton();
}
  1. Modify the function in FriendsManagementHandler.cs to obtain all available friend data, such as in the following example:
/// Get Friends and Display them
private void GetFriends()
{

    //Cleanup First    LoopThroughTransformAndDestroy(friendsDisplayPanel.transform, friendsDisplayPlaceholder);

    AccelBytePlugin.GetLobby().LoadFriendsList(result =>
    {
        //Check this is not an error
        if (!result.IsError)
        {
            //Check if no friends were returned
            if (result.Value.friendsId.Length <= 0)
            {
                //Display the Placeholder Text
                friendsDisplayPlaceholder.gameObject.SetActive(true);
                return;
            }
            //Hide the Placeholder Text
            friendsDisplayPlaceholder.gameObject.SetActive(false);


            //Fire off Requests to create UI for each friend
            Debug.Log("Loaded Friends List Succesfully");
            foreach (string friendID in result.Value.friendsId)
            {
                ...
                AccelBytePlugin.GetUser().GetUserByUserId(friendID, x =>
                {
                    CreateFriendUI(x.Value);
                });
            }    
        }
        else
        {
            //Display the Placeholder
            friendsDisplayPlaceholder.gameObject.SetActive(true);
            ...
        }
    });
}
  1. Once completed, you will need to modify a function to list any pending Incoming/Outgoing Friend Requests. In FriendsManagementHandler.cs, modify the following codes:
private void DisplayPending()
{
    //Cleanup First, remove all Children from the Contents OTHER than the Placeholders
    LoopThroughTransformAndDestroy(pendingIncomingRequestsContent.transform, pendingIncomingRequestPlaceholder);
    LoopThroughTransformAndDestroy(pendingOutgoingRequestsContent.transform, pendingOutgoingRequestPlaceholder);

    //Get all Incoming Friend Requests
    AccelBytePlugin.GetLobby().ListIncomingFriends(result =>
    {
        //Check for an Error
        if (result.IsError)
        {
            ...
            //Set the Placeholder Text to be Active so it doesn't just look broken
            pendingIncomingRequestPlaceholder.gameObject.SetActive(true);
        }
        else
        {
            //If there are Zero Incoming Requests, set the PlaceHolder to be active
            if (result.Value.friendsId.Length <= 0)
            {
                pendingIncomingRequestPlaceholder.gameObject.SetActive(true);
            }
            //Otherwise set the PlaceHolder to be inactive
            else
            {
                pendingIncomingRequestPlaceholder.gameObject.SetActive(false);
            }

            //Loop through all the UserID's returned by the Friends callback and get their PublicUserData
            foreach (string userID in result.Value.friendsId)
            {
                //Request the PublicUserData for the specific Friend
                AccelBytePlugin.GetUser().GetUserByUserId(userID, userResult =>
                {
                    if (userResult.IsError)
                    ...
                    else
                    {
                        FriendsIncomingPanel incomingPanel = Instantiate(pendingIncomingRequestsPrefab, pendingIncomingRequestsContent).GetComponent<FriendsIncomingPanel>();
                        //Pass the PublicUserData into this function
                        incomingPanel.Create(userResult.Value);                       
                    }
                });
            }
        }
    });

    AccelBytePlugin.GetLobby().ListOutgoingFriends(result =>
    {
        if (result.IsError)
        ...
        else
        {
            if (result.Value.friendsId.Length <= 0)
            {
pendingOutgoingRequestPlaceholder.gameObject.SetActive(true);
            }
            else
            {
pendingOutgoingRequestPlaceholder.gameObject.SetActive(false);
            }
            foreach (string userID in result.Value.friendsId)
            {
                AccelBytePlugin.GetUser().GetUserByUserId(userID, userResult =>
                {
                    if (userResult.IsError)
                    ...
                    else
                    {
                        FriendsOutgoingPanel outgoingPanel = Instantiate(pendingOutgoingRequestsPrefab, pendingOutgoingRequestsContent).GetComponent<FriendsOutgoingPanel>();
                        outgoingPanel.Create(userResult.Value);
                    }
                });
            }
        }
    });  
}
  1. Add the following code in the DisplayBlocked() function to instantiate the display prefab for blocked players in FriendsManagementHandler.cs:
...
AccelBytePlugin.GetUser().GetUserByUserId(blockedUser.blockedUserId, userResult =>
{
    ...
    if (userResult.IsError)
    ...
    //If we have valid data, Instantiate the Prefab for the specific UI Piece and call relevant functions
    else
    {
        FriendsBlockedPanel blockedPanel = Instantiate(blockedUserPrefab, blockedContent).GetComponent<FriendsBlockedPanel>();
        //Pass the PublicUserData into this function
        blockedPanel.Create(userResult.Value);  
    }
});        
...
  1. In the Unity Editor, navigate to the AccelByteHandler gameObject and drag the following objects to their references in the FriendsManagementHandler script component:
  • Panel and buttons references

Friends

  • Friends panel and Search panel references

Friends

  • Pending panel and Blocked panel references

Friends

Congratulations! You have now fully implemented the Friends service.

Proceed to the next section to learn how to implement Party services (opens new window).

# Full Code

FriendsManagementHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using System.Collections.Generic;
using UnityEngine;
using AccelByte.Api;
using AccelByte.Models;
using UnityEngine.Serialization;
using UnityEngine.UI;
using Button = UnityEngine.UI.Button;

public class FriendsManagementHandler : MonoBehaviour
{
    private Dictionary<string, FriendStatusPanel> friendUIDictionary = new Dictionary<string, FriendStatusPanel>();

    public GameObject FriendsManagementWindow;

    [FormerlySerializedAs("FriendsPanel")]
    [SerializeField]
    private RectTransform friendsPanel;
    [SerializeField]
    private RectTransform requestsPanel;
    [SerializeField]
    private RectTransform blockedPanel;

    #region Buttons

    [SerializeField]
    private Button friendsTabButton;
    [SerializeField]
    private Button pendingTabButton;
    [SerializeField]
    private Button blockedTabButton;

    [SerializeField]
    private Button exitButton;

    #endregion

    [SerializeField]
    private Transform friendsDisplayPanel;

    [FormerlySerializedAs("FriendsDisplayPrefab")]
    [SerializeField]
    private GameObject friendsDisplayPrefab;

    [SerializeField]
    private Button friendsSearchButton;

    [SerializeField]
    private Transform friendsDisplayPlaceholder;
    [SerializeField]
    private Transform blockedDisplayPlaceholder;

    #region Search

    [SerializeField]
    private GameObject addFriendsPrefab;
    [SerializeField]
    private Transform friendsSearchPanel;

    [SerializeField]
    private InputField friendsSearchInputField;
    [SerializeField]
    private RectTransform friendsSearchScrollView;
    [SerializeField]
    private Button friendsSearchQuitButton;

    [SerializeField]
    private Transform friendsSearchPlaceholder;

    #endregion

    #region Pending

    [SerializeField]
    private RectTransform pendingIncomingRequestsContent;
    [SerializeField]
    private RectTransform pendingOutgoingRequestsContent;
    [SerializeField]
    private RectTransform blockedContent;

    [SerializeField]
    private GameObject pendingOutgoingRequestsPrefab;
    [SerializeField]
    private GameObject pendingIncomingRequestsPrefab;
    [SerializeField]
    private GameObject blockedUserPrefab;

    [SerializeField]
    private Transform pendingIncomingRequestPlaceholder;
    [SerializeField]
    private Transform pendingOutgoingRequestPlaceholder;

    [SerializeField]
    private Text pendingIncomingRequestText;
    [SerializeField]
    private Text pendingOutgoingRequestText;

    #endregion

    private bool setupStatus = false;

    public enum FriendsMode
    {
        Default,
        Friends,
        Pending,
        Blocked
    };

    private FriendsMode _displayMode = FriendsMode.Default;

    private FriendsMode DisplayMode
    {
        get => _displayMode;
        set
        {
            switch (value)
            {
                case FriendsMode.Default:
                    friendsPanel.gameObject.SetActive(true);
                    requestsPanel.gameObject.SetActive(false);
                    blockedPanel.gameObject.SetActive(false);
                    break;
                case FriendsMode.Friends:
                    friendsPanel.gameObject.SetActive(true);
                    requestsPanel.gameObject.SetActive(false);
                    blockedPanel.gameObject.SetActive(false);
                    GetFriends();
                    break;
                case FriendsMode.Pending:
                    requestsPanel.gameObject.SetActive(true);
                    friendsPanel.gameObject.SetActive(false);
                    blockedPanel.gameObject.SetActive(false);
                    DisplayPending();
                    break;
                case FriendsMode.Blocked:
                    blockedPanel.gameObject.SetActive(true);
                    friendsPanel.gameObject.SetActive(false);
                    requestsPanel.gameObject.SetActive(false);
                    DisplayBlocked();
                    break;
            }
        }
    }

    public void UpdateFriends(FriendsStatusNotif notification)
    {
        //Find the friend and update it's UI
        if (friendUIDictionary.ContainsKey(notification.userID))
        {
friendUIDictionary[notification.userID].SetOnlineStatus(notification);
        }
        //Otherwise We should handle this in some way, possibly creating a Friend UI Piece
        else
        {
            Debug.Log("Unregistered Friend received a Notification");
        }
    }

    /// <summary>
    /// Setup UI and prepare State
    /// </summary>
    /// <param name="exitType"> name of the destination panel</param>
    public void Setup()
    {
        // reset the exit button's listener, then add the listener based on the exit screen type
        exitButton.onClick.RemoveAllListeners();
        exitButton.onClick.AddListener(() =>
        {
            FriendsManagementWindow.SetActive(false);
            GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);
        });

        // Check whether the FriendsPanel already set up or not
        if (setupStatus)
        {
            DisplayMode = FriendsMode.Default;
            return;
        }
        // Run the setup if it still hasn't
        else
        {
            setupStatus = true;

            DisplayMode = FriendsMode.Friends;

            // reset listeners, so it won't triggered more than once
            friendsTabButton.onClick.RemoveAllListeners();
            pendingTabButton.onClick.RemoveAllListeners();
            blockedTabButton.onClick.RemoveAllListeners();

            // add the listeners
            friendsTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Friends);
            pendingTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Pending);
            blockedTabButton.onClick.AddListener(() => DisplayMode = FriendsMode.Blocked);
            friendsSearchPanel.gameObject.SetActive(false);
            friendsDisplayPlaceholder.gameObject.SetActive(true);
            friendsSearchQuitButton.onClick.AddListener(() => friendsSearchPanel.gameObject.SetActive(false));
            friendsSearchButton.onClick.AddListener(DisplaySearch);
            friendsSearchInputField.onEndEdit.AddListener(SearchForFriends);
            friendsSearchButton.onClick.AddListener(() => friendsSearchPanel.gameObject.SetActive(true));
        }
    }

    /// <summary>
    /// Get Friends and Display them
    /// </summary>
    private void GetFriends()
    {
        //Cleanup First
        LoopThroughTransformAndDestroy(friendsDisplayPanel.transform, friendsDisplayPlaceholder);

        AccelBytePlugin.GetLobby().LoadFriendsList(result =>
        {            
            //Check this is not an error
            if (!result.IsError)
            {
                //Cleanup First
                LoopThroughTransformAndDestroy(friendsDisplayPanel.transform, friendsDisplayPlaceholder);

                //Check if no friends were returned
                if (result.Value.friendsId.Length <= 0)
                {
                    //Display the Placeholder Text
                    friendsDisplayPlaceholder.gameObject.SetActive(true);
                    return;
                }
                //Hide the Placeholder Text
                friendsDisplayPlaceholder.gameObject.SetActive(false);


                //Fire off Requests to create UI for each friend
                Debug.Log("Loaded Friends List Succesfully");
                foreach (string friendID in result.Value.friendsId)
                {
                    Debug.Log($"Friend : {friendID}");
                    AccelBytePlugin.GetUser().GetUserByUserId(friendID, x =>
                    {
                        CreateFriendUI(x.Value);
                    });
                }    
            }
            else
            {
                //Display the Placeholder
                friendsDisplayPlaceholder.gameObject.SetActive(true);
                Debug.LogWarning("Error in Getting Friends");
            }    
        });
    }

    private void CreateFriendUI(PublicUserData userData)
    {
        FriendStatusPanel panel = Instantiate(friendsDisplayPrefab, friendsDisplayPanel).GetComponent<FriendStatusPanel>();
        panel.Create(userData);
        if (!friendUIDictionary.ContainsKey(userData.userId))
        {
            friendUIDictionary.Add(userData.userId, panel);
        }

        panel.SetupButton();
    }

    private void DisplaySearch()
    {
        friendsSearchPanel.gameObject.SetActive(true);
        LoopThroughTransformAndDestroy(friendsSearchScrollView.transform, friendsSearchPlaceholder);
        friendsSearchPlaceholder.gameObject.SetActive(true);
    }

    private void SearchForFriends(string query)
    {
            AccelBytePlugin.GetUser().SearchUsers(query, result =>
            {
                if (!result.IsError)
                {
                    ListQueriedusers(result.Value);
                }
                else
                {
                    Debug.LogWarning($"Unable to Query Users Code: {result.Error.Code}, Message: {result.Error.Message}");
                }
            });
    }

    private void ListQueriedusers(PagedPublicUsersInfo pagedInfo)
    {
            //Cleanup First
            LoopThroughTransformAndDestroy(friendsSearchScrollView.transform, friendsSearchPlaceholder);

            if (pagedInfo.data.Length <=0)
            {
                friendsSearchPlaceholder.gameObject.SetActive(true);
            }
            else
            {
                friendsSearchPlaceholder.gameObject.SetActive(false);
                foreach (PublicUserInfo info in pagedInfo.data)
                {
                    FriendsAddPanel addPanel = Instantiate(addFriendsPrefab,friendsSearchScrollView).GetComponent<FriendsAddPanel>();
                    addPanel.Create(info);
                }
            }
    }

    private void DisplayPending()
    {
        //Cleanup First, remove all Children from the Contents OTHER than the Placeholders
LoopThroughTransformAndDestroy(pendingIncomingRequestsContent.transform, pendingIncomingRequestPlaceholder);
LoopThroughTransformAndDestroy(pendingOutgoingRequestsContent.transform, pendingOutgoingRequestPlaceholder);

        //Get all Incoming Friend Requests
        AccelBytePlugin.GetLobby().ListIncomingFriends(result =>
        {
            //Check for an Error
            if (result.IsError)
            {
                Debug.LogWarning($"Unable to get Incoming Requests Code: {result.Error.Code}, Message: {result.Error.Message}");
                //Set the Placeholder Text to be Active so it doesn't just look broken
pendingIncomingRequestPlaceholder.gameObject.SetActive(true);
            }
            else
            {
                //If there are Zero Incoming Requests, set the PlaceHolder to be active
                if (result.Value.friendsId.Length <= 0)
                {
pendingIncomingRequestPlaceholder.gameObject.SetActive(true);
                }
                //Otherwise set the PlaceHolder to be inactive
                else
                {
pendingIncomingRequestPlaceholder.gameObject.SetActive(false);
                }    

                //Loop through all the UserID's returned by the Friends callback and get their PublicUserData
                foreach (string userID in result.Value.friendsId)
                {
                    //Request the PublicUserData for the specific Friend
                    AccelBytePlugin.GetUser().GetUserByUserId(userID, userResult =>
                    {
                        //If it's an Error, report it and do nothing else
                        if (userResult.IsError)
                        {
                            Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
                        }
                        //If we have valid data, Instantiate the Prefab for the specific UI Piece and call relevant functions
                        else
                        {
                            FriendsIncomingPanel incomingPanel = Instantiate(pendingIncomingRequestsPrefab, pendingIncomingRequestsContent).GetComponent<FriendsIncomingPanel>();
                            //Pass the PublicUserData into this function
                            incomingPanel.Create(userResult.Value);
                        }
                    });
                }
            }
        });

        AccelBytePlugin.GetLobby().ListOutgoingFriends(result =>
        {
            if (result.IsError)
            {
                Debug.LogWarning($"Unable to get Outgoing Requests Code: {result.Error.Code}, Message: {result.Error.Message}");
            }
            else
            {
                if (result.Value.friendsId.Length <= 0)
                {
                    pendingOutgoingRequestPlaceholder.gameObject.SetActive(true);
                }
                else
                {
                    pendingOutgoingRequestPlaceholder.gameObject.SetActive(false);
                }
                foreach (string userID in result.Value.friendsId)
                {
                    AccelBytePlugin.GetUser().GetUserByUserId(userID, userResult =>
                    {
                        if (userResult.IsError)
                        {
                            Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
                        }
                        else
                        {
                            FriendsOutgoingPanel outgoingPanel = Instantiate(pendingOutgoingRequestsPrefab, pendingOutgoingRequestsContent).GetComponent<FriendsOutgoingPanel>();
                            outgoingPanel.Create(userResult.Value);                          
                        }                  
                    });                    
                }
            }
        });
    }

    private void DisplayBlocked()
    {
        //Cleanup First
LoopThroughTransformAndDestroy(blockedContent.transform,blockedDisplayPlaceholder);

        //Get Blocked List
        AccelBytePlugin.GetLobby().GetListOfBlockedUser(result =>
        {
            //Check for an Error
            if (result.IsError)
            {
                Debug.LogWarning($"Unable to get Blocked Player List Code: {result.Error.Code}, Message: {result.Error.Message}");

                blockedDisplayPlaceholder.gameObject.SetActive(true);
            }
            else
            {
                blockedDisplayPlaceholder.gameObject.SetActive(false);
                //Loop through all the UserID's returned by the callback and get their PublicUserData
                foreach (BlockedData blockedUser in result.Value.data)
                {
                    //Request the PublicUserData for the specific User
                    AccelBytePlugin.GetUser().GetUserByUserId(blockedUser.blockedUserId, userResult =>
                    {
                        //If it's an Error, report it and do nothing else
                        if (userResult.IsError)
                        {
                            Debug.LogWarning($"Unable to User Code: {userResult.Error.Code}, Message: {userResult.Error.Message}");
                        }
                        //If we have valid data, Instantiate the Prefab for the specific UI Piece and call relevant functions
                        else
                        {
                            FriendsBlockedPanel blockedPanel = Instantiate(blockedUserPrefab, blockedContent).GetComponent<FriendsBlockedPanel>();
                            //Pass the PublicUserData into this function
                            blockedPanel.Create(userResult.Value);                      
                        }                  
                    });                  
                }
            }
        });
    }

    /// <summary>
    /// A utility function to Destroy all Children of the parent transform. Optionally do not remove a specific Transform
    /// </summary>
    /// <param name="parent">Parent Object to destroy children</param>
    /// <param name="doNotRemove">Optional specified Transform that should NOT be destroyed</param>
    private static void LoopThroughTransformAndDestroy(Transform parent, Transform doNotRemove = null)
    {
        //Loop through all the children and add them to a List to then be deleted
        List<GameObject> toBeDeleted = new List<GameObject>();
        foreach (Transform t in parent)
        {
            //except the Do Not Remove transform if there is one
            if (t != doNotRemove)
            {
                    toBeDeleted.Add(t.gameObject);
            }
        }
        //Loop through list and Delete all Children
        for (int i = 0; i < toBeDeleted.Count; i++)
        {
            Destroy(toBeDeleted[i]);
        }
    }
}
FriendStatusPanel.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using System.Collections;
using System.Collections.Generic;
using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;


public class FriendStatusPanel : MonoBehaviour
{
    [SerializeField]
    private Image profilePicture;
    [SerializeField]
    private Image statusDisplay;

    [SerializeField]
    private Button chatButton;
    [SerializeField]
    private Button inviteToPartyButton;
    [SerializeField]
    private Button unfriendButton;
    [SerializeField]
    private Button blockButton;

    [SerializeField]
    private Text displayNameText;
    [SerializeField]
    private Text onlineStatusText;


    PublicUserData _userData;


    void SetOnlineStatus(FriendsStatusNotif notification)
    {
        switch (notification.availability)
        {
            case "offline":
                onlineStatusText.text = "Offline";
                statusDisplay.color = Color.black;
                break;
            case "online":
                onlineStatusText.text = "Online";
                statusDisplay.color = Color.green;
                break;
            case "busy":
                onlineStatusText.text = "Busy";
                statusDisplay.color = Color.yellow;
                break;
            case "invisible":
                onlineStatusText.text = "Offline";
                statusDisplay.color = Color.black;
                break;
            default:
                onlineStatusText.text = $"INVALID UNHANDLED {notification.availability}";
                statusDisplay.color = Color.magenta;
                break;
        }

        Debug.Log($"Friend Status for {notification.userID} changed to {notification.availability}");
    }

    public void Create(PublicUserData pud)
    {
        _userData = pud;
        displayNameText.text = _userData.displayName;
    }

    public void UpdateUser(FriendsStatusNotif notification)
    {
        SetOnlineStatus(notification);

    }

    /// <summary>
    /// Setup UI Button Listener
    /// </summary>
    public void SetupButton()
    {
        unfriendButton.onClick.AddListener(() =>
        {
            AccelBytePlugin.GetLobby().Unfriend(_userData.userId, result =>
            {
                if (result.IsError)
                {
                    Debug.Log($"Failed to unfriend a friend: code: {result.Error.Code}, message: {result.Error.Message}");
                }
                else
                {
                    Debug.Log("Successfully unfriend a friend!");
                    LobbyHandler.Instance.GetComponent<FriendsManagementHandler>().RefreshFriendsList();
                }
            });
        });
    }
}
FriendsIncomingPanel.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;

/// <summary>
/// Used to control the UI for the Incoming Pending Friend Request
/// </summary>
public class FriendsIncomingPanel : MonoBehaviour
{
    [SerializeField]
    private Image profilePicture;

    [SerializeField]
    private Text displayNameText;
    [SerializeField]
    private Button acceptFriendButton;
    [SerializeField]
    private Button declineFriendButton;
    [SerializeField]
    private Button blockFriendButton;

    private PublicUserData _userData;

    /// <summary>
    /// An initialisation Function required to be called to Populate the UI appropriately
    /// </summary>
    /// <param name="userData">The PublicUserData that is required to Populate the UI</param>
    public void Create(PublicUserData userData)
    {
        //Cache the PublicUserData
        _userData = userData;
        //Set the Display Name
        displayNameText.text = _userData.displayName;
        //Setup the Button to Accept a Friend Request
        acceptFriendButton.onClick.AddListener(() =>
        {
            //Make the Call to Accept the Friend Request using the cached PublicUserData UserID
            AccelBytePlugin.GetLobby().AcceptFriend(_userData.userId, result =>
            {
                if (result.IsError)
                {
                    //We would probably want to display some user-feedback if the request is not successful
                    Debug.Log($"Failed to accept a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
                }
                else
                {
                    Debug.Log("Accepted Friend Request");
                    //Destroy the UI Piece if the request was successful to remove it from the list
                    Destroy(gameObject);
                }
            });            
        });
        //Setup the Button to Decline a Friend Request
        declineFriendButton.onClick.AddListener(() =>
        {
            //Make the Call to Decline the Friend Request using the cached PublicUserData UserID
            AccelBytePlugin.GetLobby().RejectFriend(_userData.userId, result =>
            {
                if (result.IsError)
                {
                    //We would probably want to display some user-feedback if the request is not successful
                    Debug.Log($"Failed to decline a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
                }
                else
                {
                    Debug.Log("Declined Friend Request");
                    //Destroy the UI Piece if the request was successful to remove it from the list
                    Destroy(gameObject);
                }
            });
        });
        //Setup the Button to Block a Player
        blockFriendButton.onClick.AddListener(() =>
        {
            //Make the Call to Block a Player using the cached PublicUserData UserID
            AccelBytePlugin.GetLobby().BlockPlayer(_userData.userId, result =>
            {
                if (result.IsError)
                {
                    //We would probably want to display some user-feedback if the request is not successful
                    Debug.Log($"Failed to block a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
                }
                else
                {
                    Debug.Log("Block Friend Request");
                    //Destroy the UI Piece if the request was successful to remove it from the list
                    Destroy(gameObject);
                }
            });
        });
    }   
}
FriendsOutgoingPanel.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;

public class FriendsOutgoingPanel : MonoBehaviour
{
     [SerializeField]
    private Image profilePicture;

    [SerializeField]
    private Text displayNameText;
    [SerializeField]
    private Button cancelRequestButton;

    private PublicUserData _userData;

    /// <summary>
    /// An initialisation Function required to be called to Populate the UI appropriately
    /// </summary>
    /// <param name="userData">The PublicUserData that is required to Populate the UI</param>
    public void Create(PublicUserData userData)
    {
        //Cache the PublicUserData
        _userData = userData;
        //Set the Display Name
        displayNameText.text = _userData.displayName;
        //Setup the Button to Cancel A Friend Request
        cancelRequestButton.onClick.AddListener(() =>
        {
            //Make the Call to Reject the Friend Request using the cached PublicUserData UserID
AccelBytePlugin.GetLobby().CancelFriendRequest(_userData.userId, result =>
            {
                if (result.IsError)
                {
                    //We would probably want to display some user-feedback if the request is not successful
                    Debug.Log($"Failed to cancel a friend request: error code: {result.Error.Code} message: {result.Error.Message}");
                }
                else
                {
                    Debug.Log("Cancelled Friend Request");
                    //Destroy the UI Piece if the request was successful to remove it from the list
                    Destroy(gameObject);
                }
            });
        });
    }
}
FriendsBlockedPanel.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;

public class FriendsBlockedPanel : MonoBehaviour
{

    [SerializeField]
    private Image profilePicture;
    [SerializeField]
    private Text displayNameText;
    [SerializeField]
    private Button unblockButton;

    private PublicUserData _userData;

    /// <summary>
    /// An initialisation Function required to be called to Populate the UI appropriately
    /// </summary>
    /// <param name="userData">The PublicUserData that is required to Populate the UI</param>
    public void Create(PublicUserData userData)
    {
        //Cache the PublicUserData
        _userData = userData;
        //Set the Display Name
        displayNameText.text = _userData.displayName;
        //Setup the Button to Unblock a User
        unblockButton.onClick.AddListener(() =>
        {
            //Make the Call to Unblock the given User using the cached PublicUserData UserID
            AccelBytePlugin.GetLobby().UnblockPlayer(_userData.userId, result =>
            {
                    if (result.IsError)
                    {
                        //We would probably want to display some user-feedback if the request is not successful
                        Debug.Log($"Failed to unblock a player: error code: {result.Error.Code} message: {result.Error.Message}");
                    }
                    else
                    {
                        Debug.Log("Unblocked a Player");
                        //Destroy the UI Piece if the request was successful to remove it from the list
                        Destroy(gameObject);
                    }
            });
        });
    }   
}
riendsAddPanel.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using AccelByte.Api;
using AccelByte.Models;
using UnityEngine;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;

/// <summary>
/// Controls the UI Piece associated with Adding a Friend from a Search
/// </summary>
public class FriendsAddPanel : MonoBehaviour
{
    [SerializeField]
    private Image profilePicture;

    [SerializeField]
    private Text displayNameText;
    [SerializeField]
    private Button addFriendButton;

    private PublicUserInfo _userData;

    /// <summary>
    /// An initialisation Function required to be called to Populate the UI appropriately
    /// </summary>
    /// <param name="userInfo">The PublicUserInfo that is required to Populate the UI</param>
    public void Create(PublicUserInfo userInfo)
    {
        //Cache the PublicUserInfo
        _userData = userInfo;
        //Set the Display Name
        displayNameText.text = _userData.displayName;
        //Setup the Button to Request a Friend
        addFriendButton.onClick.AddListener(() =>
        {
            //Make the call to initiate the Friend Request
        AccelBytePlugin.GetLobby().RequestFriend(_userData.userId, result =>
            {
                    if (result.IsError)
                    {
                        Debug.Log($"Failed to send a friends request: error code: {result.Error.Code} message: {result.Error.Message}");
                    }
                    else
                    {
                        Debug.Log("Sent Friends Request");
                        //If we were successful, set the button to be non-intractable and change the Text
                        addFriendButton.interactable = false;
addFriendButton.GetComponentInChildren<Text>().text = "Request Sent";
                    }
            });
        });
    }   
}
MenuHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using UnityEngine;
using UnityEngine.UI;

public class MenuHandler : MonoBehaviour
{
    public Transform Menu;

    public Button FriendsButton;

    private bool isInitialized = false;

    public void Create()
    {
        if (isInitialized) return;

        isInitialized = true;

        FriendsButton.onClick.AddListener(() =>
        {
            GetComponent<FriendsManagementHandler>().Setup(FriendsManagementHandler.ExitMode.Menu);
            Menu.gameObject.SetActive(false);
            GetComponent<FriendsManagementHandler>().FriendsManagementWindow.SetActive(true);
        });
    }
}
LobbyHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using UnityEngine;
using AccelByte.Api;

public class LobbyHandler : MonoBehaviour
{
    /// <summary>
    /// Private Instance
    /// </summary>
    static LobbyHandler _instance;

    /// <summary>
    /// The Instance Getter
    /// </summary>
    public static LobbyHandler Instance => _instance;

    /// <summary>
    /// The Instance Getter
    /// </summary>
    private Lobby _lobby;

    [HideInInspector]
    public NotificationHandler notificationHandler;


    private void Awake()
    {
        // Check if another Instance is already created, and if so delete this one, otherwise destroy the object
        if (_instance != null && _instance != this)
        {
            Destroy(this);
            return;
        }
        else
        {
            _instance = this;
        }

        // Get the the object handler
        notificationHandler = gameObject.GetComponent<NotificationHandler>();
    }

    /// <summary>
    /// Connect to the <see cref="Lobby"/> and setup CallBacks
    /// </summary>
    public void ConnectToLobby()
    {
        //Get a reference to the instance of the Lobby
        _lobby = AccelBytePlugin.GetLobby();

        //Init menu handler
        GetComponent<MenuHandler>().Create();
        GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);

        //Connection
        _lobby.Connected += notificationHandler.OnConnected;
        _lobby.Disconnecting += notificationHandler.OnDisconnecting;
        _lobby.Disconnected += notificationHandler.OnDisconnected;

        //Friends
        _lobby.FriendsStatusChanged += notificationHandler.OnFriendsStatusChanged;
        _lobby.FriendRequestAccepted += notificationHandler.OnFriendRequestAccepted;
        _lobby.OnIncomingFriendRequest += notificationHandler.OnIncomingFriendRequest;
        _lobby.FriendRequestCanceled += notificationHandler.OnFriendRequestCanceled;
        _lobby.FriendRequestRejected += notificationHandler.OnFriendRequestRejected;
        _lobby.OnUnfriend += notificationHandler.OnUnfriend;

        //Connect to the Lobby
        if (!_lobby.IsConnected)
        {
            _lobby.Connect();
        }
    }

    public void RemoveLobbyListeners()
    {
        //Remove delegate from Lobby
        //Connection
        _lobby.Connected -= notificationHandler.OnConnected;
        _lobby.Disconnecting -= notificationHandler.OnDisconnecting;
        _lobby.Disconnected -= notificationHandler.OnDisconnected;

        //Friends
        _lobby.FriendsStatusChanged -= notificationHandler.OnFriendsStatusChanged;
        _lobby.FriendRequestAccepted -= notificationHandler.OnFriendRequestAccepted;
        _lobby.OnIncomingFriendRequest -= notificationHandler.OnIncomingFriendRequest;
        _lobby.FriendRequestCanceled -= notificationHandler.OnFriendRequestCanceled;
        _lobby.FriendRequestRejected -= notificationHandler.OnFriendRequestRejected;
        _lobby.OnUnfriend -= notificationHandler.OnUnfriend;
    }

    public void DisconnectFromLobby()
    {
        if (AccelBytePlugin.GetLobby().IsConnected)
        {
            AccelBytePlugin.GetLobby().Disconnect();
        }
    }

    private void OnApplicationQuit()
    {
        // Attempt to Disconnect from the Lobby when the Game Quits
        DisconnectFromLobby();
    }
}
NotificationHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using UnityEngine;
using AccelByte.Models;
using AccelByte.Core;

public class NotificationHandler : MonoBehaviour
{
    #region Notifications

    // Collection of connection notifications
    #region Connections
    /// <summary>
    /// Called when lobby is connected
    /// </summary>
    public void OnConnected()
    {
        Debug.Log("Lobby Connected");
    }

    /// <summary>
    /// Called when connection is disconnecting
    /// </summary>
    /// <param name="result"> Contains data of message</param>
    public void OnDisconnecting(Result<DisconnectNotif> result)
    {
        Debug.Log($"Lobby Disconnecting {result.Value.message}");
    }

    /// <summary>
    /// Called when connection is being disconnected
    /// </summary>
    /// <param name="result"> Contains data of websocket close code</param>
    public void OnDisconnected(WsCloseCode result)
    {
        Debug.Log($"Lobby Disconnected: {result}");
    }
    #endregion

    // Collection of friend notifications
    #region Friends
    /// <summary>
    /// Called when friend status is changed
    /// </summary>
    /// <param name="result"> Contains data of user id, availability, status, etc</param>
    public void OnFriendsStatusChanged(Result<FriendsStatusNotif> result)
    {
        GetComponent<FriendsManagementHandler>().UpdateFriends(result.Value);
    }

    /// <summary>
    /// Called when friend request is accepted
    /// </summary>
    /// <param name="result"> Contains data of friend's user id</param>
    public void OnFriendRequestAccepted(Result<Friend> result)
    {
        Debug.Log($"Accepted a Friend Request from user {result.Value.friendId}");
    }

    /// <summary>
    /// Called when there is incomming friend request
    /// </summary>
    /// <param name="result"> Contains data of friend's user id</param>
    public void OnIncomingFriendRequest(Result<Friend> result)
    {
        Debug.Log($"Received a Friend Request from user {result.Value.friendId}");
    }

    /// <summary>
    /// Called when friend is unfriend
    /// </summary>
    /// <param name="result"> Contains data of friend's user id</param>
    public void OnUnfriend(Result<Friend> result)
    {
        Debug.Log($"Unfriended User {result.Value.friendId}");
    }

    /// <summary>
    /// Called when friend request is canceled
    /// </summary>
    /// <param name="result"> Contains data of sender user id</param>
    public void OnFriendRequestCanceled(Result<Acquaintance> result)
    {
        Debug.Log($"Cancelled a Friend Request from user {result.Value.userId}");
    }

    /// <summary>
    /// Called when friend request is rejected
    /// </summary>
    /// <param name="result"> Contains data of rejector user id</param>
    public void OnFriendRequestRejected(Result<Acquaintance> result)
    {
        Debug.Log($"Rejected a Friend Request from user {result.Value.userId}");
    }
    #endregion

    #endregion
}