dotnetco.de

How to get facebook friendlist in Xamarin Forms iOS using native facebook login

In previous post I’ve shown how to login to Facebook using Facebooks native SDK using client side authentication. So now we know the user, what else can we get? Here is a guide how to get the friendlist of the logged-in user. Why the friendlist? Because next to public profile and email, friendlist is one of the 3 attributes which are supplied by facebook without reviewing your app. Of course you could easily use current article to get any other attribute you want.

Inform Facebook about Friendlist request

So Facebook does not need to approve a request for the friendlist, but the user has to. So we have to inform Facebook that we want to get the friendlist of the currently logged-in user. On the facebook developer dashboard, click on your app and select ‘App Review’. There you’ll find all permissions granted by Facebook for your app. If you need to get any information which is not granted by Facebook yet, it would not make sense to ask your user because you always need Facebooks approval for your app. See Facebooks Permission overview for all available permissions.

But as said, friendlist is always granted by Facebook automatically. As seen in the screenshot, permission name is “user_friends”. So we add the new permission to the string array in FacebookLoginButtonRendereriOS.cs in our iOS project:

using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Facebook.LoginKit;
using Facebook.CoreKit;
using FacebookLoginNative;
using FacebookLoginNative.iOS;

[assembly: ExportRenderer(typeof(FacebookLoginButton), typeof(FacebookLoginButtonRendererIos))]

namespace FacebookLoginNative.iOS
{
  public class FacebookLoginButtonRendererIos : ButtonRenderer
  {

    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
    {
      base.OnElementChanged(e);

      //DEBUG
      //new LoginManager().LogOut();

      if (Control != null)
      {
        UIButton button = Control;

        button.TouchUpInside += delegate
        {
          HandleFacebookLoginClicked();
        };
      }

      if (AccessToken.CurrentAccessToken != null)
      {
        App.PostSuccessFacebookAction(AccessToken.CurrentAccessToken.ToString());
      }
    }

    void HandleFacebookLoginClicked()
    {
      if (AccessToken.CurrentAccessToken != null)
      {
        App.PostSuccessFacebookAction(AccessToken.CurrentAccessToken.ToString());
      }
      else
      {
        var window = UIApplication.SharedApplication.KeyWindow;
        var vc = window.RootViewController;
        while (vc.PresentedViewController != null)
        {
          vc = vc.PresentedViewController;
        }

        var manager = new LoginManager();
        manager.LogInWithReadPermissions(new string[] { "public_profile", "user_friends" },
                         vc,
                         (result, error) =>
                         {
                           if (error == null && !result.IsCancelled)
                           {
                             App.PostSuccessFacebookAction(result.Token.ToString());
                           }
                         });
      }

    }
  }
}

 

Facebook will inform the user about the friendlist request during authentication.

See the “Edit this”? Users are able to authenticate via Facebook but they deny access to the friendlist. So even if we request it, we have to keep in mind that the friendlist is not always supplied. “Public Profile” could not be rejected if you want to authenticate, but the other permissions are optional.

Get Data from Facebook

As usual, there are several options to get the data from Facebook now. One option would be to use the Facebook SDK as we already use it for the login. This is just a very basic idea how this could be done.

public static string GetFriendList()
{
      Foundation.NSDictionary friendList;
            string json = string.Empty;
        
            if (AccessToken.CurrentAccessToken != null)
            {
          var request = new GraphRequest("me/friends", null, AccessToken.CurrentAccessToken.TokenString, null, "GET");
          request.Start((connection, result, error) => {
    				if (error != null)
    				{
    					return;
    				}

    				friendList = result as Foundation.NSDictionary;
    				json = result.ToString();
                });
            }
            return json;
        }

 

The other option is using web requests. As already said, I’ve already implemented that in my current solution so I checked whether I could use the existing code.

What do we have

With Login, Facebook supplies some values automatically. This includes e.g. the TokenString and the UserID.

