Querying for Epic Friends and their status

Rajen Kishna, Technical Account Manager, Epic Games |
October 19, 2021
To round out the Epic Online Services Getting Started articles on Epic Account Services—after having covered the authentication and presence interfaces—we’ll cover how to query for Epic Friends and their status using the Friends Interface. In this article, we’ll go over:
 

Friends API scope

Before we dive into the query API, it’s important to understand how these APIs are scoped. As briefly explained in the previous article, by default the friends APIs will not return the full list of friends of a player that they would see in the Epic Games Launcher, for example. As we’ve seen, players will be prompted to consent to our EAS application accessing the specific information we specify (basic profile, presence, friends). Only after a player’s friend has provided this consent will the Friends Interface return that particular friend when querying. If players purchase the game through the Epic Games Store, consent is implicitly provided as part of the purchase flow. To illustrate:
 
  • Player A is friends with Player B and Player C, and purchases the game through Epic Games Store
  • Player B does not own the game
  • Player C purchases the game through Steam

In this scenario, Player A will not see any friends in the game when we call the query API until Player C launches the game and provides consent as part of the authentication flow. Player B will never be returned by the query API as they do not own the game.

Note that per the documentation, there are currently no APIs to manage friend requests or adding/deleting friends for the player. The Social Overlay will be expanded later this year to assist with that functionality.

Adding friends permission to the application

Similar to what we’ve done with presence, we’ll have to add the friends permission to our EAS application to get the proper consent from our players:
 
  1. Log in to the Developer Portal at https://dev.epicgames.com/portal/
  2. Navigate to your product and click on Epic Account Services in the left menu
  3. Click on the Configure button next to the application you’re using
  4. Click on the Permissions tab at the top right
  5. Enable the Friends (friends_list) permission. Note that you’ll see an update to the preview at the left, which now includes consent to friends information
  6. Click Save to confirm
Developer Portal Friends Permission
Adding the Friends permission to an EAS application

Lastly, we can open ApplicationSettings.cs in our Visual Studio solution and add the presence ScopeFlag:

public AuthScopeFlags ScopeFlags
{
    get
    {
        return AuthScopeFlags.BasicProfile | AuthScopeFlags.Presence | AuthScopeFlags.FriendsList;
    }
}


When you run the app and trigger Auth login, you should now see the updated consent screen in the browser and still be able to successfully complete authentication.

Querying for friends

Now that that’s added, let’s start implementing the query functionality in our solution:
 
  1. Add a new UserControl to the Views folder, called FriendsView
  2. Paste in the following XAML, replacing the empty Grid node:
 

<Grid>

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="*" />

        <ColumnDefinition Width="Auto" />

    </Grid.ColumnDefinitions>


    <StackPanel Grid.Column="1">

        <Button Width="100" Height="23" Margin="2" Content="Query friends" Command="{Binding FriendsQuery}" />

        <Button Width="100" Height="23" Margin="2" Content="Subscribe" Command="{Binding FriendsSubscribeUpdates}" />

        <Button Width="100" Height="23" Margin="2" Content="Unsubscribe" Command="{Binding FriendsUnsubscribeUpdates}" />

        <Button Width="100" Height="23" Margin="2" Content="Query status" Command="{Binding PresenceQuery}" CommandParameter="{Binding SelectedFriend}" />

    </StackPanel>


    <ListView x:Name="FriendsListView" Grid.Column="0" Margin="2" ItemsSource="{Binding Friends}" SelectedItem="{Binding SelectedFriend}" SelectionChanged="FriendsListView_SelectionChanged">

        <ListView.View>

            <GridView>

                <GridViewColumn Header="Friend" Width="300" DisplayMemberBinding="{Binding EpicAccountId}">

                    <GridViewColumn.HeaderContainerStyle>

                        <Style TargetType="{x:Type GridViewColumnHeader}">

                            <Setter Property="HorizontalContentAlignment" Value="Left" />

                        </Style>

                    </GridViewColumn.HeaderContainerStyle>

                </GridViewColumn>

                <GridViewColumn Header="Friend Status" Width="150" DisplayMemberBinding="{Binding FriendsStatus}">

                    <GridViewColumn.HeaderContainerStyle>

                        <Style TargetType="{x:Type GridViewColumnHeader}">

                            <Setter Property="HorizontalContentAlignment" Value="Left" />

                        </Style>

                    </GridViewColumn.HeaderContainerStyle>

                </GridViewColumn>

                <GridViewColumn Header="Status" Width="150" DisplayMemberBinding="{Binding Status}">

                    <GridViewColumn.HeaderContainerStyle>

                        <Style TargetType="{x:Type GridViewColumnHeader}">

                            <Setter Property="HorizontalContentAlignment" Value="Left" />

                        </Style>

                    </GridViewColumn.HeaderContainerStyle>

                </GridViewColumn>

            </GridView>

        </ListView.View>

    </ListView>

