Skip to main content

Find friends with the OSS - Search for players - (Unreal Engine module)

Last updated on October 24, 2024

Unwrap the Subsystem

In this section, you will learn how to implement finding potential friends by their display name and friend code using AccelByte Gaming Services (AGS) Online Subsystem (OSS). In the Byte Wars project, there is already a Game Instance Subsystem created named FriendsSubsystem. This subsystem contains friends-related functionality, including finding potential friends. In this tutorial, you will use a starter version of that subsystem, the FriendsSubsystem_Starter, so you can implement friends-functionalities from scratch.

What's in the Starter Pack

To follow this tutorial, a starter subsystem class named FriendsSubsystem_Starter has been prepared for you. This class is available in the Resources sections and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Social/FriendsEssentials/FriendsSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Social/FriendsEssentials/FriendsSubsystem_Starter.cpp

The FriendsSubsystem_Starter class has several functionalities already included.

  • The AGS OSS interfaces declarations named FriendsInterface and UserInterface. You will use these interfaces to implement friends-related functionalities later.

    protected:
    // ...
    FOnlineUserAccelBytePtr UserInterface;
    FOnlineFriendsAccelBytePtr FriendsInterface;
  • A helper function to get UniqueNetId from a PlayerController. You will need these helpers for the AGS OSS interfaces mentioned above.

    FUniqueNetIdPtr UFriendsSubsystem_Starter::GetUniqueNetIdFromPlayerController(const APlayerController* PC) const
    {
    if (!PC)
    {
    return nullptr;
    }

    ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (!LocalPlayer)
    {
    return nullptr;
    }

    return LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    }
  • A helper function to get the LocalUserNum from a PlayerController. You will need these helpers for the AGS OSS interfaces later, too.

    int32 UFriendsSubsystem_Starter::GetLocalUserNumFromPlayerController(const APlayerController* PC) const
    {
    int32 LocalUserNum = 0;

    if (!PC)
    {
    return LocalUserNum;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (LocalPlayer)
    {
    LocalUserNum = LocalPlayer->GetControllerId();
    }

    return LocalUserNum;
    }

In addition to the starter subsystem, there are some constants, delegates, and other helpers prepared in the /Source/AccelByteWars/TutorialModules/Social/FriendsEssentials/FriendsEssentialsModels.h file. In that file, you can find the following helpers:

  • An enum that defines a friends' status. You will need this to distinguish between friend types, such as whether the friend is already invited, already accepted as a friend, etc.

    UENUM()
    enum class EFriendStatus : uint8
    {
    Accepted = 0,
    PendingInbound,
    PendingOutbound,
    Searched,
    Blocked,
    Unknown
    };
  • A helper class named FriendData that contains the friend's information such as display name, avatar URL, and statuses. This data will be used to assign data to the entry widget, hence, we're using UObject instead of a struct or a regular CPP class.

    UCLASS()
    class ACCELBYTEWARS_API UFriendData : public UObject
    {
    GENERATED_BODY()

    public:
    UFriendData() : bIsOnline(false), bCannotBeInvited(false) {}

    FUniqueNetIdPtr UserId;
    FString DisplayName;
    FString AvatarURL;
    EFriendStatus Status = EFriendStatus::Unknown;

    bool bIsOnline;
    FDateTime LastOnline;

    bool bCannotBeInvited;
    FString ReasonCannotBeInvited;

    static UFriendData* ConvertToFriendData(TSharedRef<FOnlineUser> OnlineUser)
    {
    UFriendData* FriendData = NewObject<UFriendData>();

    FriendData->UserId = OnlineUser->GetUserId();
    FriendData->DisplayName = OnlineUser->GetDisplayName();
    OnlineUser->GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, FriendData->AvatarURL);
    FriendData->Status = EFriendStatus::Unknown;
    FriendData->bCannotBeInvited = false;

    return FriendData;
    }

    static UFriendData* ConvertToFriendData(TSharedRef<FOnlineFriend> OnlineUser)
    {
    UFriendData* FriendData = ConvertToFriendData(StaticCast<TSharedRef<FOnlineUser>>(OnlineUser));

    switch (OnlineUser->GetInviteStatus())
    {
    case EInviteStatus::Accepted:
    FriendData->Status = EFriendStatus::Accepted;
    FriendData->bCannotBeInvited = true;
    FriendData->ReasonCannotBeInvited = NSLOCTEXT("AccelByteWars", "Already friend", "Already friend").ToString();
    break;
    case EInviteStatus::PendingInbound:
    FriendData->Status = EFriendStatus::PendingInbound;
    FriendData->bCannotBeInvited = true;
    FriendData->ReasonCannotBeInvited = NSLOCTEXT("AccelByteWars", "You've been invited", "You've been invited").ToString();
    break;
    case EInviteStatus::PendingOutbound:
    FriendData->Status = EFriendStatus::PendingOutbound;
    FriendData->bCannotBeInvited = true;
    FriendData->ReasonCannotBeInvited = NSLOCTEXT("AccelByteWars", "Already invited", "Already invited").ToString();
    break;
    case EInviteStatus::Blocked:
    FriendData->Status = EFriendStatus::Blocked;
    FriendData->bCannotBeInvited = true;
    FriendData->ReasonCannotBeInvited = NSLOCTEXT("AccelByteWars", "Blocked", "Blocked").ToString();
    break;
    default:
    FriendData->Status = EFriendStatus::Unknown;
    FriendData->bCannotBeInvited = false;
    }

    return FriendData;
    }

    static UFriendData* ConvertToFriendData(TSharedRef<FOnlineBlockedPlayer> OnlineUser)
    {
    UFriendData* FriendData = ConvertToFriendData(StaticCast<TSharedRef<FOnlineUser>>(OnlineUser));

    FriendData->Status = EFriendStatus::Blocked;
    FriendData->bCannotBeInvited = true;
    FriendData->ReasonCannotBeInvited = NSLOCTEXT("AccelByteWars", "Blocked", "Blocked").ToString();

    return FriendData;
    }
    };
  • Delegates can be used as a callback upon caching friends' data completed, finding friends process completed, and sending friend requests completed.

    DECLARE_DELEGATE_ThreeParams(FOnGetCacheFriendListComplete, bool /*bWasSuccessful*/, TArray<TSharedRef<FOnlineFriend>>& /*CachedFriendList*/, const FString& /*ErrorMessage*/);
    // ...
    DECLARE_DELEGATE_ThreeParams(FOnGetSelfFriendCodeComplete, bool /*bWasSuccessful*/, UFriendData* /*FriendData*/, const FString& /*FriendCode*/);
    DECLARE_DELEGATE_ThreeParams(FOnFindFriendComplete, bool /*bWasSuccessful*/, UFriendData* /*FriendData*/, const FString& /*ErrorMessage*/);
    DECLARE_DELEGATE_ThreeParams(FOnSendFriendRequestComplete, bool /*bWasSuccessful*/, UFriendData* /*FriendData*/, const FString& /*ErrorMessage*/);

