Goals of Part 3
1. Using Database Handler (DbHandler)
2. Adding a Service (UpdaterService)
3. Creating a Custom Adapter (TimelineAdapter)
4. Presentation (timeline.xml & row.xml)
5. Timeline Activity (Timeline)
To further enhance the MyTwitter application beyond the simple twitter update functionality and preference menu. A few enhancements are necessary including;
It would be nice to view all the status updates of th people who you are following and can be viewed. The status updates could be stored in a database and together with a service can be used to fetch the twitter status information in the background.
1. Using Database Handler (DbHandler)
Our database is used to store the Time, Statuses and Username.
As before we use a Database handler to handle the database connections including creations, updates and upgrades of the SQLite3 database.
DbHandler.java
package com.example; import winterwell.jtwitter.Twitter.Status; import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; import android.util.Log; public class DbHelper extends SQLiteOpenHelper { Context context; static final String TAG = "DbHelper"; static final String DB_NAME = "timeline.db"; static final int DB_VERSION = 5; static final String TABLE = "timeline"; static final String C_ID = BaseColumns._ID; static final String C_CREATED_AT = "created_at"; static final String C_TEXT = "status"; static final String C_USER = "user"; /** Constructor for DbHelper */ public DbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); this.context = context; } /** Called only once, first time database is created */ @Override public void onCreate(SQLiteDatabase db) { // String sql = String.format( // "CREATE table %s ( " + // "%s INTEGER NOT NULL PRIMARY KEY," + // "%s INTEGER, %s TEXT, %s TEXT)", // TABLE, C_ID, C_CREATED_AT, C_TEXT, C_USER); String sql = context.getResources().getString(R.string.sql); db.execSQL(sql); // execute the sql Log.d(TAG, "onCreate'd sql: " + sql); } /** Called every time DB version changes */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.d(TAG, String .format("onUpgrade from %s to %s", oldVersion, newVersion)); // temporary db.execSQL("DROP TABLE IF EXISTS timeline;"); this.onCreate(db); } /** Converts Twitter.Status to ContentValues */ public static ContentValues statusToContentValues(Status status) { ContentValues ret = new ContentValues(); ret.put(C_ID, status.id); ret.put(C_CREATED_AT, status.getCreatedAt().getTime()); ret.put(C_TEXT, status.getText()); ret.put(C_USER, status.getUser().getScreenName()); return ret; } }
2. Adding a Service (UpdaterService)
UpdaterService service was created to work in the background to retrieve the twitter status updates at a set delay time. In this service the key parts again are the use of SharedPreferences to extract information from the preferences to get login information to retrieve twitter statuses.
Using getTwitter().getFriendsTimeline()
method the service is able retrieve a List of Status objects. Also we have used a Androids Handler() interface to handle when the the service will run.
In our service, we have explicitly used the states of our service to handle different behaviors example onStart() - and begin an updater through the handler, onDestroy() for stopping the updater and closing the database connection.
UpdaterService.java
package com.example; import java.util.List; import winterwell.jtwitter.Twitter; import winterwell.jtwitter.TwitterException; import winterwell.jtwitter.Twitter.Status; import android.app.Service; import android.content.ContentValues; 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"; 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(); } @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 long DELAY = 100000L; public void run() { 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); } catch(SQLException e) {} Log.d(TAG, "Got status: " + status.toString()); } } catch (TwitterException e) { Log.e(TAG, "Updater.run exception: " + e); } // 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", null); String password = prefs.getString("password", null); if (username != null && password != null) twitter = new Twitter(username, password); } return twitter; } }
3. Creating a Custom Adapter (TimelineAdapter)
To map the data to the Timeline view, we created a custom adapter which extends SimpleCursorAdaptor to provide corresponding data to its row view which lives in the larger timeline.xml.
TimelineAdapter.java
package com.example; import android.content.Context; import android.database.Cursor; import android.text.format.DateUtils; import android.view.View; import android.widget.SimpleCursorAdapter; import android.widget.TextView; /** Custom adapter so we can apply transformations on the data */ public class TimelineAdapter extends SimpleCursorAdapter { static final String[] from = { DbHelper.C_CREATED_AT, DbHelper.C_USER, DbHelper.C_TEXT }; static final int[] to = { R.id.textCreatedAt, R.id.textUser, R.id.textText }; /** Constructor */ public TimelineAdapter(Context context, Cursor c) { super(context, R.layout.row, c, from, to); } /** This is where data is mapped to its view */ @Override public void bindView(View row, Context context, Cursor cursor) { super.bindView(row, context, cursor); // Get the individual pieces of data long createdAt = cursor.getLong(cursor .getColumnIndex(DbHelper.C_CREATED_AT)); // Find views by id TextView textCreatedAt = (TextView) row.findViewById(R.id.textCreatedAt); // Apply custom transformations textCreatedAt.setText(DateUtils.getRelativeTimeSpanString(createdAt)); } }
4. Presentation (timeline.xml & row.xml)
timeline.xml
Contains all the rows for the timeline look.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:layout_width="fill_parent" android:background="@drawable/background" android:orientation="vertical"> <TextView style="@style/title" android:text="@string/labelTimeline" /> <ListView android:layout_height="fill_parent" android:layout_width="fill_parent" android:id="@+id/listTimeline" android:background="@color/transparentBlue"></ListView> </LinearLayout>
row.xml
Contains the individual row data
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textUser" android:text="Bobby" android:textColor="@color/darkBlue" android:textStyle="bold"></TextView> <TextView android:layout_height="wrap_content" android:id="@+id/textCreatedAt" android:text="10 minues ago" android:layout_gravity="right" android:gravity="right" android:layout_width="fill_parent" android:textColor="@color/darkBlue"></TextView> </LinearLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textText" android:text="The latest status from Bobby" android:textColor="@color/darkBlue" android:autoLink="all"></TextView> </LinearLayout>
5. Timeline Activity (Timeline)
Timeline is an activity that connects the timeline view with the data with a cursor to our custom data adapter (TimelineAdapter).
Timeline.java
package com.example; import android.app.Activity; 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; @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"); Log.d(TAG, "cursor got count: " + cursor.getCount()); // Setup the adapter adapter = new TimelineAdapter(this, cursor); listTimeline.setAdapter(adapter); } }
ScreenShot
Source
https://www.protechtraining.com/static/tutorials/MyTwitter-Part3.zip