Saturday, 23 November 2013

Java/Android - Method for getting a url's domain name

Below is a method which extracts a url's domain name and which uses simple String matching. What it actually does is extract the bit between the first "://" (or index 0 if there's no "://" contained) and the first subsequent "/" (or index String.length() if there's no subsequent "/"). The remaining, preceding "www(_)*." bit is chopped off. I'm sure there'll be cases where this won't be good enough but it should be good enough in most cases!

I read on forums that the java.net.URI class could do this (and was preferred to the java.net.URL class) but I encountered problems with the URI class. Notably, URI.getHost() gives a null value if the url does not include the scheme, i.e. the "http(s)" bit.


/**
* Extracts the domain name from {@code url}
* by means of String manipulation
* rather than using the {@link URI} or {@link URL} class.
*
* @param url is non-null.
* @return the domain name within {@code url}.
*/
public String getUrlDomainName(String url) {
  String domainName = new String(url);

  int index = domainName.indexOf("://");

  if (index != -1) {
    // keep everything after the "://"
    domainName = domainName.substring(index + 3);
  }

  index = domainName.indexOf('/');

  if (index != -1) {
    // keep everything before the '/'
    domainName = domainName.substring(0, index);
  }

  // check for and remove a preceding 'www'
  // followed by any sequence of characters (non-greedy)
  // followed by a '.'
  // from the beginning of the string
  domainName = domainName.replaceFirst("^www.*?\\.", "");

  return domainName;
}

Tuesday, 5 March 2013

Facebook Android SDK 3.0 - getting a user's profile picture

Here's another Facebook operation which you'd think should not only be easy to do but also be easy to find in the documentation. It certainly was easy to do, but once again wasn't so easy to find in the documentation. Oh and, strictly speaking, you don't need the Facebook SDK for this. You just need to make a HTTP GET call to the Facebook user's picture connection. (You'll need the Facebook SDK to get the user's Facebook id beforehand.)

final String userFacebookId = ...

new AsyncTask<Void, Void, Bitmap>()
{
  @Override
  protected Bitmap doInBackground(Void... params)
  {
    // safety check
    if (userFacebookId == null)
      return null;

    String url = String.format(
        "https://graph.facebook.com/%s/picture",
        userFacebookId);

    // you'll need to wrap the two method calls
    // which follow in try-catch-finally blocks
    // and remember to close your input stream

    InputStream inputStream = new URL(url).openStream();
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

    return bitmap;
  }

  @Override
  protected void onPostExecute(Bitmap bitmap)
  {
    // safety check
    if (bitmap != null
        && !isChangingConfigurations()
        && !isFinishing())
      // do what you need to do with the bitmap :)
  }
}.execute();

Saturday, 2 March 2013

When methods and functions do too much

It's like asking a robot to get you some milk and it comes back to you with flavoured milk. You might want flavoured milk, but you might not! Your methods should only do as much as they are contracted to do. No more.

Here's another example: you create an Android AlertDialog with the AlertDialog.Builder class. Now every time you click one of the buttons of your AlertDialog, the AlertDialog is automically dismissed, as well as notifying you that a button was clicked. But you don't want the AlertDialog to be dismissed, you only want to be notified that a button was clicked!

Sometimes doing more is less helpful.

Friday, 1 March 2013

Android - load and show html from a file into a TextView

There's a known bug in the Android WebView such that a WebView set with a transparent background colour does not appear with a transparent background on all devices. None of the workarounds are definitive and work for all devices. An alternative to loading html in a WebView is to load it in a TextView. There are limitations as to what HTML tags can be used (see here) but if you're only doing basic HTML tagging, this will work:

try
{
  InputStream inputStream = getResources().getAssets().open("myFile.html");

  String html = IOUtils.toString(inputStream);

  myTextView.setText(Html.fromHtml(html));
}
catch (IOException exception)
{
  myTextView.setText("Failed loading html.");
}

Thursday, 28 February 2013

Facebook Android SDK 3.0 - how to log out and close a session

In the previous two Blog posts (here and here), I showed how to connect a user to their Facebook account (i.e. open a Facebook session) and how to make a Facebook API request (requesting additional Facebook permissions en route if necessary). In this final Blog post, I'll show to close the Facebook session, as follows:

public void disconnectFacebookAccount()
{
  showProgressDialog("Disconnecting Facebook account...");

  new AsyncTask<Void, Void, Boolean>()
  {
    @Override
    protected Boolean doInBackground(Void... params)
    {
      if (Session.getActiveSession() != null)
        Session.getActiveSession().closeAndClearTokenInformation();

      clearFacebookInfoFromSharedPreferences();

      // perform any other operations you need to do perform here
      // such as clearing local database tables and so forth

      return true;
    }

    @Override
    protected void onPostExecute(Boolean result)
    {
      // safety check
      if (isFinishing())
        return;

      if (result == null
          || result == false)
        onFailedFacebookDisconnect();
      else
        onSucceededFacebookDisconnect();
    }
  }.execute();
}

That's it! That's the end of my Facebook Android 3.0 series. You should have enough know-how now for all kinds of Facebook API requests! :)

Facebook Android SDK 3.0 - requesting additional permissions and making an API request