A TokenString is also used for the web requests so let’s see whether that’s the same. First, FacebookLoginButtonRendererIos.cs should returned the TokenString on each path.

using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Facebook.LoginKit;
using Facebook.CoreKit;
using FacebookLoginNative;
using FacebookLoginNative.iOS;

[assembly: ExportRenderer(typeof(FacebookLoginButton), typeof(FacebookLoginButtonRendererIos))]

namespace FacebookLoginNative.iOS
{
  public class FacebookLoginButtonRendererIos : ButtonRenderer
  {

    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
    {
      base.OnElementChanged(e);

      if (Control != null)
      {
        UIButton button = Control;

        button.TouchUpInside += delegate
        {
          HandleFacebookLoginClicked();
        };
      }

      if (AccessToken.CurrentAccessToken != null)
      {
                App.PostSuccessFacebookAction(AccessToken.CurrentAccessToken.TokenString);
      }
    }

    void HandleFacebookLoginClicked()
    {
      if (AccessToken.CurrentAccessToken != null)
      {
        App.PostSuccessFacebookAction(AccessToken.CurrentAccessToken.TokenString);
      }
      else
      {
        var window = UIApplication.SharedApplication.KeyWindow;
        var vc = window.RootViewController;
        while (vc.PresentedViewController != null)
        {
          vc = vc.PresentedViewController;
        }

        var manager = new LoginManager();
        manager.LogInWithReadPermissions(new string[] { "public_profile", "user_friends" },
                         vc,
                         (result, error) =>
                         {
                           if (error == null && !result.IsCancelled)
                           {
                             App.PostSuccessFacebookAction(result.Token.TokenString);
                           }
                         });

        
      }

    }
  }
}

 

For testing I’ve created just a very basic class FacebookConnector.cs in the PCL.

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace FacebookLoginNative
{
    public class FacebookConnector
    {
        public static string tokenString { get; set; }

    const string host = "https://graph.facebook.com";
    const string version = "v2.10";

    private static async Task<string> GetFacebookJSON(string url)
    {
      HttpClient client = new HttpClient();

      var userJson = await client.GetStringAsync(url);
      return userJson;
    }

    public static async Task<string> GetFacebookFriendsAsync()
    {
        var requestUrl = string.Format("{0}/{1}/me/friends?access_token={2}", host, version, tokenString);
        var userJson = await GetFacebookJSON(requestUrl);
                return userJson;
    }
    }
}

 

As said, this is just a very basic example. In a real app you should of course take care about secure storing of the tokenString (if storing is necessary), error handling etc.

On the FacebookLoginNativePage.xaml we now need a new button

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:local="clr-namespace:FacebookLoginNative" 
    x:Class="FacebookLoginNative.FacebookLoginNativePage">
    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
    <local:FacebookLoginButton x:Name="fbLoginButton" 
      Text="Login using Facebook" 
      VerticalOptions="Center" 
      HorizontalOptions="Center" />
    <Label x:Name="fbLoginResult" 
      VerticalOptions="Center" 
      HorizontalOptions="Center" />
        <Button Text="Load Friendlist" Clicked="LoadFriendlist_Clicked"></Button>
    </StackLayout>        
</ContentPage>

 

and of course the click handler has to be added to the FacebookLoginNativePage.xaml.cs. As we now return the TokenString I’ve also changed the display so that only the first characters of the TokenString are shown on the screen.

using Xamarin.Forms;

namespace FacebookLoginNative
{
    public partial class FacebookLoginNativePage : ContentPage
    {
        public FacebookLoginNativePage()
        {
            InitializeComponent();
            App.PostSuccessFacebookAction = token =>
            {
                fbLoginResult.Text = (token.Length > 20 ? token.Substring(0, 10) + "..." : token);
                FacebookConnector.tokenString = token;
            };
        }

        async void LoadFriendlist_Clicked(object sender, System.EventArgs e)
        {
            string result = await FacebookConnector.GetFacebookFriendsAsync();
            fbLoginResult.Text = result;
        }
    }
}

 

 

