Development of an Android App - MyTwitter Part 4

Forums » Android - Examples > Development of an Android App - MyTwitter Part 4
February 4, 2010 9:19:38 PM PST (2 years ago). Seen 21,007 times. 7 replies.
Photo Serete Itebete
Member since Dec 23, 2009
Location: Oakland
Forum Posts: 17
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

Code:
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
Code:

<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
Code:
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;
}
}


Reference on Location Services: http://marakana.com/forums/android/android_examples/42.html

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
http://marakana.com/static/tutorials/MyTwitter-Part4.zip
Edited 7 times. Last edit by Marcio Valenzuela on May 24, 2011 at 10:20:31 AM (about 52 weeks ago).
November 8, 2010 11:09:09 PM PST (one year ago)
Photo Por Yee Ong
Student
Member since Aug 25, 2010
Forum Posts: 4
hi nice work, but i am facing some problems with the updates. Below is the logcat, hope you can help me with it. Thank you

11-09 07:06:28.155: ERROR/MyTwitter(218): Twitter exception: winterwell.jtwitter.TwitterException: 401 Unauthorized http://twitter.com/statuses/update.json
11-09 07:06:28.195: INFO/NotificationService(53): enqueueToast pkg=com.example callback=android.app.ITransientNotification$Stub$Proxy@43debf88 duration=1
11-09 07:06:42.035: DEBUG/UpdaterService(218): Updater ran.
11-09 07:06:42.715: ERROR/UpdaterService(218): Updater.run exception: winterwell.jtwitter.TwitterException: 401 Unauthorized http://twitter.com/statuses/friends_timeline.json
November 8, 2010 11:16:21 PM PST (one year ago)
Photo Serete Itebete
Member since Dec 23, 2009
Location: Oakland
Forum Posts: 17
Por,

TwitterException 401 might suggest that you are not authorized with the twitter server.

Please make sure that you already have a Twitter account by registering your new account at http://twitter.com They use the same username and password to login through this application.

Hope that helps.
November 8, 2010 11:19:01 PM PST (one year ago)
Photo Por Yee Ong
Student
Member since Aug 25, 2010
Forum Posts: 4
Thx for the fast reply, i have already created a new acc for this project and i was able to tweet on the web itself.

its okay i will try to find an answer myself thank you :)
November 19, 2010 9:57:24 AM PST (one year ago)
Photo Por Yee Ong
Student
Member since Aug 25, 2010
Forum Posts: 4
Hey man, it seems that the lib files are not valid anymore, to use the current twitter you have to use Oauth and twitter4j

with warmest regards.
April 13, 2011 11:31:16 AM PDT (one year ago)
Photo Rafael Decker
Student
Member since Apr 13, 2011
Forum Posts: 1
Hello,

I've downloaded the source code of this example and put my username and password on the preferences.
When i send a message, is displayed a message warning that the message was sent succesfully, but the message doesn't is sent.
I enter on twitter to see the message, but it isn't there.
Anyone knows what probably happens?
May 24, 2011 10:20:31 AM PDT (51 weeks ago)
Photo Marcio Valenzuela
Santiapps
Member since May 16, 2011
Forum Posts: 10
Ok, I tested this and it did not work. Like the first guy to post, I get this:


05-24 11:05:45.692: ERROR/MyTwitter(726): Twitter exception: winterwell.jtwitter.TwitterException: 401 Unauthorized http://twitter.com/statuses/update.json

05-24 11:05:45.722: INFO/NotificationService(563): enqueueToast pkg=com.example callback=android.app.ITransientNotification$Stub$Proxy@43712e48 duration=1

my account is fine, ive triple checked my password and i can tweet just fine.

i registered my app at dev.twitter.com under an email and pass. but i have no callback or scheme...pls hlp! I guess that might be the problem.
January 17, 2012 5:37:09 AM PST (17 weeks ago)
Photo Vijay Kumar
Er
Web solutions
Member since Jan 17, 2012
Forum Posts: 1
I had a error message in emulater like "Unfortunately MyTwitter has stopped". What is problem of mine???????? Pls give me a solution. I,m new to android developing......