At the end of the previous Blog post, I defined a connectFacebookAccount(...) method which connects to a user's Facebook account and opens a Facebook session for subsequent Facebook API requests. In this Blog post, I'll show how to use this method and the opened Facebook session to post to the user's feed. You'll notice in reading through the postFacebookMessage(...) method below that it is sometimes necessary to request additional Facebook permissions before making a Facebook API request. (It's no longer possible to request all required Facebook permissions at the point of first connecting to Facebook.) Without further ado, here's the postFacebookMessage(...) method:

/** Posts the provided message to Facebook,
 * connecting to Facebook
 * and requesting required permissions en route if necessary.*/
public void postFacebookMessage(final String message)
{
  connectFacebookAccount(new FacebookConnectHandler()
  {
    @Override
    public void onSuccess()
    {
      // safety check
      if (isFinishing())
        return;

      showProgressDialog("Posting message to Facebook...");

      // check for publish permissions

      final List permissions_required = Arrays.asList(
          new String[] { Constants.Facebook_Permission_PublishStream });

      if (Session.getActiveSession().getPermissions() == null
          || !Session.getActiveSession().getPermissions().containsAll(
              permissions_required))
      {
        // need to make a Session.openActiveSessionFromCache(...) call
        // because of a bug in the Facebook sdk
        // where a second call to get permissions
        // won't result in a session callback when the token is updated

        if (Session.openActiveSessionFromCache(BaseFacebookActivity.this) == null)
        {
          onFailedPostingFacebookMessage();
          return;
        }

        Session.getActiveSession().addCallback(new Session.StatusCallback()
        {
          @Override
          public void call(
              Session session,
              SessionState state,
              Exception exception)
          {
            if (exception != null
                || state.equals(SessionState.CLOSED)
                || state.equals(SessionState.CLOSED_LOGIN_FAILED))
            {
              // didn't get required permissions

              session.removeCallback(this);

              // safety check
              if (!isFinishing())
                onFailedPostingFacebookMessage();
            }
            else if (state.equals(SessionState.OPENED_TOKEN_UPDATED)
                && session.getPermissions().containsAll(permissions_required))
            {
              // got required permissions

              session.removeCallback(this);

              // safety check
              if (!isFinishing())
                postFacebookMessage(message);
            }
          }
        });

        Session.getActiveSession().requestNewPublishPermissions(
            new Session.NewPermissionsRequest(
                BaseFacebookActivity.this,
                permissions_required));

        return;
      }

      // got sufficient permissions, so publish message

      Bundle bundle_params = new Bundle();

      bundle_params.putString("caption", "Enter your caption here"));
      bundle_params.putString("description", "Enter your description here");
      bundle_params.putString("link", "http://www.your-app-url.com");
      bundle_params.putString("message", message);
      bundle_params.putString("name", "My App for Android");
      bundle_params.putString("picture", "http://www.your-app-icon-url.com");

      new Request(
          Session.getActiveSession(),
          "me/feed",
          bundle_params,
          HttpMethod.POST,
          new Request.Callback()
          {
            @Override
            public void onCompleted(Response response)
            {
              // safety check
              if (isFinishing())
                return;

              if (response.getError() != null
                  || response.getGraphObject() == null)
              {
                onFailedPostingFacebookMessage();
                return;
              }

              Object id = response.getGraphObject().getProperty("id");

              if (id == null
                  || !(id instanceof String)
                  || TextUtils.isEmpty((String)id))
                onFailedPostingFacebookMessage();
              else
                onSucceedPostingFacebookMessage((String)id);
            }
          }).executeAsync();
    }

    @Override
    public void onFailure()
    {
      cancelProgressDialog();
      showToast("Failed connecting to Facebook.");
    }
  });
}

You'll notice two methods above onSucceedPostingFacebookMessage(...) and onFailedPostingFacebookMessage() which you'll need to define to cancel the shown progress dialog, show a toast alerting the user to success or failure, and to perform any additional operations you require. That's it! Just one last Blog post to follow...

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.

Wednesday, 6 February 2013

Windows 8 - how to stop Thumbs.db files being created

I've just recently upgraded from Windows 7 to Windows 8 and I've started seeing Thumbs.db files appear in my folders which contain images. This has adverse effects as you can imagine when running scripts etc that expect only images. So, it's important my operating system not add files in my folders without my say-so!

The only way I've found so far of stopping the operating system adding this Thumbs.db file in my folders is as follows:
  • open a File Explorer window,
  • select the View tab from within the File Explorer window,
  • open the Folder Options window by clicking the Options button from within the View tab,
  • select the View tab from within the Folder Options window,
  • lastly, tick the Always show icons, never thumbnails option in the Advanced settings section of the View tab.
That's it. Hopefully no more Thumbs.db files will be created in your folders as long as this option is ticked. Bear in mind however that you might have to delete any existing Thumbs.db files.

Wednesday, 30 January 2013

Parse Android SDK, Facebook and ProGuard

If you're logging into Facebook via the Parse Android SDK and are building your application package (apk) with ProGuard enabled, here's what you'll need to add to your proguard-project.txt file:

# need this for Facebook SDK
-keepattributes Signature

-dontwarn com.facebook.**
-dontwarn com.parse.**

-keep class com.facebook.** { *; }
-keep class com.parse.** { *; }