Thursday, 28 February 2013

Facebook Android SDK 3.0 - opening a session

I've been working with the new Facebook Android SDK (version 3.0) the past two weeks and every time I thought "yes, now I've got it!", I've found something wrong with the integration of it in my apps! But this time I think I've really got it (!!) and I'm gonna do a sequence of three Blog posts to explain (1) how to connect to a Facebook user account and open a Facebook session, (2) how to obtain additional, required Facebook permissions and then perform a Facebook API request like posting to the user's feed, and (3) how to log out and close the Facebook session. I wish the official documentation better explained these core concepts but it doesn't! And this is evident in browsing developer forums like Stack Overflow where almost everybody has a different take on how to perform the above operations!! So here's my two cents...

Firstly, I like to a have single Activity (let's call it BaseFacebookActivity) that has all my Facebook methods in it and which Activities that do Facebook operations are to extend. Don't forget the onActivityResult(...) method!, as follows:

public abstract class BaseFacebookActivity
  extends Activity
{
  @Override
  protected void onActivityResult(
      int requestCode,
      int resultCode,
      Intent data)
  {
    super.onActivityResult(requestCode, resultCode, data);

    if (Session.getActiveSession() != null)
      Session.getActiveSession().onActivityResult(
          this,
          requestCode,
          resultCode,
          data);
  }

  // other methods to follow
}

Secondly, let's define an interface which will help us report back to the user when an attempt to open a Facebook session has been successful or not, as follows:

public interface FacebookConnectHandler
{
  /** Method to call when the user's Facebook account
    * was connected to
    * and a Facebook session was opened successfully.*/
  public void onSuccess();
  /** Method to call when the user's Facebook account
    * was not connected to
    * or a Facebook session was not opened successfully.*/
  public void onFailure();
}

Thirdly and finally, a method to put in your BaseFacebookActivity which will connect to the user's Facebook account and open a Facebook session, as follows:

private void connectFacebookAccount(
    final FacebookConnectHandler handler)
{
  // safety check
  if (!isActiveNetworkConnected())
  {
    handler.onFailure();
    return;
  }

  // check whether the user already has an active session
  // and try opening it if we do

  // (note: making a Session.openActiveSessionFromCache(...) call
  // instead of simply checking whether the active session is opened
  // because of a bug in the Facebook sdk
  // where successive calls to update a token
  // (requesting additional permissions etc)
  // don't result in a session callback)

  if (Session.getActiveSession() != null
      && Session.openActiveSessionFromCache(this) != null)
  {
    handler.onSuccess();
    return;
  }

  // initialise the session status callback

  Session.StatusCallback callback = new Session.StatusCallback()
  {
    @Override
    public void call(
        Session session,
        SessionState state,
        Exception exception)
    {
      // safety check
      if (isFinishing())
        return;

      // check session state

      if (state.equals(SessionState.CLOSED)
          || state.equals(SessionState.CLOSED_LOGIN_FAILED))
      {
        clearFacebookInfoFromSharedPreferences();

        // specific action for when the session is closed
        // because an open-session request failed
        if (state.equals(SessionState.CLOSED_LOGIN_FAILED))
        {
          cancelProgressDialog();
          handler.onFailure();
        }
      }
      else if (state.equals(SessionState.OPENED))
      {
        cancelProgressDialog();

        saveFacebookInfoInSharedPreferences(
            session.getAccessToken(),
            session.getExpirationDate());

        showToast("Succeeded connecting to Facebook");

        handler.onSuccess();
      }
    }
  };

  // make the call to open the session

  showProgressDialog("Connecting to Facebook...");

  if (Session.getActiveSession() == null
      && getSharedPreferences().contains("facebookAccessToken")
      && getSharedPreferences().contains("facebookAccessTokenExpires"))
  {
    // open a session from the access token info
    // saved in the app's shared preferences

    String accessTokenString = getSharedPreferences().getString(
        "facebookAccessToken",
        "");

    Date accessTokenExpires = new Date(getSharedPreferences().getLong(
        "facebookAccessTokenExpires",
        0));

    AccessToken accessToken = AccessToken.createFromExistingAccessToken(
        accessTokenString,
        accessTokenExpires,
        null,
        null,
        null);

    Session.openActiveSessionWithAccessToken(this, accessToken, callback);
  }
  else
    // open a new session, logging in if necessary
    Session.openActiveSession(this, true, callback);
}

Done! In the next Blog post I'll show how to use this connectFacebookAccount(...) method prior to making a Facebook API request like posting to the user's feed.

16 comments:

Zakaria said...

Thanks my friend for this interesting post, I've been looking desperately for such a tutorial!
I have a little question though:
Where are these functions defined?

isActiveNetworkConnected()
clearFacebookInfoFromSharedPreferences()
cancelProgressDialog()
saveFacebookInfoInSharedPreferences(session.getAccessToken(), session.getExpirationDate())
showProgressDialog("Connecting to Facebook...")
getSharedPreferences()

Thanks again my friend!

adil said...

Hi Zakaria, these are nice-to-have methods which are not essential to the core of the post (working with the Facebook SDK) and which I abstracted away from the tutorial to keep the tutorial focused and simple(r).

Andy said...

Thank you such Needed tutorial of Facebook 3.0...
I have a Little question.

if (Session.getActiveSession() == null
&& getSharedPreferences().contains("facebookAccessToken")
&& getSharedPreferences().contains("facebookAccessTokenExpires"))
{
// open a session from the access token info
// saved in the app's shared preferences

String accessTokenString = getSharedPreferences().getString(
"facebookAccessToken",
"");

Date accessTokenExpires = new Date(getSharedPreferences().getLong(
"facebookAccessTokenExpires",
0));
I get the error to method getSharedPreferences()

Meet said...

Hi Adil,
Thanks... for Tutorial.
but I have not any idea of this Two
"facebookAccessToken"
"facebookAccessTokenExpires"

token value so, Please tell me token value. Thank you
facebookAccessToken
facebookAccessTokenExpires

adil said...

Hi all, please note that the tutorial contains methods which you can omit or which are pretty straightforward to write but are outside the scope of the tutorial and would distract from the main purpose of the tutorial which is to demonstrate usage of the new Facebook SDK.

Kostas said...

Hello adil,
do you use com.facebook.widget.LoginButton from sdk or a custom button?

adil said...

Kostas, no, I didn't use the Facebook LoginButton and used a custom button instead.

Anonymous said...

Hey Adil, very nice example.
I have one question - i saw that you're not check for the token expiry time. what will happen if i get the token info from the shared preferences but it is already expired ?

Anonymous said...

Hi Adil,Thanks for yout post !
I already made Login an open a Session, but I would like to ask for Publish permissions later in my app, or maybe user don't need it untill next execution. I have my AccessToken saved, can I restore a session with this token and ask for new permissions? Dou you know if there is an easy call to do it? Thank!

adil said...

Hi Anonymous, I'm not sure if this is what you're after but you might find this useful:

http://adilatwork.blogspot.co.uk/2013/02/facebook-android-sdk-30-requesting.html

vishwas said...

Hi Adil,

thanks for tutorial. Also, I am little curious to know why not to use fb loggin button. What are the advantages of this approach over standard login button.

thanks,

adil said...

Hi vishwas, the main reasons for my not using the fb login button and also the reason for my putting together these "Facebook Android SDK 3.0" Blog posts was because the documentation available at the time was very very poor.

Aiman Baharum said...

Hey Adil, thanks for the work. This is a great resource to implement a Facebook login auth.

However, is there any possibilities for you to show all the source perhaps on github or whatnot? Just wanna have an extra understanding on the implementation. Great explanation!

Vivek Ramesh said...

Greetings Adil, Nice setup but I have a feeling I found an even better solution, most people wanna use Facebook SDK with Fragments since they can use used with so many things like ViewPagers and ScrollTabs, There are certain doubts I had while trying to interpret your code and develop my Fragments based generic solution based on that...
I created an AbstractFacebookFragment with a UILifecycleHelper without any Views and my code turns to be something like pasted below. Its pretty similar to yours with the exception of the one thing, this Fragment does not have a View, I am expecting all subclass fragments to extend this one and define their own views but what do you think should be done with the LoginButton, I am also using a UILifecycleHelper to reduce some of the things you did in the onActivityResult

package vivz.slidenerd.thefpamapp;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;

import com.facebook.Session;
import com.facebook.SessionState;
import com.facebook.UiLifecycleHelper;

import java.util.Date;

import vivz.slidenerd.utils.NetworkUtils;

/**
* Created by Windows on 28-11-2014.
*/
public abstract class AbstractFacebookFragment extends Fragment {
private UiLifecycleHelper lifecycleHelper;

protected abstract void clearProgressDialog();

protected abstract void clearFacebookInfoFromSharedPreferences();

protected abstract void saveFacebookInfoToSharedPreferences(String accessToken, Date expirationDate);

protected void connectWithFacebook(final FacebookConnectionHandler connectionHandler) {
if (!NetworkUtils.isNetworkAvailable(getActivity())) {
connectionHandler.onFailure();
return;
}
if (Session.getActiveSession() != null
&& Session.openActiveSessionFromCache(getActivity()) != null) {
connectionHandler.onSuccess();
return;
}
Session.StatusCallback callback = new Session.StatusCallback() {
@Override
public void call(Session session, SessionState state, Exception exception) {
if (state.equals(SessionState.CLOSED) || state.equals(SessionState.CLOSED_LOGIN_FAILED)) {
clearFacebookInfoFromSharedPreferences();
if (state.equals(SessionState.CLOSED_LOGIN_FAILED)) {
clearProgressDialog();
connectionHandler.onFailure();
}
} else if (state.equals(SessionState.OPENED)) {
clearProgressDialog();
saveFacebookInfoToSharedPreferences(session.getAccessToken(), session.getExpirationDate());
connectionHandler.onSuccess();
}
onSessionStateChange(session, state, exception);
}
};
}


private void onSessionStateChange(Session session, SessionState state, Exception exception) {
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifecycleHelper = new UiLifecycleHelper(getActivity(), callback);

}

@Override
public void onResume() {
super.onResume();
lifecycleHelper.onResume();
}

@Override
public void onPause() {
super.onPause();
lifecycleHelper.onPause();
}

@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
lifecycleHelper.onSaveInstanceState(outState);
}

@Override
public void onStop() {
super.onStop();
lifecycleHelper.onStop();
}

@Override
public void onDestroy() {
super.onDestroy();
lifecycleHelper.onDestroy();
}


@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
lifecycleHelper.onActivityResult(requestCode, resultCode, data);
}
}

Vivek Ramesh said...
This comment has been removed by the author.
Lucas said...

Thanks!!!!!! You're my hero!