Implement find friend by display name

In this section, you will implement functionality to find potential friends by their display name.

  1. Before finding potential friends and sending them friend invitation requests, it is possible that the potential friends are already a friend to the player. Therefore, you need to get the player's friend list first. Then, you will use the list to check whether the found potential friends are already a friend or not. Open the FriendsSubsystem_Starter class Header file and declare the following function:

    protected:
    // ...
    void GetCacheFriendList(const int32 LocalUserNum, const FOnGetCacheFriendListComplete& OnComplete = FOnGetCacheFriendListComplete());
  2. Create the definition for the function above. Open the FriendsSubsystem_Starter class CPP file and add the code below. The function will request to get the friend list from the backend and cache the list. Once the list is cached, you can directly access it without requesting to the backend again.

    void UFriendsSubsystem_Starter::GetCacheFriendList(const int32 LocalUserNum, const FOnGetCacheFriendListComplete& OnComplete)
    {
    if (!ensure(FriendsInterface))
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Cannot cache friend list. Friends Interface is not valid."));
    return;
    }

    // Try to get cached friend list first.
    TArray<TSharedRef<FOnlineFriend>> CachedFriendList;
    if (FriendsInterface->GetFriendsList(LocalUserNum, TEXT(""), CachedFriendList))
    {
    // Then, update the cached friends' information by querying their user information.
    TPartyMemberArray FriendIds;
    for (const TSharedRef<FOnlineFriend>& CachedFriend : CachedFriendList)
    {
    FriendIds.Add(CachedFriend.Get().GetUserId());
    }

    // Query friends' user information.
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    LocalUserNum,
    FriendIds,
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, OnComplete, LocalUserNum](
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    // Refresh friends data with queried friend's user information.
    TArray<TSharedRef<FOnlineFriend>> NewCachedFriendList;
    FriendsInterface->GetFriendsList(LocalUserNum, TEXT(""), NewCachedFriendList);
    for (const TSharedRef<FOnlineFriend>& NewCachedFriend : NewCachedFriendList)
    {
    // Update friend's avatar URL based on queried friend's user information.
    FString UserAvatarURL;
    TSharedPtr<FOnlineUser> UserInfo = UserInterface->GetUserInfo(
    LocalUserNum, NewCachedFriend.Get().GetUserId().Get());
    UserInfo->GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, UserAvatarURL);
    StaticCastSharedRef<FOnlineFriendAccelByte>(NewCachedFriend).Get().SetUserAttribute(
    ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, UserAvatarURL);
    }

    OnComplete.ExecuteIfBound(true, NewCachedFriendList, TEXT(""));
    }));
    }
    }
    // If none, request to backend then get the cached the friend list.
    else
    {
    FriendsInterface->ReadFriendsList(
    LocalUserNum,
    TEXT(""),
    FOnReadFriendsListComplete::CreateWeakLambda(this, [this, OnComplete](int32 LocalUserNum, bool bWasSuccessful, const FString& ListName, const FString& Error)
    {
    TArray<TSharedRef<FOnlineFriend>> CachedFriendList;
    FriendsInterface->GetFriendsList(LocalUserNum, TEXT(""), CachedFriendList);

    OnComplete.ExecuteIfBound(bWasSuccessful, CachedFriendList, Error);
    }
    ));
    }
    }
  3. Back in the FriendsSubsystem_Starter class Header file, create a function declaration to find a potential friend.

    public:
    // ...
    void FindFriend(const APlayerController* PC, const FString& InKeyword, const FOnFindFriendComplete& OnComplete = FOnFindFriendComplete());
  4. You will also need to create a callback to handle the find a potential friend process completion.

    protected:
    // ...
    void OnFindFriendComplete(bool bWasSuccessful, const FUniqueNetId& UserId, const FString& DisplayName, const FUniqueNetId& FoundUserId, const FString& Error, int32 LocalUserNum, const FOnFindFriendComplete OnComplete);
  5. Create the definitions for the functions above. Open the FriendsSubsystem_Starter class CPP file and define the FindFriend() function. This function will try to get the cached friend list first by calling the GetCacheFriendList() function before performing the find a potential friend by its display name. The cached list will be used to check whether the found potential friend is already a friend or not. This check will be handled by the OnFindFriendComplete() function, which is the callback when the finding process is complete.

    void UFriendsSubsystem_Starter::FindFriend(const APlayerController* PC, const FString& InKeyword, const FOnFindFriendComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(UserInterface))
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Cannot find a friend. Friends Interface or User Interface is not valid."));
    return;
    }

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    const FUniqueNetIdPtr LocalPlayerId = GetUniqueNetIdFromPlayerController(PC);
    if (!ensure(LocalPlayerId.IsValid()))
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Cannot find friends. LocalPlayer NetId is not valid."));
    return;
    }

    GetCacheFriendList(LocalUserNum, FOnGetCacheFriendListComplete::CreateWeakLambda(this, [this, LocalPlayerId, LocalUserNum, InKeyword, OnComplete](bool bWasSuccessful, TArray<TSharedRef<FOnlineFriend>>& CachedFriendList, const FString& ErrorMessage)
    {
    if (bWasSuccessful)
    {
    // Find friend by exact display name.
    UserInterface->QueryUserIdMapping(LocalPlayerId.ToSharedRef().Get(), InKeyword, IOnlineUser::FOnQueryUserMappingComplete::CreateUObject(this, &ThisClass::OnFindFriendComplete, LocalUserNum, OnComplete));
    }
    else
    {
    OnComplete.ExecuteIfBound(false, nullptr, ErrorMessage);
    }
    }));
    }
  6. Define the OnFindFriendComplete() function. When successful, this function will check whether the potential friend is already a friend or not and then return the result by calling the callback delegate.

    void UFriendsSubsystem_Starter::OnFindFriendComplete(bool bWasSuccessful, const FUniqueNetId& UserId, const FString& DisplayName, const FUniqueNetId& FoundUserId, const FString& Error, int32 LocalUserNum, const FOnFindFriendComplete OnComplete)
    {
    if (bWasSuccessful)
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Success to find a friend with keyword: %s"), *DisplayName);

    // Check if the found user is the player it self.
    if (UserId == FoundUserId)
    {
    OnComplete.ExecuteIfBound(false, nullptr, CANNOT_INVITE_FRIEND_SELF.ToString());
    return;
    }

    // Check if the found user is already friend.
    TSharedPtr<FOnlineFriend> FoundFriend = FriendsInterface->GetFriend(LocalUserNum, FoundUserId, TEXT(""));
    if (FoundFriend.IsValid())
    {
    OnComplete.ExecuteIfBound(true, UFriendData::ConvertToFriendData(FoundFriend.ToSharedRef()), TEXT(""));
    return;
    }

    // Request the found user information to backend (to retrieve avatar URL, display name, etc.)
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    LocalUserNum,
    TPartyMemberArray{ FoundUserId.AsShared() },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, OnComplete](
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (Error.bSucceeded && !UsersInfo.IsEmpty())
    {
    OnComplete.ExecuteIfBound(true, UFriendData::ConvertToFriendData(UsersInfo[0].ToSharedRef()), TEXT(""));
    }
    else
    {
    OnComplete.ExecuteIfBound(false, nullptr, Error.ErrorMessage.ToString());
    }
    }));
    }
    }
    else
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Failed to find a friend with keyword: %s"), *DisplayName);
    OnComplete.ExecuteIfBound(false, nullptr, Error);
    }
    }

