Goal of part 4
Basic MyTwitter is complete and we turn to enhance the application.
1. Using BroadcastReceiver & Notification services in the MyTwitter application
2. Use LocationServices through LocationHelper
1. Using BroadcastReciever & Notification services in the MyTwitter application
As we enhance the MyTwitter application, there was a need to send a message to the UpdaterService that a new message has been posted on your twitter account. Using the BroadcastReceiver and its related methods inside UpdaterService class, one can send a message to the notification manager to allow us to work with the notification.
UpdaterService class has the following added or changed to make this happen;
- Leaveraged the use of db.insertOrThrow(DbHelper.TABLE, null, values) to check if an insert in the database was performed since no exception was thrown by setting our flag haveNewStatus to true.
- Checking the haveNewStatus state determined if to send a Broadcast intent and create a notfication to sent.
Notifications require a Notification instance and NotificationManager i.e.;
Notification is the icon that appears on the top left corner of the Mytwitter screen when its running. And to ensure that the notification continues when phone boots we implement this BootReceiver class;
BootReceiver.java
Code:
package com.example; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; /** This receiver is called when system boots, * so it can start our UpdaterService. */ public class BootReceiver extends BroadcastReceiver { static final String TAG = "BootReceiver"; @Override public void onReceive(Context context, Intent intent) { context.startService(new Intent(context, UpdaterService.class)); Log.d(TAG, "onReceive started UpdaterService"); } }
UpdaterService.java
import java.util.List; import winterwell.jtwitter.Twitter; import winterwell.jtwitter.TwitterException; import winterwell.jtwitter.Twitter.Status; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.os.Handler; import android.os.IBinder; import android.preference.PreferenceManager; import android.util.Log; /** * Responsible for pulling twitter updates from twitter.com and putting it into * the database. */ public class UpdaterService extends Service { static final String TAG = "UpdaterService"; static final String ACTION_NEW_TWITTER_STATUS = "ACTION_NEW_TWITTER_STATUS"; Twitter twitter; Handler handler; Updater updater; DbHelper dbHelper; SQLiteDatabase db; SharedPreferences prefs; @Override public void onCreate() { super.onCreate(); // Get shared preferences prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs .registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences arg0, String arg1) { twitter = null; } }); // Setup handler handler = new Handler(); // Initialize DB dbHelper = new DbHelper(this); db = dbHelper.getWritableDatabase(); Log.d(TAG, "onCreate'd"); } @Override public void onStart(Intent i, int startId) { super.onStart(i, startId); updater = new Updater(); handler.post(updater); Log.d(TAG, "onStart'ed"); } @Override public void onDestroy() { super.onDestroy(); handler.removeCallbacks(updater); // stop the updater db.close(); Log.d(TAG, "onDestroy'd"); } @Override public IBinder onBind(Intent intent) { return null; } /** Updates the database from twitter.com data */ class Updater implements Runnable { static final int NOTIFICATION_ID = 47; static final long DELAY = 100000L; Notification notification; NotificationManager notificationManager; PendingIntent pendingIntent; Updater() { notificationManager = (NotificationManager) UpdaterService.this .getSystemService(Context.NOTIFICATION_SERVICE); notification = new Notification( android.R.drawable.stat_sys_download, "MyTwitter", System.currentTimeMillis()); pendingIntent = PendingIntent.getActivity(UpdaterService.this, 0, new Intent(UpdaterService.this, Timeline.class), 0); } public void run() { boolean haveNewStatus = false; Log.d(UpdaterService.TAG, "Updater ran."); try { List<Status> timeline = getTwitter().getFriendsTimeline(); for (Status status : timeline) { ContentValues values = DbHelper.statusToContentValues(status); // Insert will throw exceptions for duplicate IDs try { db.insertOrThrow(DbHelper.TABLE, null, values); // We have a new status Log.d(TAG, "run() got new status: " + status.getText()); haveNewStatus = true; } catch (SQLException e) { } Log.d(TAG, "Got status: " + status.toString()); } } catch (TwitterException e) { Log.e(TAG, "Updater.run exception: " + e); } // If there's new status, send a broadcast & notify user if (haveNewStatus) { sendBroadcast(new Intent(ACTION_NEW_TWITTER_STATUS)); Log.d(TAG, "run() sent ACTION_NEW_TWITTER_STATUS broadcast."); // Create the notification notification.setLatestEventInfo(UpdaterService.this, "New Twitter Status", "You have new tweets in the timeline", pendingIntent); notification.when = System.currentTimeMillis(); notificationManager.notify(NOTIFICATION_ID, notification); } // Set this to run again later handler.postDelayed(this, DELAY); } } // Initializes twitter, if needed private Twitter getTwitter() { if (twitter == null) { // TODO Fix case when login doesn't work String username = prefs.getString("username", ""); String password = prefs.getString("password", ""); if (username != null && password != null) twitter = new Twitter(username, password); } return twitter; } }
Another addition to our Application was a change in the Timeline.java code where the broadcaster receiver is registered and there is a cursor.requery() to reset information on the Timeline activity.
Timeline.java
Code:
package com.example; import android.app.Activity; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.util.Log; import android.widget.ListView; /** Displays the list of all timelines from the DB. */ public class Timeline extends Activity { static final String TAG = "Timeline"; ListView listTimeline; DbHelper dbHelper; SQLiteDatabase db; Cursor cursor; TimelineAdapter adapter; BroadcastReceiver twitterStatusReceiver; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.timeline); // Find views by id listTimeline = (ListView) findViewById(R.id.listTimeline); // Initialize DB dbHelper = new DbHelper(this); db = dbHelper.getReadableDatabase(); // Get the data from the DB cursor = db.query(DbHelper.TABLE, null, null, null, null, null, DbHelper.C_CREATED_AT + " DESC"); startManagingCursor(cursor); Log.d(TAG, "cursor got count: " + cursor.getCount()); // Setup the adapter adapter = new TimelineAdapter(this, cursor); listTimeline.setAdapter(adapter); // Register to get ACTION_NEW_TWITTER_STATUS broadcasts twitterStatusReceiver = new TwitterStatusReceiver(); registerReceiver(twitterStatusReceiver, new IntentFilter( UpdaterService.ACTION_NEW_TWITTER_STATUS)); } @Override public void onResume() { super.onResume(); // Cancel notification NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.cancel(UpdaterService.Updater.NOTIFICATION_ID); } @Override public void onDestroy() { super.onDestroy(); unregisterReceiver(twitterStatusReceiver); } class TwitterStatusReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive got ACTION_NEW_TWITTER_STATUS broadcast"); cursor.requery(); } } }
NOTE: Necessary to register BootReceiver in the AndroidManifest.xml file
<receiver android:name=".BootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> <uses-sdk android:minSdkVersion="3" /> ... <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> ...
2. Use LocationServices through LocationHelper
Another enhancement was to be able to parser our twitter update we post to look for a code "@loc" which would refer to geolocation when we sent the tweet from.
For this we require the use of LocationHelper class that would manage using the LocationManager and get the location of the cellphone by leaving replacing the "@loc" with "(longitude, latitude)" in the tweet message and since there is 140 character limit, we also made sure the output was not going to be beyond the allowed twitter limit.
LocationHelper.java
import android.content.Context; import android.location.Criteria; import android.location.Location; import android.location.LocationManager; import android.util.Log; /** Helper class to add support for location in the status updates. */ public class LocationHelper { static final String TAG = "LocationHelper"; static final String LOC = "@loc"; // code that gets replaced with location Context context; LocationManager locationManager; Location location; Criteria criteria; String bestProvider; public LocationHelper(Context context) { this.context = context; locationManager = (LocationManager) context .getSystemService(Context.LOCATION_SERVICE); criteria = new Criteria(); bestProvider = locationManager.getBestProvider(criteria, false); location = locationManager.getLastKnownLocation(bestProvider); Log.d(TAG, "construct'd with location: " + location); } /** Converts the LOC code to current location */ public String updateStatusForLocation(String input) { String output; if (location == null) { output = input.replaceAll(LOC, "UNKNOWN"); } else { output = input.replaceAll(LOC, String.format("(%f,%f)", location.getLongitude(), location.getLatitude())); } // Make sure we don't go over 140 characters output = (output.length() > 139) ? output.substring(0, 139) : output; Log.d(TAG, String.format("updateStatusForLocation(%s)=>%s", input, output)); return output; } }
AndroidManifest.xml
Code:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/twitter_icon" android:label="@string/app_name"> <activity android:name=".MyTwitter" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Prefs" /> <activity android:name=".Timeline" /> <service android:name=".UpdaterService" /> <receiver android:name=".BootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> <uses-sdk android:minSdkVersion="3" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> </manifest>
NOTE: To avoid repeating what each method does please review the comments inserted in the code.
ScreenShots
Source
https://www.protechtraining.com/static/tutorials/MyTwitter-Part4.zip