From 8c25d89fcfb39ddc1087f7dedfdd5ce411e1e39c Mon Sep 17 00:00:00 2001 From: Joshua Ganderson Date: Thu, 18 Aug 2016 15:35:08 -0500 Subject: [PATCH 1/3] Storing notifications to db. --- .../com/g11x/checklistapp/data/Database.java | 3 ++- .../services/NotificationService.java | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/g11x/checklistapp/data/Database.java b/app/src/main/java/com/g11x/checklistapp/data/Database.java index 5010e62..2964ec9 100644 --- a/app/src/main/java/com/g11x/checklistapp/data/Database.java +++ b/app/src/main/java/com/g11x/checklistapp/data/Database.java @@ -163,6 +163,7 @@ public static class Notification { public static final String TITLE_COLUMN = "title"; public static final String MESSAGE_COLUMN = "message"; public static final String READ_COLUMN = "item_hash"; + public static final String SENT_TIME = "sent_time"; /** * SQL statement for creating this table. @@ -198,7 +199,7 @@ private static Uri createContentUri() { } private static String createCreateTableSql() { - return "create table " + TABLE_NAME + " (_ID integer primary key, " + TITLE_COLUMN + " text, " + MESSAGE_COLUMN + " text, " + READ_COLUMN + " boolean);"; + return "create table " + TABLE_NAME + " (_ID integer primary key, " + TITLE_COLUMN + " text, " + MESSAGE_COLUMN + " text, " + READ_COLUMN + " boolean, " + SENT_TIME + " integer);"; } } diff --git a/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java b/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java index ff13ae3..2adb2b2 100644 --- a/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java +++ b/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java @@ -18,8 +18,11 @@ package com.g11x.checklistapp.services; +import android.content.ContentValues; +import android.net.Uri; import android.util.Log; +import com.g11x.checklistapp.data.Database; import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; @@ -33,10 +36,25 @@ public class NotificationService extends FirebaseMessagingService { public void onMessageReceived (RemoteMessage message) { super.onMessageReceived(message); + RemoteMessage.Notification notification = message.getNotification(); + Log.d(TAG, "Received notification: " + message.getNotification().getBody()); - // TODO: Route message to DB, specifically message.getNotification().getBody() && getTitle(). // NOTE: Message expiration date not present in message payload, appears to only be server-side. + ContentValues newValues = new ContentValues(); + newValues.put(Database.Notification.MESSAGE_COLUMN, notification.getBody()); + newValues.put(Database.Notification.READ_COLUMN, false); + newValues.put(Database.Notification.SENT_TIME, message.getSentTime()); + String title = notification.getTitle(); + if (title != null && !title.isEmpty()) { + newValues.put(Database.Notification.TITLE_COLUMN, notification.getBody()); + } + getContentResolver().insert( + Database.Notification.CONTENT_URI, + newValues + ); + + // TODO: Update notification view to be bound to DB and auto-reflect updates in reverse // chronological order. From b118899e8c172ea348a427c89dea508d208e41da Mon Sep 17 00:00:00 2001 From: Joshua Ganderson Date: Fri, 19 Aug 2016 14:15:20 -0500 Subject: [PATCH 2/3] Wiring notifications to db. Adding incoming notifications to db. Retrieving notifications from db for notification view. Updating db calls to throw exceptions when unimplemented. --- .../g11x/checklistapp/LocalRepository.java | 6 +- .../NotificationListActivity.java | 111 +++++++++++------- .../com/g11x/checklistapp/data/Database.java | 35 +++++- .../g11x/checklistapp/data/Notification.java | 50 +++++++- .../services/NotificationService.java | 11 +- .../res/layout/activity_notification_list.xml | 14 +-- build.gradle | 2 +- 7 files changed, 165 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/g11x/checklistapp/LocalRepository.java b/app/src/main/java/com/g11x/checklistapp/LocalRepository.java index 51e7f9f..f4bffb4 100644 --- a/app/src/main/java/com/g11x/checklistapp/LocalRepository.java +++ b/app/src/main/java/com/g11x/checklistapp/LocalRepository.java @@ -51,12 +51,12 @@ public Uri insert(@NonNull Uri uri, ContentValues contentValues) { @Override public int delete(@NonNull Uri uri, String s, String[] strings) { - return 0; + throw new UnsupportedOperationException(); } @Override - public int update(@NonNull Uri uri, ContentValues contentValues, String s, String[] strings) { - return 0; + public int update(@NonNull Uri uri, ContentValues values, String whereClause, String[] whereArgs) { + return Database.update(openHelper.getWritableDatabase(), uri, values, whereClause, whereArgs); } // Helper class that actually creates and manages the provider's underlying data repository. diff --git a/app/src/main/java/com/g11x/checklistapp/NotificationListActivity.java b/app/src/main/java/com/g11x/checklistapp/NotificationListActivity.java index e98f18b..7de6b5a 100644 --- a/app/src/main/java/com/g11x/checklistapp/NotificationListActivity.java +++ b/app/src/main/java/com/g11x/checklistapp/NotificationListActivity.java @@ -1,6 +1,11 @@ package com.g11x.checklistapp; +import android.content.ContentValues; +import android.database.Cursor; import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; @@ -9,10 +14,14 @@ import android.view.ViewGroup; import android.widget.TextView; +import com.g11x.checklistapp.data.Database; import com.g11x.checklistapp.data.Notification; public class NotificationListActivity extends NavigationActivity { + private NotificationAdapter adapter; + private RecyclerView mRecyclerView; + @Override protected int getNavDrawerItemIndex() { return NAVDRAWER_ITEM_NOTIFICATIONS; @@ -22,7 +31,7 @@ protected int getNavDrawerItemIndex() { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_notification_list); - RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view); + mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view); // use this setting to improve performance if you know that changes // in content do not change the layout size of the RecyclerView @@ -40,58 +49,74 @@ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHol @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - // TODO: Remove item from DB (when db exists). + long id = ((NotificationAdapter.ViewHolder) viewHolder).id; + + ContentValues newValues = new ContentValues(); + newValues.put(Database.Notification.READ_COLUMN, true); + + getContentResolver().update( + Database.Notification.CONTENT_URI, + newValues, + Database.ID_COLUMN + " = " + id, + null + ); } }); helper.attachToRecyclerView(mRecyclerView); - // TODO: Replace dataset with db cursor. - Notification[] dataset = new Notification[]{ - new Notification("message 1", null), - new Notification("message 2", "I'm a title!"), - new Notification("message C", "I can't count :("), - new Notification("I like fish, fishy fishy fish.", null) - }; - RecyclerView.Adapter mAdapter = new NotificationAdapter(dataset); - mRecyclerView.setAdapter(mAdapter); - } - - public class NotificationAdapter extends RecyclerView.Adapter { - private final Notification[] mDataset; + getSupportLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks() { + @Override + public Loader onCreateLoader(int i, Bundle bundle) { + Loader cursor = new CursorLoader(NotificationListActivity.this, + Database.Notification.CONTENT_URI, + Database.Notification.PROJECTION, "NOT " + Database.Notification.READ_COLUMN, null, Database.Notification.SENT_TIME); + return cursor; + } - // Provide a reference to the views for each data item - // Complex data items may need more than one view per item, and - // you provide access to all the views for a data item in a view holder - public class ViewHolder extends RecyclerView.ViewHolder { - // each data item is just a string in this case - public final TextView mTitle; - public final TextView mBody; + @Override + public void onLoadFinished(Loader loader, Cursor cursor) { + if (adapter == null) { + if (cursor != null) { + adapter = new NotificationAdapter(cursor); + mRecyclerView.setAdapter(adapter); + } + } else { + adapter.swapCursor(cursor); + } + } - public ViewHolder(View v) { - super(v); - mTitle = (TextView) v.findViewById(R.id.notification_title); - mBody = (TextView) v.findViewById(R.id.notification_body); + @Override + public void onLoaderReset(Loader loader) { + adapter.swapCursor(null); } + }); + } + + @Override + protected void onStart() { + super.onStart(); + if (adapter != null) { + adapter.notifyDataSetChanged(); } + } + + private static class NotificationAdapter extends RecyclerViewCursorAdapter { - // Provide a suitable constructor (depends on the kind of dataset) - public NotificationAdapter(Notification[] myDataset) { - mDataset = myDataset; + NotificationAdapter(Cursor cursor) { + super(cursor); } - // Create new views (invoked by the layout manager) @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - // create a new view - View v = LayoutInflater.from(parent.getContext()) + View layout = LayoutInflater.from(parent.getContext()) .inflate(R.layout.view_notification_item, parent, false); - return new ViewHolder(v); + return new ViewHolder(layout); } - // Replace the contents of a view (invoked by the layout manager) @Override - public void onBindViewHolder(ViewHolder holder, int position) { - Notification n = mDataset[position]; + public void onBindViewHolder(ViewHolder holder, Cursor cursor) { + Notification n = Notification.fromCursor(cursor); + holder.id = n.getId(); holder.mBody.setText(n.getMessage()); String title = n.getTitle(); if (title != null && !title.isEmpty()) { @@ -102,10 +127,16 @@ public void onBindViewHolder(ViewHolder holder, int position) { } } - // Return the size of your dataset (invoked by the layout manager) - @Override - public int getItemCount() { - return mDataset.length; + static class ViewHolder extends RecyclerView.ViewHolder { + public final TextView mTitle; + public final TextView mBody; + public long id; + + public ViewHolder(View v) { + super(v); + mTitle = (TextView) v.findViewById(R.id.notification_title); + mBody = (TextView) v.findViewById(R.id.notification_body); + } } } } diff --git a/app/src/main/java/com/g11x/checklistapp/data/Database.java b/app/src/main/java/com/g11x/checklistapp/data/Database.java index 2964ec9..7f0bf15 100644 --- a/app/src/main/java/com/g11x/checklistapp/data/Database.java +++ b/app/src/main/java/com/g11x/checklistapp/data/Database.java @@ -45,6 +45,10 @@ public static Uri insert(SQLiteDatabase db, Uri uri, ContentValues contentValues return Database.getTableHandler(db, uri).insert(contentValues); } + public static int update(SQLiteDatabase db, Uri uri, ContentValues values, String whereClause, String[] whereArgs) { + return Database.getTableHandler(db, uri).update(values, whereClause, whereArgs); + } + /** * Information representing the ImportantInformation content.. */ @@ -87,6 +91,11 @@ public Cursor query(String[] projection, String selection, String[] selectionArg return db.query(Database.ImportantInformation.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder); } + + @Override + public int update(ContentValues values, String whereClause, String[] whereArgs) { + throw new UnsupportedOperationException(); + } } private static Uri createContentUri() { @@ -142,6 +151,11 @@ public Uri insert(ContentValues contentValues) { public Cursor query(String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } + + @Override + public int update(ContentValues values, String whereClause, String[] whereArgs) { + throw new UnsupportedOperationException(); + } } private static Uri createContentUri() { @@ -162,9 +176,17 @@ public static class Notification { public static final String TITLE_COLUMN = "title"; public static final String MESSAGE_COLUMN = "message"; - public static final String READ_COLUMN = "item_hash"; + public static final String READ_COLUMN = "read"; public static final String SENT_TIME = "sent_time"; + public static final String[] PROJECTION = { + Database.ID_COLUMN, + SENT_TIME, + READ_COLUMN, + TITLE_COLUMN, + MESSAGE_COLUMN + }; + /** * SQL statement for creating this table. */ @@ -183,13 +205,18 @@ private static class NotificationTableHandler implements TableHandler { @Override public Uri insert(ContentValues contentValues) { - long id = db.insert(Database.ChecklistItem.TABLE_NAME, null, contentValues); + long id = db.insert(TABLE_NAME, null, contentValues); return ContentUris.withAppendedId(contentUri, id); } + @Override + public int update(ContentValues values, String whereClause, String[] whereArgs) { + return db.update(TABLE_NAME, values, whereClause, whereArgs); + } @Override public Cursor query(String[] projection, String selection, String[] selectionArgs, String sortOrder) { - return null; + // group by, having, order by + return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, null); } } @@ -223,6 +250,8 @@ private interface TableHandler { Uri insert(ContentValues contentValues); Cursor query(String[] projection, String selection, String[] selectionArgs, String sortOrder); + + int update(ContentValues values, String whereClause, String[] whereArgs); } private static TableHandler getTableHandler(SQLiteDatabase db, Uri contentUri) { diff --git a/app/src/main/java/com/g11x/checklistapp/data/Notification.java b/app/src/main/java/com/g11x/checklistapp/data/Notification.java index 1820c76..3330abb 100644 --- a/app/src/main/java/com/g11x/checklistapp/data/Notification.java +++ b/app/src/main/java/com/g11x/checklistapp/data/Notification.java @@ -17,14 +17,21 @@ package com.g11x.checklistapp.data; -import android.support.annotation.Nullable; +import android.database.Cursor; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; /** * Stored representation of a Firebase notification. */ - public class Notification { + @NonNull + private final Long id; + + @NonNull + private final Long sentTime; + + private final boolean read; @Nullable private final String title; @@ -32,11 +39,48 @@ public class Notification { @NonNull private final String message; - public Notification(@NonNull String message, @Nullable String title) { + public Notification( + @NonNull Long id, + @NonNull Long sentTime, + boolean read, + @Nullable String title, + @NonNull String message) { + this.id = id; + this.sentTime = sentTime; + this.read = read; this.message = message; this.title = title; } + /** + * Convenience method to convert a {@code Cursor} to {@code Notification}. Field order found in + * {@code com.g11x.checklistapp.data.Database.Notification.PROJECTION}. + */ + public static + @NonNull + Notification fromCursor(Cursor cursor) { + return new Notification( + cursor.getLong(0), + cursor.getLong(1), + cursor.getShort(2) > 0, + cursor.getString(3), + cursor.getString(4)); + } + + @NonNull + public Long getId() { + return id; + } + + @NonNull + public Long getSentTime() { + return sentTime; + } + + public boolean isRead() { + return read; + } + @Nullable public String getTitle() { return title; diff --git a/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java b/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java index 2adb2b2..1fca254 100644 --- a/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java +++ b/app/src/main/java/com/g11x/checklistapp/services/NotificationService.java @@ -19,7 +19,6 @@ import android.content.ContentValues; -import android.net.Uri; import android.util.Log; import com.g11x.checklistapp.data.Database; @@ -33,7 +32,7 @@ public class NotificationService extends FirebaseMessagingService { private static final String TAG = "NotificationService"; - public void onMessageReceived (RemoteMessage message) { + public void onMessageReceived(RemoteMessage message) { super.onMessageReceived(message); RemoteMessage.Notification notification = message.getNotification(); @@ -47,17 +46,15 @@ public void onMessageReceived (RemoteMessage message) { newValues.put(Database.Notification.SENT_TIME, message.getSentTime()); String title = notification.getTitle(); if (title != null && !title.isEmpty()) { - newValues.put(Database.Notification.TITLE_COLUMN, notification.getBody()); + newValues.put(Database.Notification.TITLE_COLUMN, title); } getContentResolver().insert( Database.Notification.CONTENT_URI, newValues ); - - - // TODO: Update notification view to be bound to DB and auto-reflect updates in reverse - // chronological order. + // TODO: Provide some affordance to display the message when the app is in the foreground. + // See https://firebase.google.com/docs/cloud-messaging/android/receive for more details. } } diff --git a/app/src/main/res/layout/activity_notification_list.xml b/app/src/main/res/layout/activity_notification_list.xml index 25c3858..d129d11 100644 --- a/app/src/main/res/layout/activity_notification_list.xml +++ b/app/src/main/res/layout/activity_notification_list.xml @@ -1,15 +1,15 @@ - + - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1a16d93..242e4d1 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-beta1' + classpath 'com.android.tools.build:gradle:2.2.0-beta2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 167811926951235397d6ddbf71c1905417a7602d Mon Sep 17 00:00:00 2001 From: Joshua Ganderson Date: Fri, 19 Aug 2016 14:58:39 -0500 Subject: [PATCH 3/3] Addressing nit --- .../main/java/com/g11x/checklistapp/data/Database.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/g11x/checklistapp/data/Database.java b/app/src/main/java/com/g11x/checklistapp/data/Database.java index 945a410..8fe7060 100644 --- a/app/src/main/java/com/g11x/checklistapp/data/Database.java +++ b/app/src/main/java/com/g11x/checklistapp/data/Database.java @@ -211,7 +211,6 @@ public Uri insert(ContentValues contentValues) { @Override public Cursor query(String[] projection, String selection, String[] selectionArgs, String sortOrder) { - // group by, having, order by return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, null); } @@ -223,11 +222,14 @@ public int update(ContentValues contentValues, String selection, String[] select private static Uri createContentUri() { // TODO: Figure out how to use getString(R.string.content_provider_authority) here. - return Uri.parse("content://" + COM_G11X_CHECKLISTAPP_PROVIDER + "/" + Database.NAME + "/" + TABLE_NAME); + return Uri.parse("content://" + COM_G11X_CHECKLISTAPP_PROVIDER + "/" + Database.NAME + "/" + + TABLE_NAME); } private static String createCreateTableSql() { - return "create table " + TABLE_NAME + " (_ID integer primary key, " + TITLE_COLUMN + " text, " + MESSAGE_COLUMN + " text, " + READ_COLUMN + " boolean, " + SENT_TIME + " integer);"; + return "create table " + TABLE_NAME + " (" + ID_COLUMN + " integer primary key, " + + TITLE_COLUMN + " text, " + MESSAGE_COLUMN + " text, " + READ_COLUMN + " boolean, " + + SENT_TIME + " integer);"; } }