So when I start my application now I get the TokenString displayed on the screen.

And when I click on the new button….

my friendlist is returned as expected!! As defined in Facebooks Developer Api for User Friends, it contains a list of User Nodes, paging information and a total count. If you’re surprised that in current case only 1 user is returned instead of a list with all 146 friends: With release of Api 2.0, Facebook now only returns all friends who also use the current app. So if you are the first to use XamTestAuth 😉 your list of friends will be empty.

Facebook Graph API Explorer

If you later on want to add other requests you might have a look at the Facebook Graph API Explorer. It lets you easily test certain calls and see the return results. This might help when setting up the structure.

Get Userdata into structure

Facebook returns all the data in JSON-Format. To get the returned userdata into a certain structure I’ve now installed Newtonsoft Json 8.0.3 to my PCL and iOS project. I’ve used the older version 8.0.3 even though the current one is 10.0.3 because 8.0.3 was the last one without any dependencies so that’s fine for current example. Feel free to use any version you want / you already use in your project.

So of course we could now manually create the classes necessary for the friendlist. Or we use a service like json2csharp.com . You just need to copy the result from Facebooks Graph Api Explorer and paste it into the textbox on json2csharp. It will generate the classes based on the supplied JSON. You probably want to change the class names but for current example I’ve just kept them as named by Facebook.

So I’ve created a new FriendList.cs in my PCL object with the classes returned by json2csharp

using System;
using System.Collections.Generic;

namespace FacebookLoginNative
{
  public class Datum
  {
    public string name { get; set; }
    public string id { get; set; }
  }

  public class Cursors
  {
    public string before { get; set; }
    public string after { get; set; }
  }

  public class Paging
  {
    public Cursors cursors { get; set; }
  }

  public class Summary
  {
    public int total_count { get; set; }
  }

  public class RootObject
  {
    public List<Datum> data { get; set; }
    public Paging paging { get; set; }
    public Summary summary { get; set; }
  }
}

 

In FacebookConnector.cs in PCL we deserialize the returned facebook data.

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace FacebookLoginNative
{
    public class FacebookConnector
    {
        public static string tokenString { get; set; }

    const string host = "https://graph.facebook.com";
    const string version = "v2.10";

    private static async Task<string> GetFacebookJSON(string url)
    {
      HttpClient client = new HttpClient();

      var userJson = await client.GetStringAsync(url);
      return userJson;
    }

    public static async Task<RootObject> GetFacebookFriendsAsync()
    {
      var requestUrl = string.Format("{0}/{1}/me/friends?access_token={2}", host, version, tokenString);
      var userJson = await GetFacebookJSON(requestUrl);
            RootObject myFriends = JsonConvert.DeserializeObject<RootObject>(userJson);
            return myFriends;
    }
    }
}

 

And in FacebookLoginNativePage.xaml.cs we update the displayed text:

using Xamarin.Forms;

namespace FacebookLoginNative
{
    public partial class FacebookLoginNativePage : ContentPage
    {
        public FacebookLoginNativePage()
        {
            InitializeComponent();
            App.PostSuccessFacebookAction = token =>
            {
                fbLoginResult.Text = (token.Length > 20 ? token.Substring(0, 10) + "..." : token);
                FacebookConnector.tokenString = token;
            };
        }

        async void LoadFriendlist_Clicked(object sender, System.EventArgs e)
        {
            RootObject friendList = await FacebookConnector.GetFacebookFriendsAsync();
            fbLoginResult.Text = string.Format("You have a total of {0} friends in Facebook. {1} of them also use current app. ",
                                               friendList.summary.total_count.ToString(), friendList.data.Count.ToString());
            if (friendList.data.Count > 0)
                fbLoginResult.Text += string.Format("{0} also uses current app.", friendList.data[0].name);
        }
    }
}

 

And now when we start the app and click on Load Friendlist button….

In case you need it, here is a zipped version of the example project: FacebookLoginNativeAndFriendlist

 

Leave a Comment