Implement send friend request

In this section, you will implement sending a friend invitation request.

  1. Open the FriendsSubsystem_Starter class Header file and declare the following function:

    public:
    // ...
    void SendFriendRequest(const APlayerController* PC, const FUniqueNetIdRepl FriendUserId, const FOnSendFriendRequestComplete& OnComplete = FOnSendFriendRequestComplete());
  2. Create a callback function to handle when the sending friend request process is complete.

    protected:
    // ...
    void OnSendFriendRequestComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnSendFriendRequestComplete OnComplete);
  3. Define the functions above. Open the FriendsSubsystem_Starter class CPP file and define the SendFriendRequest() function first. This function will send a friend request and call the OnSendFriendRequestComplete() function to handle the callback.

    void UFriendsSubsystem_Starter::SendFriendRequest(const APlayerController* PC, const FUniqueNetIdRepl FriendUserId, const FOnSendFriendRequestComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(PromptSubsystem))
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Cannot send friend request. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    PromptSubsystem->ShowLoading(SEND_FRIEND_REQUEST_MESSAGE);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    // Send friend requests by friend's user ID. We leave the ListName argument empty since the AccelByte OSS does not require it.
    FriendsInterface->SendInvite(LocalUserNum, *FriendUserId.GetUniqueNetId().Get(), TEXT(""), FOnSendInviteComplete::CreateUObject(this, &ThisClass::OnSendFriendRequestComplete, OnComplete));
    }
  4. Define the OnSendFriendRequestComplete() function. This function will show a pop-up notification telling the player that their request successfully sent, and trigger the OnComplete delegate, so the caller can do something when this complete function triggers.

    void UFriendsSubsystem_Starter::OnSendFriendRequestComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnSendFriendRequestComplete OnComplete)
    {
    PromptSubsystem->HideLoading();

    TSharedPtr<FOnlineFriend> FoundFriend = FriendsInterface->GetFriend(LocalUserNum, FriendId, TEXT(""));
    if (bWasSuccessful && FoundFriend.IsValid())
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Success to send a friend request."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_SEND_FRIEND_REQUEST);
    OnComplete.ExecuteIfBound(true, UFriendData::ConvertToFriendData(FoundFriend.ToSharedRef()), TEXT(""));
    }
    else
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Failed to send a friend request. Error: %s"), *ErrorStr);

    PromptSubsystem->ShowMessagePopUp(ERROR_PROMPT_TEXT, FText::FromString(ErrorStr));
    OnComplete.ExecuteIfBound(false, nullptr, ErrorStr);
    }
    }

