11package com .codepath .instagram .persistence ;
22
3- public class InstagramClientDatabase {
3+ import android .content .ContentValues ;
4+ import android .content .Context ;
5+ import android .database .Cursor ;
6+ import android .database .sqlite .SQLiteDatabase ;
7+ import android .database .sqlite .SQLiteOpenHelper ;
8+ import android .util .Log ;
9+
10+ import com .codepath .instagram .models .InstagramComment ;
11+ import com .codepath .instagram .models .InstagramImage ;
12+ import com .codepath .instagram .models .InstagramPost ;
13+ import com .codepath .instagram .models .InstagramUser ;
14+
15+ import java .util .ArrayList ;
16+ import java .util .List ;
17+
18+ public class InstagramClientDatabase extends SQLiteOpenHelper {
419 private static final String TAG = "InstagramClientDatabase" ;
520
21+ // Database info
622 private static final String DATABASE_NAME = "instagramClientDatabase" ;
723 private static final int DATABASE_VERSION = 1 ;
824
25+ // Tables
926 private static final String TABLE_POSTS = "posts" ;
1027 private static final String TABLE_USERS = "users" ;
1128 private static final String TABLE_IMAGES = "images" ;
1229 private static final String TABLE_COMMENTS = "comments" ;
1330 private static final String TABLE_POST_COMMENTS = "postComments" ;
1431
32+ // Constraints
1533 private static final String CONSTRAINT_POST_COMMENTS_PK = "postComments_pk" ;
1634
17- private static final String KEY_ID = "_id" ;
18-
1935 // Posts table columns
36+ private static final String KEY_POST_ID = "id" ;
2037 private static final String KEY_POST_MEDIA_ID = "mediaId" ;
2138 private static final String KEY_POST_USER_ID_FK = "userId" ;
2239 private static final String KEY_POST_IMAGE_ID_FK = "imageId" ;
@@ -26,15 +43,18 @@ public class InstagramClientDatabase {
2643 private static final String KEY_POST_CREATED_TIME = "createdTime" ;
2744
2845 // Users table columns
46+ private static final String KEY_USER_ID = "id" ;
2947 private static final String KEY_USER_NAME = "userName" ;
3048 private static final String KEY_USER_PROFILE_PICTURE_URL = "profilePictureUrl" ;
3149
3250 // Images table columns
51+ private static final String KEY_IMAGE_ID = "id" ;
3352 private static final String KEY_IMAGE_URL = "imageUrl" ;
3453 private static final String KEY_IMAGE_HEIGHT = "imageHeight" ;
3554 private static final String KEY_IMAGE_WIDTH = "imageWidth" ;
3655
3756 // Comments table columns
57+ private static final String KEY_COMMENT_ID = "id" ;
3858 private static final String KEY_COMMENT_USER_ID_FK = "userId" ;
3959 private static final String KEY_COMMENT_TEXT = "text" ;
4060 private static final String KEY_COMMENT_CREATED_TIME = "createdTime" ;
@@ -43,6 +63,284 @@ public class InstagramClientDatabase {
4363 private static final String KEY_POST_COMMENT_POST_ID_FK = "postId" ;
4464 private static final String KEY_POST_COMMENT_COMMENT_ID_FK = "commentId" ;
4565
66+ // Singleton instance
67+ private static InstagramClientDatabase sInstance ;
68+
69+ /**
70+ * Constructor should be private to prevent direct instantiation.
71+ * make call to static method "getInstance()" instead.
72+ */
73+ private InstagramClientDatabase (Context context ) {
74+ super (context , DATABASE_NAME , null , DATABASE_VERSION );
75+ }
76+
77+ public static synchronized InstagramClientDatabase getInstance (Context context ) {
78+ if (sInstance == null ) {
79+ // Use the application context, which will ensure that you
80+ // don't accidentally leak an Activity's context.
81+ // See this article for more information: http://bit.ly/6LRzfx
82+ sInstance = new InstagramClientDatabase (context .getApplicationContext ());
83+ }
84+
85+ return sInstance ;
86+ }
87+
88+ @ Override
89+ public void onConfigure (SQLiteDatabase db ) {
90+ super .onConfigure (db );
91+ db .setForeignKeyConstraintsEnabled (true );
92+ }
93+
94+ @ Override
95+ public void onCreate (SQLiteDatabase db ) {
96+ String CREATE_POSTS_TABLE = "CREATE TABLE " + TABLE_POSTS +
97+ "(" +
98+ KEY_POST_ID + " INTEGER PRIMARY KEY," +
99+ KEY_POST_MEDIA_ID + " TEXT," +
100+ KEY_POST_USER_ID_FK + " INTEGER REFERENCES " + TABLE_USERS + "," +
101+ KEY_POST_IMAGE_ID_FK + " INTEGER REFERENCES " + TABLE_IMAGES + "," +
102+ KEY_POST_CAPTION + " TEXT," +
103+ KEY_POST_LIKES_COUNT + " INTEGER," +
104+ KEY_POST_COMMENTS_COUNT + " INTEGER," +
105+ KEY_POST_CREATED_TIME + " INTEGER" +
106+ ")" ;
107+
108+ String CREATE_USERS_TABLE = "CREATE TABLE " + TABLE_USERS +
109+ "(" +
110+ KEY_USER_ID + " INTEGER PRIMARY KEY," +
111+ KEY_USER_NAME + " TEXT UNIQUE ON CONFLICT ROLLBACK," +
112+ KEY_USER_PROFILE_PICTURE_URL + " TEXT" +
113+ ")" ;
114+
115+ String CREATE_IMAGES_TABLE = "CREATE TABLE " + TABLE_IMAGES +
116+ "(" +
117+ KEY_IMAGE_ID + " INTEGER PRIMARY KEY," +
118+ KEY_IMAGE_URL + " TEXT," +
119+ KEY_IMAGE_HEIGHT + " INTEGER," +
120+ KEY_IMAGE_WIDTH + " INTEGER" +
121+ ")" ;
122+
123+ String CREATE_COMMENTS_TABLE = "CREATE TABLE " + TABLE_COMMENTS +
124+ "(" +
125+ KEY_COMMENT_ID + " INTEGER PRIMARY KEY," +
126+ KEY_COMMENT_TEXT + " TEXT," +
127+ KEY_COMMENT_CREATED_TIME + " INTEGER," +
128+ KEY_COMMENT_USER_ID_FK + " INTEGER REFERENCES " + TABLE_USERS +
129+ ")" ;
130+
131+ String CREATE_POST_COMMENTS_TABLE = "CREATE TABLE " + TABLE_POST_COMMENTS +
132+ "(" +
133+ KEY_POST_COMMENT_POST_ID_FK + " INTEGER REFERENCES " + TABLE_POSTS + "," +
134+ KEY_POST_COMMENT_COMMENT_ID_FK + " INTEGER REFERENCES " + TABLE_COMMENTS + "," +
135+ "constraint " + CONSTRAINT_POST_COMMENTS_PK +
136+ " PRIMARY KEY(" +
137+ KEY_POST_COMMENT_POST_ID_FK + "," +
138+ KEY_POST_COMMENT_COMMENT_ID_FK +
139+ ")" +
140+ ")" ;
141+
142+ db .execSQL (CREATE_USERS_TABLE );
143+ db .execSQL (CREATE_IMAGES_TABLE );
144+ db .execSQL (CREATE_COMMENTS_TABLE );
145+ db .execSQL (CREATE_POSTS_TABLE );
146+ db .execSQL (CREATE_POST_COMMENTS_TABLE );
147+ }
148+
149+ @ Override
150+ public void onUpgrade (SQLiteDatabase db , int oldVersion , int newVersion ) {
151+ // TODO: Implement this method
152+ }
153+
154+ public void emptyAllTables () {
155+ // TODO: Implement this method to delete all rows from all tables
156+ }
157+
158+ public void addInstagramPosts (List <InstagramPost > posts ) {
159+ // TODO: Implement this method
160+ // Take a look at the helper methods addImage, addComment, etc as you implement this method
161+ // It's also a good idea to do this work in a transaction
162+ }
163+
164+ // Poor man's "upsert".
165+ // Since SQLite doesn't support "upsert" we need to fall back on an attempt to UPDATE (in case the
166+ // user already exists, followed by an INSERT (in case the user does not already exist).
167+ // Unfortunately, there is a bug with the insertOnConflict method
168+ // (https://code.google.com/p/android/issues/detail?id=13045) so we need to fall back to the more
169+ // verbose option of querying for the user's primary key if we did an update.
170+ private long addorUpdateUser (InstagramUser user ) {
171+ if (user == null ) {
172+ throw new IllegalArgumentException (String .format ("Attemping to add a null user to %s" , DATABASE_NAME ));
173+ }
174+
175+ SQLiteDatabase db = getWritableDatabase ();
176+
177+ ContentValues values = new ContentValues ();
178+ values .put (KEY_USER_NAME , user .userName );
179+ values .put (KEY_USER_PROFILE_PICTURE_URL , user .profilePictureUrl );
180+ long userId = -1 ;
181+
182+ // First try to update the user in case the user already exists in DB
183+ int rows = db .update (TABLE_USERS , values , KEY_USER_NAME + "= ?" , new String [] {user .userName });
184+
185+ // Check if update succeeded
186+ if (rows == 1 ) {
187+ // Get the primary key of the user we just updated
188+ String usersSelectQuery = String .format ("SELECT %s FROM %s WHERE %s = ?" ,
189+ KEY_USER_ID , TABLE_USERS , KEY_USER_NAME );
190+
191+ Cursor usersCursor = db .rawQuery (usersSelectQuery , new String []{String .valueOf (user .userName )});
192+ try {
193+ if (usersCursor .moveToFirst ()) {
194+ userId = usersCursor .getInt (0 );
195+ }
196+ // There should only be one user
197+ if (usersCursor .moveToNext ()) {
198+ Log .wtf (TAG , "Too many primary keys returned" );
199+ }
200+ } catch (Exception e ) {
201+ Log .wtf (TAG , "Error while trying to add or update user" );
202+ e .printStackTrace ();
203+ } finally {
204+ usersCursor .close ();
205+ }
206+ } else {
207+ // user with this userName did not already exist, so insert new user
208+ userId = db .insert (TABLE_USERS , null ,values );
209+ }
210+
211+ return userId ;
212+ }
213+
214+ private long addImage (InstagramImage image ) {
215+ if (image == null ) {
216+ throw new IllegalArgumentException (String .format ("Attemping to add a null image to %s" , DATABASE_NAME ));
217+ }
218+
219+ SQLiteDatabase db = getWritableDatabase ();
220+
221+ ContentValues values = new ContentValues ();
222+ values .put (KEY_IMAGE_URL , image .imageUrl );
223+ values .put (KEY_IMAGE_HEIGHT , image .imageHeight );
224+ values .put (KEY_IMAGE_WIDTH , image .imageHeight );
225+
226+ return db .insert (TABLE_IMAGES , null , values );
227+ }
228+
229+ private long addComment (InstagramComment comment , long postId ) {
230+ if (comment == null ) {
231+ throw new IllegalArgumentException (String .format ("Attemping to add a null comment to %s" , DATABASE_NAME ));
232+ }
233+ SQLiteDatabase db = getWritableDatabase ();
234+
235+ long commentUserId = addorUpdateUser (comment .user );
236+
237+ ContentValues values = new ContentValues ();
238+ values .put (KEY_COMMENT_TEXT , comment .text );
239+ values .put (KEY_COMMENT_USER_ID_FK , commentUserId );
240+ values .put (KEY_COMMENT_CREATED_TIME , comment .createdTime );
241+
242+ long commentId = db .insert (TABLE_COMMENTS , null , values );
243+ addPostCommentMapping (postId , commentId );
244+ return commentId ;
245+ }
246+
247+ private long addPostCommentMapping (long postId , long commentId ) {
248+
249+ SQLiteDatabase db = getWritableDatabase ();
250+
251+ ContentValues values = new ContentValues ();
252+ values .put (KEY_POST_COMMENT_COMMENT_ID_FK , commentId );
253+ values .put (KEY_POST_COMMENT_POST_ID_FK , postId );
254+
255+ return db .insert (TABLE_POST_COMMENTS , null , values );
256+ }
257+
258+ public List <InstagramPost > getAllInstagramPosts () {
259+ List <InstagramPost > posts = new ArrayList <>();
260+
261+ String LEFT_OUTER_JOIN_FORMAT_STRING = "LEFT OUTER JOIN %s ON %s.%s = %s.%s" ;
262+
263+ String userJoin = String .format (LEFT_OUTER_JOIN_FORMAT_STRING ,
264+ TABLE_USERS , TABLE_POSTS , KEY_POST_USER_ID_FK , TABLE_USERS , KEY_USER_ID );
265+
266+ String imageJoin = String .format (LEFT_OUTER_JOIN_FORMAT_STRING ,
267+ TABLE_IMAGES , TABLE_POSTS , KEY_POST_IMAGE_ID_FK , TABLE_IMAGES , KEY_IMAGE_ID );
268+
269+ String postsSelectQuery = "SELECT * FROM " + TABLE_POSTS + " " + userJoin + " " + imageJoin ;
270+
271+ String commentJoin = String .format (LEFT_OUTER_JOIN_FORMAT_STRING ,
272+ TABLE_COMMENTS , TABLE_POST_COMMENTS , KEY_POST_COMMENT_COMMENT_ID_FK , TABLE_COMMENTS , KEY_COMMENT_ID );
273+ String commentUserJoin = String .format (LEFT_OUTER_JOIN_FORMAT_STRING ,
274+ TABLE_USERS , TABLE_USERS , KEY_USER_ID , TABLE_COMMENTS , KEY_COMMENT_USER_ID_FK );
275+
276+ String commentsSelectQuery = String .format ("SELECT * FROM %s %s %s WHERE %s = ?" ,
277+ TABLE_POST_COMMENTS , commentJoin , commentUserJoin , KEY_POST_COMMENT_POST_ID_FK );
278+
279+ SQLiteDatabase db = this .getReadableDatabase ();
280+ Cursor postsCursor = db .rawQuery (postsSelectQuery , null );
281+
282+ try {
283+ if (postsCursor .moveToFirst ()) {
284+ do {
285+ InstagramPost post = new InstagramPost ();
286+ post .mediaId = postsCursor .getString (postsCursor .getColumnIndexOrThrow (KEY_POST_MEDIA_ID ));
287+ post .caption = postsCursor .getString (postsCursor .getColumnIndexOrThrow (KEY_POST_CAPTION ));
288+ post .likesCount = postsCursor .getInt (postsCursor .getColumnIndexOrThrow (KEY_POST_LIKES_COUNT ));
289+ post .commentsCount = postsCursor .getInt (postsCursor .getColumnIndexOrThrow (KEY_POST_COMMENTS_COUNT ));
290+ post .createdTime = postsCursor .getLong (postsCursor .getColumnIndexOrThrow (KEY_POST_CREATED_TIME ));
291+
292+ InstagramUser user = new InstagramUser ();
293+ user .userName = postsCursor .getString (postsCursor .getColumnIndexOrThrow (KEY_USER_NAME ));
294+ user .profilePictureUrl = postsCursor .getString (postsCursor .getColumnIndexOrThrow (KEY_USER_PROFILE_PICTURE_URL ));
295+ post .user = user ;
296+
297+ InstagramImage image = new InstagramImage ();
298+ image .imageUrl = postsCursor .getString (postsCursor .getColumnIndexOrThrow (KEY_IMAGE_URL ));
299+ image .imageHeight = postsCursor .getInt (postsCursor .getColumnIndexOrThrow (KEY_IMAGE_HEIGHT ));
300+ image .imageWidth = postsCursor .getInt (postsCursor .getColumnIndexOrThrow (KEY_IMAGE_WIDTH ));
301+ post .image = image ;
302+
303+ int key = postsCursor .getInt (0 );
304+
305+ // Get all comments for this post
306+ Cursor commentsCursor = db .rawQuery (commentsSelectQuery , new String []{String .valueOf (key )});
307+ try {
308+ if (commentsCursor .moveToFirst ()) {
309+ do {
310+ InstagramComment comment = new InstagramComment ();
311+ comment .text = commentsCursor .getString (commentsCursor .getColumnIndexOrThrow (KEY_COMMENT_TEXT ));
312+ comment .createdTime = commentsCursor .getLong (commentsCursor .getColumnIndexOrThrow (KEY_COMMENT_CREATED_TIME ));
313+
314+ InstagramUser commentUser = new InstagramUser ();
315+ commentUser .userName = commentsCursor .getString (commentsCursor .getColumnIndexOrThrow (KEY_USER_NAME ));
316+ commentUser .profilePictureUrl = commentsCursor .getString (commentsCursor .getColumnIndexOrThrow (KEY_USER_PROFILE_PICTURE_URL ));
317+ comment .user = commentUser ;
318+
319+ post .appendComment (comment );
320+ } while (commentsCursor .moveToNext ());
321+ }
322+ } catch (Exception e ) {
323+ Log .wtf (TAG , "Error while trying to get comments from database" );
324+ e .printStackTrace ();
325+ } finally {
326+ commentsCursor .close ();
327+ }
328+ posts .add (post );
329+ } while (postsCursor .moveToNext ());
330+ }
331+ } catch (Exception e ) {
332+ Log .wtf (TAG , "Error while trying to get posts from database" );
333+ e .printStackTrace ();
334+ } finally {
335+ closeCursor (postsCursor );
336+ }
337+
338+ return posts ;
339+ }
46340
47- // ... add code ...
341+ private void closeCursor (Cursor cursor ) {
342+ if (cursor != null && !cursor .isClosed ()) {
343+ cursor .close ();
344+ }
345+ }
48346}
0 commit comments