</Grid>

 
  • We’ll use a ListView to display the friends we get back from the API and have additional buttons to subscribe and unsubscribe to/from updates, as well as query for friend’s presence.
 
  1. Open FriendsView.xaml.cs and define the DataContext:

public partial class FriendsView : UserControl
{
    public FriendsViewModel ViewModel { get { return ViewModelLocator.Friends; } }

    public FriendsView()
    {
        InitializeComponent();
        DataContext = ViewModel;
    }

    private void FriendsListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
    }
}

 
  • Note the empty FriendsListView_SelectionChanged event handler, which we’ll use later when we implement friend presence status querying.
 
  1. As we’ll combine a few things in our UI to display friends (Epic Account Id, Friend status, Presence status), create a new folder called Models to the root of the project and add a Friend.cs class:

public class Friend : BindableBase
{
    private EpicAccountId _epicAccountId;
    public EpicAccountId EpicAccountId
    {
        get { return _epicAccountId; }
        set { SetProperty(ref _epicAccountId, value); }
    }

    private FriendsStatus _friendsStatus;
    public FriendsStatus FriendsStatus
    {
        get { return _friendsStatus; }
        set { SetProperty(ref _friendsStatus, value); }
    }

    private Status _status;
    public Status Status
    {
        get { return _status; }
        set { SetProperty(ref _status, value); }
    }
}

 
  1. Add a FriendsViewModel.cs class to the ViewModels folder:

public class FriendsViewModel : BindableBase
{
    private ObservableCollection<Friend> _friends;
    public ObservableCollection<Friend> Friends
    {
        get { return _friends; }
        set { SetProperty(ref _friends, value); }
    }

    private Friend _selectedFriend;
    public Friend SelectedFriend
    {
        get { return _selectedFriend; }
        set { SetProperty(ref _selectedFriend, value); }
    }

    private ulong _notificationId;
    public ulong NotificationId
    {
        get { return _notificationId; }
        set { SetProperty(ref _notificationId, value); }
    }
}

 
  1. Add a static reference to FriendsViewModel to ViewModelLocator:

private static FriendsViewModel _friends;
public static FriendsViewModel Friends
{
    get { return _friends ??= new FriendsViewModel(); }
}

 
  1. Add a FriendsService.cs class to the Services folder:

public static class FriendsService
{
    public static void QueryFriends()
    {
        var queryFriendsOptions = new QueryFriendsOptions()
        {
            LocalUserId =​​​​​ EpicAccountId.FromString(ViewModelLocator.Main.AccountId)
        };

        ViewModelLocator.Main.StatusBarText = $"Querying friends...";

        App.Settings.PlatformInterface.GetFriendsInterface()
.QueryFriends(queryFriendsOptions, null,
(QueryFriendsCallbackInfo queryFriendsCallbackInfo) =>
        {
            Debug.WriteLine($"QueryFriends {queryFriendsCallbackInfo.ResultCode}");

            if (queryFriendsCallbackInfo.ResultCode == Result.Success)
            {
                var getFriendsCountOptions = new GetFriendsCountOptions()
                {
                    LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId)
                };
                var friendsCount = App.Settings.PlatformInterface
.GetFriendsInterface()
.GetFriendsCount(getFriendsCountOptions);

                for (int i = 0; i < friendsCount; i++)
                {
                    var getFriendAtIndexOptions = new GetFriendAtIndexOptions()
                    {
                        LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
                        Index = i
                    };
                    var friend = App.Settings.PlatformInterface.GetFriendsInterface()
.GetFriendAtIndex(getFriendAtIndexOptions);

                    if (friend != null)
                    {
                        var getStatusOptions = new GetStatusOptions()
                        {
                            LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
                            TargetUserId = friend
                        };
                        var friendStatus = App.Settings.PlatformInterface.GetFriendsInterface()
.GetStatus(getStatusOptions);

                        ViewModelLocator.Friends.Friends.Add(new Friend()
                        {
                            EpicAccountId = ​​​​​​​friend,
                            FriendsStatus = ​​​​​​​friendStatus
                        });
                    }
                }
            }

            ViewModelLocator.Main.StatusBarText = string.Empty;
        });
    }
}

 
  • We instantiate an options class, pass it to the QueryFriends API on the Friends Interface, retrieve the count from cache, and iterate to retrieve the status for each friend.
  • We then add the friend, including friend status, to the ViewModel to be displayed in the UI.
 
  1. Add a FriendsQueryCommand.cs class to the Commands folder:

public class FriendsQueryCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId);
    }

    public override void Execute(object parameter)
    {
        ViewModelLocator.Friends.Friends = new ObservableCollection<Friend>();
        FriendsService.QueryFriends();
    }
}

 
  1. Now open FriendsViewModel.cs again and instantiate FriendsQueryCommand:

public FriendsQueryCommand FriendsQuery { get; set; }

public FriendsViewModel()
{
    FriendsQuery = new FriendsQueryCommand();
}

 
  1. Add the following line in the ViewModelLocator RaiseAuthCanExecuteChanged() method to ensure that the Query presence button is only enabled when a user is logged in:

Friends.FriendsQuery.RaiseCanExecuteChanged();
 
  1. Finally, add the PresenceView to our MainWindow TabControl:

<TabItem x:Name="Friends" Header="Friends">
    <views:FriendsView />
</TabItem>



Now we can run the app, authenticate, and consent to the updated permissions in the browser, after which we’ll be able to use the Query friends button to get the player’s friends and status. As expected, however, we won’t get any friends back from the API, as none of them have consented to our application. Let’s fix that.

Getting consent from a friend

To validate our code, we have to get a friend to consent to our application. Note that only friends who have accepted your friend request will be returned by the QueryFriends API we implemented. If this were an actual game we were developing, we could complete a Brand Review submission for our application and share our game with a friend to get consent. 

However, as we’re only developing this sample, we’ll have to add a friend to our Developer Portal Organization, as otherwise they will not be able to access our application. This is done to protect Epic Games ecosystem users, you (the developer), other game developers, and Epic Games from brand spoofing, phishing, and more.
 
  1. Log in to the Developer Portal at https://dev.epicgames.com/portal/.
  2. Click on the Organization tab on the left and use the blue Invite button to invite a friend, selecting an appropriate Role.
 
Developer Portal Invite To Organization
Adding someone to the Organization

Now our friend has to run our sample application, connecting to our product, client, sandbox, and deployment. Package and share the following files from the /bin/ directory of the project:
 
  • EOSCSharpSample.dll
  • EOSCSharpSample.exe
  • EOSCSharpSample.runtimeconfig.json
  • EOSSDK-Win32-Shipping.dll

The first time they run the application, they will get prompted for consent, and should see our account show up when Querying friends. Subsequently, when we do the same after the friend has provided consent, their account will show up when Querying friends.
 