Implement send friend request by friend code

In this section, you will implement sending a friend invite using a friend code.

  1. Open the FriendsSubsystem_Starter class Header file and declare the following function:

    public:
    // ...
    void GetSelfFriendCode(const APlayerController* PC, const FOnGetSelfFriendCodeComplete& OnComplete = FOnGetSelfFriendCodeComplete());
  2. Open the FriendsSubsystem_Starter class CPP file and define the function above. This function will get the current logged-in player's friend code.

    void UFriendsSubsystem_Starter::GetSelfFriendCode(const APlayerController* PC, const FOnGetSelfFriendCodeComplete& OnComplete)
    {
    if (!ensure(UserInterface))
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Cannot get self friend code. User Interface is not valid."));
    return;
    }

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    const FUniqueNetIdPtr LocalPlayerId = GetUniqueNetIdFromPlayerController(PC);
    if (!ensure(LocalPlayerId.IsValid()))
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Cannot get self friend code. LocalPlayer NetId is not valid."));
    return;
    }

    // Try to get friend code from cache.
    if (const TSharedPtr<FOnlineUser> UserInfo = UserInterface->GetUserInfo(LocalUserNum, LocalPlayerId.ToSharedRef().Get()))
    {
    if (const TSharedPtr<FUserOnlineAccountAccelByte> UserAccount = StaticCastSharedPtr<FUserOnlineAccountAccelByte>(UserInfo))
    {
    const FString FriendCode = UserAccount->GetPublicCode();
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Successfully obtained self friend code: %s"), *FriendCode);
    OnComplete.ExecuteIfBound(true, UFriendData::ConvertToFriendData(UserInfo.ToSharedRef()), FriendCode);
    return;
    }
    }

    // If not available on cache then query the user info to get friend code.
    if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
    {
    StartupSubsystem->QueryUserInfo(
    LocalUserNum,
    TPartyMemberArray{ LocalPlayerId.ToSharedRef() },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, OnComplete](
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (!Error.bSucceeded || UsersInfo.IsEmpty())
    {
    UE_LOG_FRIENDS_ESSENTIALS(
    Warning, TEXT("Failed to get self friend code: User info query has failed."));
    OnComplete.ExecuteIfBound(false, nullptr, TEXT(""));
    return;
    }

    const FString FriendCode = UsersInfo[0]->GetPublicCode();
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Successfully obtained self friend code: %s"), *FriendCode);
    OnComplete.ExecuteIfBound(true, UFriendData::ConvertToFriendData(UsersInfo[0].ToSharedRef()), FriendCode);
    }));
    }
    }
  3. Next, open the FriendsSubsystem_Starter class Header file and declare the following function to send a friend request by a friend code:

    public:
    // ...
    void SendFriendRequest(const APlayerController* PC, const FString& FriendCode, const FOnSendFriendRequestComplete& OnComplete = FOnSendFriendRequestComplete());
  4. Create a callback function to handle when the request is complete.

    protected:
    // ...
    void OnSendFriendRequestByFriendCodeComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnSendFriendRequestComplete OnComplete);
  5. Open the FriendsSubsystem_Starter class CPP file and define the SendFriendRequest function. This function will send a friend request directly using the friend code and call OnSendFriendRequestByFriendCodeComplete function to handle the callback.

    void UFriendsSubsystem_Starter::SendFriendRequest(const APlayerController* PC, const FString& FriendCode, const FOnSendFriendRequestComplete& OnComplete)
    {
    if (!ensure(FriendsInterface))
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Cannot send friend request by friend code. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    // Send friend requests by friend code. We leave the ListName argument empty since the AccelByte OSS does not require it.
    FriendsInterface->SendInvite(LocalUserNum, FriendCode, TEXT(""), FOnSendInviteComplete::CreateUObject(this, &ThisClass::OnSendFriendRequestByFriendCodeComplete, OnComplete));
    }
  6. Define the OnSendFriendRequestByFriendCodeComplete function that will show a pop-up notification saying the invite was sent successfully, then trigger the OnComplete delegate, so the caller can do something when this function triggers.

    void UFriendsSubsystem_Starter::OnSendFriendRequestByFriendCodeComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnSendFriendRequestComplete OnComplete)
    {
    // Get cached friend info to retrieve the updated friend data to be returned to the callback.
    FUniqueNetIdPtr FriendUserId = FriendId.AsShared();
    GetCacheFriendList(
    LocalUserNum,
    FOnGetCacheFriendListComplete::CreateWeakLambda(this, [this, LocalUserNum, bWasSuccessful, FriendUserId, ErrorStr, OnComplete]
    (bool bQueryWasSuccessful, TArray<TSharedRef<FOnlineFriend>>& CachedFriendList, const FString& ErrorMessage)
    {
    UFriendData* FriendData = nullptr;
    if (FriendUserId)
    {
    TSharedPtr<FOnlineFriend> FoundFriend = FriendsInterface->GetFriend(LocalUserNum, FriendUserId.ToSharedRef().Get(), TEXT(""));
    if (FoundFriend)
    {
    FriendData = UFriendData::ConvertToFriendData(FoundFriend.ToSharedRef());
    }
    }

    if (bWasSuccessful)
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Success to send a friend request by friend code."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_SEND_FRIEND_REQUEST_BY_FRIEND_CODE);
    OnComplete.ExecuteIfBound(true, FriendData, TEXT(""));
    }
    else
    {
    UE_LOG_FRIENDS_ESSENTIALS(Warning, TEXT("Failed to send a friend request by friend code. Error: %s"), *ErrorStr);
    OnComplete.ExecuteIfBound(false, FriendData, ErrorStr);
    }
    }
    ));
    }

Resources