In this series we are building a Twitter client for the Android platform using the Twitter4J library. This tutorial will focus on implementing a Service to continually fetch new tweets for the user's home timeline. We will also use a Broadcast Receiver to update the app interface when new tweets become available for display.
Also available in this series:
- Creating a Twitter Client for Android: Setup & Overview
- Creating a Twitter Client for Android: Building the Interface
- Creating a Twitter Client for Android: Creating a Timeline Database
- Creating a Twitter Client for Android: Retrieving Updates Using a Service
- Creating a Twitter Client for Android: Tweeting, Retweeting, and Replying
Step 1: Create a Service Class
So that the user's home timeline tweets will automatically be displayed when they become available, we are going to use a Service to retrieve them at set intervals. This Service will run at a frequency of your choice, writing to the database and sending Broadcasts when new tweets have been retrieved and stored. The app's main Activity class will receive the Broadcasts and requery the database, updating the timeline interface to display the new data.
Create a new class in your project, naming it "TimelineService" and altering the opening class declaration line as follows:
public class TimelineService extends Service {
You will need the following import:
import android.app.Service;
Add the following instance variables to your class, for interacting with Twitter:
/**twitter authentication key*/ public final static String TWIT_KEY = "your key"; /**twitter secret*/ public final static String TWIT_SECRET = "your secret"; /**twitter object*/ private Twitter timelineTwitter;
Alter the key and secret variables to suit your own, as used in the previous tutorials. Add the following variables for database handling:
/**database helper object*/ private NiceDataHelper niceHelper; /**timeline database*/ private SQLiteDatabase niceDB;
Finally, add the following for generic use within the class:
/**shared preferences for user details*/ private SharedPreferences nicePrefs; /**handler for updater*/ private Handler niceHandler; /**delay between fetching new tweets*/ private static int mins = 5;//alter to suit private static final long FETCH_DELAY = mins * (60*1000); //debugging tag private String LOG_TAG = "TimelineService";
The Handler object is for scheduling updates at set frequencies. The "mins" variable and "FETCH_DELAY" constant let you set the frequency at which the app will fetch new updates from the user's home timeline. Alter the "mins" variable to reflect however many minutes you want the app to wait between updates. The updates will only be fetched at this frequency while the app is running. When the user exits the app and it is destroyed, it will stop the Service from continually running. The next time the user runs the app, it will fetch new updates by restarting the Service.
At the top of your class file, add the following imports for the Twitter4J library:
import twitter4j.Status; import twitter4j.Twitter; import twitter4j.TwitterFactory; import twitter4j.conf.Configuration; import twitter4j.conf.ConfigurationBuilder;
With the following list for Android resources:
import android.app.Service; import android.content.ContentValues; import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.os.Handler; import android.os.IBinder; import android.util.Log;
And finally the following for the Java List class:
import java.util.List;
To implement our Service class we are going to provide the "onCreate" and "onStartCommand" methods for when the class is instantiated and the Service started, the "onDestroy" method for when it finishes, and the "onBind" method, which is required although we will not be using it. We are also going to create an inner class, in which we will retrieve the updates from Twitter at set intervals.
First, let's implement the "onCreate" method:
@Override public void onCreate() { super.onCreate(); //setup the class }
Inside the method, we will instantiate some of our instance variables:
//get prefs nicePrefs = getSharedPreferences("TwitNicePrefs", 0); //get database helper niceHelper = new NiceDataHelper(this); //get the database niceDB = niceHelper.getWritableDatabase();
Let's also create an instance of the Twitter4J object so that we can fetch tweets:
//get user preferences String userToken = nicePrefs.getString("user_token", null); String userSecret = nicePrefs.getString("user_secret", null); //create new configuration Configuration twitConf = new ConfigurationBuilder() .setOAuthConsumerKey(TWIT_KEY) .setOAuthConsumerSecret(TWIT_SECRET) .setOAuthAccessToken(userToken) .setOAuthAccessTokenSecret(userSecret) .build(); //instantiate new twitter timelineTwitter = new TwitterFactory(twitConf).getInstance();
We need to implement the "onBind" method although we do not actually use it, so add it as follows:
@Override public IBinder onBind(Intent intent) { return null; }
Step 2: Create a Runnable Class
When the Service starts, the "onStartCommand" method will execute, and when it is destroyed, the "onDestroy" method will fire. We will implement both of these methods soon, but first we will create an inner class to handle retrieving the updates at fixed periods. Inside your "TimelineService" class declaration, add the following class outline:
/** * TimelineUpdater class implements the runnable interface */ class TimelineUpdater implements Runnable { //fetch updates }
Inside this class we will retrieve the user's home timeline, write them to the database and send a Broadcast to the main Activity when there are new tweets to display. This is all going to happen in the "run" method, so add it to your new inner class as follows:
//run method public void run() { }
We are only going to send the Broadcast to the main Activity when there are new tweets, so to keep track we create a boolean variable inside the "run" method:
//check for updates - assume none boolean statusChanges = false;
Next we are going to attempt to fetch data from the Internet, so we need try and catch blocks:
try { //fetch timeline } catch (Exception te) { Log.e(LOG_TAG, "Exception: " + te); }
Inside the try block, fetch the user timeline as a List object:
//retrieve the new home timeline tweets as a list List<Status> homeTimeline = timelineTwitter.getHomeTimeline();
The retrieved timeline is a List of Status objects. Each Status object contains the data for a single tweet update. Now we need to loop through the new tweets and insert them into the database:
//iterate through new status updates for (Status statusUpdate : homeTimeline) { //call the getValues method of the data helper class, passing the new updates ContentValues timelineValues = NiceDataHelper.getValues(statusUpdate); //if the database already contains the updates they will not be inserted niceDB.insertOrThrow("home", null, timelineValues); //confirm we have new updates statusChanges = true; }
Notice that we are calling the "getValues" method of the NiceDataHelper class, which is static. The "getValues" method takes Twitter Status objects and retrieves the relevant data for our database, i.e. the tweet ID, text, screen-name, time and profile image URL, all of which are contained within each Status instance. The method returns these as sets of values that can be inserted into the database, which we do here. Since there are new tweets, we set the "statusChanges" flag to true.
After the catch block, we send a Broadcast to the main Activity only if there are new tweets to display:
//if we have new updates, send a Broadcast if (statusChanges) { //this should be received in the main timeline class sendBroadcast(new Intent("TWITTER_UPDATES")); }
We will handle receipt of this in the main Activity class later. Finally, after this if statement and still inside the "run" method, instruct Android to call the "run" method again after your chosen delay:
//delay fetching new updates niceHandler.postDelayed(this, FETCH_DELAY);
At the top of your TimelineService class, add another instance variable for this new class:
/**updater thread object*/ private TimelineUpdater niceUpdater;
Step 3: Start the Runnable Object
Now we can handle the "TimelineService" method for when the Service starts. Back in the Service class (outside the new Runnable class), add the "onStartCommand" method:
@Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStart(intent, startId); //get handler niceHandler = new Handler(); //create an instance of the updater class niceUpdater = new TimelineUpdater(); //add to run queue niceHandler.post(niceUpdater); //return sticky return START_STICKY; }
Here we call the method of the superclass and return a standard integer value. We also instantiate the Handler and new Runnable class. The Handler adds the Runnable to the process queue so that its "run" method will execute.
Now all we need to finish the Service class is to implement the destroy method:
@Override public void onDestroy() { super.onDestroy(); //stop the updating niceHandler.removeCallbacks(niceUpdater); niceDB.close(); }
Step 4: Receive Broadcasts
Back in the app's main Activity class, we can now start the Service and receive the resulting Broadcasts. Add the following instance variable at the top of your "TwitNiceActivity" class:
/**Broadcast receiver for when new updates are available*/ private BroadcastReceiver niceStatusReceiver;
You will need the following import statement:
import android.content.BroadcastReceiver;
Add a new inner class to your main Activity class to receive Broadcasts, making sure you add it outside any of the methods but still inside the Activity class declaration:
/** * Class to implement Broadcast receipt for new updates */ class TwitterUpdateReceiver extends BroadcastReceiver { }
This class is going to do one thing: receive Broadcasts - so implement the "onReceive" method inside it:
@Override public void onReceive(Context context, Intent intent) { }
Inside the method, we are going to minimize the size of the database by deleting some of the records whenever new tweets become available:
int rowLimit = 100; if(DatabaseUtils.queryNumEntries(timelineDB, "home")>rowLimit) { String deleteQuery = "DELETE FROM home WHERE "+BaseColumns._ID+" NOT IN " + "(SELECT "+BaseColumns._ID+" FROM home ORDER BY "+"update_time DESC " + "limit "+rowLimit+")"; timelineDB.execSQL(deleteQuery); }
In this case we are limiting the table to 100 rows, but you can of course change this to a number of your choice. The query deletes all but the 100 newest records in the table.
Add the following import to the class:
import android.provider.BaseColumns; import android.database.DatabaseUtils; import android.content.Context;
Now we need to query the database and update the user interface Views using the Adapter:
timelineCursor = timelineDB.query("home", null, null, null, null, null, "update_time DESC"); startManagingCursor(timelineCursor); timelineAdapter = new UpdateAdapter(context, timelineCursor); homeTimeline.setAdapter(timelineAdapter);
Notice that we use a similar process to what happens in the "setupTimeline" method. When the user has already run the app at least once, on launching it they will see the existing data while new data is being fetched. As soon as the new data is available, the Views will be updated to display it. Naturally the speed at which this happens will depend on the user's network connectivity.
Step 5: Start the Service
Let's complete the "setupTimeline" method. After the line in which you instantiated the UpdateAdapter class last time, before the try block ends, add the following to set the Adapter to the timeline:
//this will make the app populate the new update data in the timeline view homeTimeline.setAdapter(timelineAdapter);
Next create an instance of your Broadcast Receiver class and register it to receive updates from the Service class:
//instantiate receiver class for finding out when new updates are available niceStatusReceiver = new TwitterUpdateReceiver(); //register for updates registerReceiver(niceStatusReceiver, new IntentFilter("TWITTER_UPDATES"));
Notice that the String we provide to the IntentFilter constructor here matches the String we use when sending the Broadcast within the TimelineService Runnable class.
You will need the following import:
import android.content.IntentFilter;
Finally, start the Service, passing the class name:
//start the Service for updates now this.getApplicationContext().startService(new Intent(this.getApplicationContext(), TimelineService.class));
Before we finish, let's implement the destroy method for the main Activity class since it concerns objects we have used here:
@Override public void onDestroy() { super.onDestroy(); try { //stop the updater Service stopService(new Intent(this, TimelineService.class)); //remove receiver register unregisterReceiver(niceStatusReceiver); //close the database timelineDB.close(); } catch(Exception se) { Log.e(LOG_TAG, "unable to stop Service or receiver"); } }
Whenever you handle databases and Services it's important not to waste resources by leaving them running when the app is not actually executing. Here we are stopping the Service, unregistering the receiver we have been listening for and closing the database. When the app is launched again these will all be restarted.
Next
We have now implemented displaying the user's home timeline tweets, fetching them at fixed intervals through a Service and detecting their retrieval using Broadcasts. In the final tutorial we will implement tweeting, retweeting and replying. This will involve implementing a new Tweet Activity and the retweet/ reply buttons within each update in the main timeline.
Comments