App Queried Friends
Queried friends for the logged in user

Note that we only see friends’ Epic Account ID and not their display name, but you can use the same QueryUserInfo call we used in AuthService.Login() to retrieve more information for each friend. The status column will always display “Offline” at this point, as that’s the 0 value of the Epic.OnlineServices.Presence.Status enum, but we’ll tackle that further in this article.

Subscribing to updates

Once you’ve queried for friends, you should subscribe to notifications when the friend status changes (rather than query again). The Friends Interface will return one of these four statuses for a friend, per the Epic.OnlineServices.Friends.FriendsStatus enum: NotFriends, InviteSent, InviteReceived, Friends. While there are no APIs to manage friends directly, as mentioned earlier, we still need to subscribe to updates so we know when a friend request is accepted or someone is unfriended. The Friends Interface provides APIs to subscribe and unsubscribe to these updates, so let’s implement them.
 
  1. Open FriendsService.cs and add the following methods:

public static void SubscribeUpdates()
{
    var addNotifyFriendsUpdateOptions = new AddNotifyFriendsUpdateOptions();

    ViewModelLocator.Main.StatusBarText = $"Adding notification for friends updates...";

    ViewModelLocator.Friends.NotificationId = App.Settings.PlatformInterface
.GetFriendsInterface()
​​​​​​​.AddNotifyFriendsUpdate(addNotifyFriendsUpdateOptions, null, (OnFriendsUpdateInfo onFriendsUpdateInfo) =>
    {
        Debug.WriteLine($"OnFriendsUpdate: {onFriendsUpdateInfo.TargetUserId}");

        ViewModelLocator.Friends.Friends.SingleOrDefault(f => f.EpicAccountId == onFriendsUpdateInfo.TargetUserId).FriendsStatus = onFriendsUpdateInfo.CurrentStatus;
    });

    ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
    ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();

    ViewModelLocator.Main.StatusBarText = string.Empty;
}

public static void UnsubscribeUpdates()
{
    ViewModelLocator.Main.StatusBarText = $"Removing notification for friends updates...";

    App.Settings.PlatformInterface.GetFriendsInterface()
​​​​​​​.RemoveNotifyFriendsUpdate(ViewModelLocator.Friends.NotificationId);
    ViewModelLocator.Friends.NotificationId = 0;

    ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
    ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();

    ViewModelLocator.Main.StatusBarText = string.Empty;
}

 
  • We call AddNotifyFriendsUpdate, passing in empty options, to get a Notification ID back, which both confirms that the notification has been set up and we’ll need to use to unsubscribe from updates (as seen in UnsubscribeUpdates()).
  • We then define a callback method with the functionality we want when a notification is sent. In this case we simply add another record to the Friends list with the new status.
 
  1. We’ll also add a trigger to these commands’ CanExecuteChanged behaviors, so they’re only available after we do an initial query for friends:

Add:

ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();


Below the following lines in FriendsService.QueryFriends():

[...]
var friendStatus = App.Settings.PlatformInterface.GetFriendsInterface()
.GetStatus(getStatusOptions);

        ViewModelLocator.Friends.Friends.Add(new Friend()
        {
            EpicAccountId = friend,
            FriendsStatus = friendStatus
        });
    }
}


ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();

 
  1. Add a FriendsSubscribeUpdatesCommand.cs class to the Commands folder:

public class FriendsSubscribeUpdatesCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId) && (ViewModelLocator.Friends.Friends.Count != 0) && (ViewModelLocator.Friends.NotificationId == 0);
    }

    public override void Execute(object parameter)
    {
        FriendsService.SubscribeUpdates();
    }
}

 
  1. Add a FriendsUnsubscribeUpdatesCommand.cs class to the Commands folder:
public class FriendsUnsubscribeUpdatesCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId) && (ViewModelLocator.Friends.Friends.Count != 0) && (ViewModelLocator.Friends.NotificationId != 0);
    }

    public override void Execute(object parameter)
    {
        FriendsService.UnsubscribeUpdates();
    }
}

 
  1. Declare and initialize our new command in FriendsViewModel.cs:

public FriendsQueryCommand FriendsQuery { get; set; }
public FriendsSubscribeUpdatesCommand FriendsSubscribeUpdates { get; set; }
public FriendsUnsubscribeUpdatesCommand FriendsUnsubscribeUpdates { get; set; }

public FriendsViewModel()
{
    FriendsQuery = new FriendsQueryCommand();
    FriendsSubscribeUpdates = new FriendsSubscribeUpdatesCommand();
    FriendsUnsubscribeUpdates = new FriendsUnsubscribeUpdatesCommand();
}


Now when we run the app, we can query for friends and subscribe/unsubscribe updates. Once subscribed for updates, you can remove a listed friend through the Epic Games Launcher and you’ll see a new entry appear for that friend in the ListView with a status of NotFriends.

Querying for friends presence

The last thing for us to do is implement the functionality to query for friends’ presence status, building on what we implemented in the last article. Now we can make use of QueryPresence to retrieve presence for each friend. 
 
  1. Open PresenceQueryCommand from the Commands folder and replace both methods with the following code:

public override bool CanExecute(object parameter)
{
    if (parameter == null)
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId);
    else
        return ((Friend)parameter).EpicAccountId != null;
}

public override void Execute(object parameter)
{
    if (parameter == null)
        PresenceService.Copy(EpicAccountId
.FromString(ViewModelLocator.Main.AccountId), EpicAccountId
​​​​​​​.FromString(ViewModelLocator.Main.AccountId));
    else
        PresenceService.Query(((Friend)parameter).EpicAccountId);
}

 
  • We modify the CanExecute code to also check for a parameter (the selected Friend we’ll pass from our FriendsView ListView) and add the “else” statement to Execute to call PresenceService.Query() when a Friend is passed.
 
  1. Open FriendsViewModel and instantiate PresenceQueryCommand:

public FriendsQueryCommand FriendsQuery { get; set; }
public FriendsSubscribeUpdatesCommand FriendsSubscribeUpdates { get; set; }
public FriendsUnsubscribeUpdatesCommand FriendsUnsubscribeUpdates { get; set; }
public PresenceQueryCommand PresenceQuery { get; set; } 

public FriendsViewModel()
{
    FriendsQuery = new FriendsQueryCommand();
    FriendsSubscribeUpdates = new FriendsSubscribeUpdatesCommand();
    FriendsUnsubscribeUpdates = new FriendsUnsubscribeUpdatesCommand();
    PresenceQuery = new PresenceQueryCommand();
}

 
  1. Finally, add the following to the FriendsListView_SelectionChanged event handler in FriendsView.xaml.cs to ensure the query status button is only enabled when a friend is selected:

ViewModel.PresenceQuery.RaiseCanExecuteChanged();

Now when we run the application, hit Query friends, select a friend that’s online, and hit Query status, we’ll see their status update in the UI.
 
App Queried Friends Status
Queried friends status

As mentioned in the previous article, you should implement AddNotifyOnPresenceChanged in a production game to get notified when there’s a change in presence status for friends.

Get the code

Get the code for this article below (follow step five and ten of this article to add the SDK to the solution, and add your SDK credentials to ApplicationSettings.cs).
 
In the next article, we’ll start using Game Services, which are the platform-agnostic back-end services in Epic Online Services. We’ll start with explaining the Connect Interface, which is used to authenticate users to these services using one of many identity providers.

You can find the full list of articles in this series in the series reference and if you have questions or feedback about this article, use the button below to head to the Community forum and let us know.

    We succeed when you succeed

    Epic believes in an open, integrated games community. By offering our online services to everyone for free, we aim to empower more developers to serve their own player communities.