├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── dd │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── dd │ │ ├── MainActivity.java │ │ ├── UserListActivity.java │ │ ├── UserListActivity2.java │ │ ├── UserListActivity3.java │ │ ├── data │ │ ├── CursorItemProxy.java │ │ ├── LazyList.java │ │ ├── User.java │ │ └── UserProxy.java │ │ ├── model │ │ ├── Database.java │ │ ├── DatabaseConnection.java │ │ ├── DatabaseHelper.java │ │ ├── DatabaseTest.java │ │ ├── dao │ │ │ └── UserDAO.java │ │ └── utils │ │ │ ├── ArrayUtils.java │ │ │ ├── CursorParser.java │ │ │ ├── DatabaseParams.java │ │ │ └── DatabaseUtils.java │ │ └── utils │ │ └── L.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_main.xml │ └── values │ ├── queries.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | ### Android ### 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | 27 | ### Intellij ### 28 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 29 | 30 | ## Directory-based project format 31 | .idea/ 32 | # if you remove the above rule, at least ignore user-specific stuff: 33 | # .idea/workspace.xml 34 | # .idea/tasks.xml 35 | # and these sensitive or high-churn files: 36 | # .idea/dataSources.ids 37 | # .idea/dataSources.xml 38 | # .idea/sqlDataSources.xml 39 | # .idea/dynamic.xml 40 | 41 | ## File-based project format 42 | *.ipr 43 | *.iws 44 | *.iml 45 | 46 | ## Additional for IntelliJ 47 | out/ 48 | 49 | # generated by mpeltonen/sbt-idea plugin 50 | .idea_modules/ 51 | 52 | # generated by JIRA plugin 53 | atlassian-ide-plugin.xml 54 | 55 | # generated by Crashlytics plugin (for Android Studio and Intellij) 56 | com_crashlytics_export_strings.xml 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sample demonstrates lazy loading data from SQLite which means parsing data from `Cursor` on the fly when you need it (on-demand). 2 | 3 | For details see [appropriate articles](http://www.dmytrodanylyk.com/lazy-data-loading-from-sqlite). 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:1.1.1' 7 | } 8 | } 9 | apply plugin: 'com.android.application' 10 | 11 | repositories { 12 | jcenter() 13 | } 14 | 15 | android { 16 | compileSdkVersion 21 17 | buildToolsVersion "21.1.2" 18 | 19 | defaultConfig { 20 | applicationId "com.dd" 21 | minSdkVersion 14 22 | targetSdkVersion 21 23 | versionCode 1 24 | versionName "1.0" 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | compile fileTree(dir: 'libs', include: ['*.jar']) 36 | compile 'com.android.support:appcompat-v7:21.0.3' 37 | } 38 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:/Work/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/dd/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.dd; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.dd; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import com.dd.data.User; 7 | import com.dd.model.Database; 8 | import com.dd.model.DatabaseConnection; 9 | import com.dd.model.DatabaseHelper; 10 | import com.dd.model.dao.UserDAO; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class MainActivity extends Activity { 16 | 17 | private UserDAO mUserDAO; 18 | 19 | @Override 20 | public void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | DatabaseConnection.initializeInstance(new DatabaseHelper(this)); 25 | 26 | Database database = DatabaseConnection.instance().open(); 27 | mUserDAO = new UserDAO(database, getApplicationContext()); 28 | 29 | initView(); 30 | 31 | } 32 | 33 | @Override 34 | protected void onDestroy() { 35 | DatabaseConnection.instance().close(); 36 | super.onDestroy(); 37 | } 38 | 39 | private void initView() { 40 | findViewById(R.id.btnDeleteAllUsers).setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | deleteAllUsers(); 44 | } 45 | }); 46 | 47 | findViewById(R.id.btnInsertUsers).setOnClickListener(new View.OnClickListener() { 48 | @Override 49 | public void onClick(View v) { 50 | insertUserList(); 51 | } 52 | }); 53 | 54 | findViewById(R.id.btnStartListActivity).setOnClickListener(new View.OnClickListener() { 55 | @Override 56 | public void onClick(View v) { 57 | UserListActivity.start(MainActivity.this); 58 | } 59 | }); 60 | 61 | findViewById(R.id.btnStartListActivity2).setOnClickListener(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View v) { 64 | UserListActivity2.start(MainActivity.this); 65 | } 66 | }); 67 | 68 | findViewById(R.id.btnStartListActivity3).setOnClickListener(new View.OnClickListener() { 69 | @Override 70 | public void onClick(View v) { 71 | UserListActivity3.start(MainActivity.this); 72 | } 73 | }); 74 | } 75 | 76 | private void deleteAllUsers() { 77 | mUserDAO.deleteAll(); 78 | } 79 | 80 | public void insertUserList() { 81 | mUserDAO.insert(generateDummyUserList(10000)); 82 | } 83 | 84 | private List generateDummyUserList(int itemsCount) { 85 | List userList = new ArrayList<>(); 86 | for (int i = 0; i < itemsCount; i++) { 87 | User user = new User(); 88 | user.setAge(i); 89 | user.setName("Jon Doe " + i); 90 | user.setEmail("jondoe" + i + "@gmail.com"); 91 | userList.add(user); 92 | } 93 | return userList; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/UserListActivity.java: -------------------------------------------------------------------------------- 1 | package com.dd; 2 | 3 | import android.app.Activity; 4 | import android.app.ListActivity; 5 | import android.content.Intent; 6 | import android.database.Cursor; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.widget.ArrayAdapter; 10 | import com.dd.data.UserProxy; 11 | import com.dd.model.Database; 12 | import com.dd.model.DatabaseConnection; 13 | import com.dd.model.dao.UserDAO; 14 | 15 | import java.util.List; 16 | 17 | public class UserListActivity extends ListActivity { 18 | 19 | private ArrayAdapter mAdapter; 20 | private UserDAO mUserDAO; 21 | 22 | public static void start(@NonNull Activity activity) { 23 | activity.startActivity(new Intent(activity, UserListActivity.class)); 24 | } 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | 30 | Database database = DatabaseConnection.instance().open(); 31 | mUserDAO = new UserDAO(database, getApplicationContext()); 32 | 33 | List userProxies = mUserDAO.selectAllUserProxy(); 34 | mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, userProxies); 35 | setListAdapter(mAdapter); 36 | } 37 | 38 | @Override 39 | protected void onDestroy() { 40 | cleanUpDatabase(); 41 | super.onDestroy(); 42 | } 43 | 44 | private void cleanUpDatabase() { 45 | if(mAdapter != null && mAdapter.getCount() != 0) { 46 | Cursor cursor = mAdapter.getItem(0).getCursor(); 47 | mUserDAO.closeCursor(cursor); 48 | } 49 | 50 | DatabaseConnection.instance().close(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/UserListActivity2.java: -------------------------------------------------------------------------------- 1 | package com.dd; 2 | 3 | import android.app.Activity; 4 | import android.app.ListActivity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.os.SystemClock; 8 | import android.support.annotation.NonNull; 9 | import android.widget.ArrayAdapter; 10 | import com.dd.data.User; 11 | import com.dd.data.UserProxy; 12 | import com.dd.model.Database; 13 | import com.dd.model.DatabaseConnection; 14 | import com.dd.model.dao.UserDAO; 15 | 16 | import java.util.List; 17 | 18 | public class UserListActivity2 extends ListActivity { 19 | 20 | public static void start(@NonNull Activity activity) { 21 | activity.startActivity(new Intent(activity, UserListActivity2.class)); 22 | } 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | Database database = DatabaseConnection.instance().open(); 29 | final UserDAO userDAO = new UserDAO(database, getApplicationContext()); 30 | 31 | new Thread(new Runnable() { 32 | @Override 33 | public void run() { 34 | List userList = userDAO.selectAll(); 35 | deliverResultUI(userList); 36 | } 37 | }).start(); 38 | 39 | } 40 | 41 | private void deliverResultUI(final List userList) { 42 | runOnUiThread(new Runnable() { 43 | @Override 44 | public void run() { 45 | ArrayAdapter adapter = new ArrayAdapter<>(UserListActivity2.this, android.R.layout.simple_list_item_1, userList); 46 | setListAdapter(adapter); 47 | } 48 | }); 49 | } 50 | 51 | @Override 52 | protected void onDestroy() { 53 | DatabaseConnection.instance().close(); 54 | super.onDestroy(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/UserListActivity3.java: -------------------------------------------------------------------------------- 1 | package com.dd; 2 | 3 | import android.app.Activity; 4 | import android.app.ListActivity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.widget.ArrayAdapter; 9 | import com.dd.data.LazyList; 10 | import com.dd.data.User; 11 | import com.dd.model.Database; 12 | import com.dd.model.DatabaseConnection; 13 | import com.dd.model.dao.UserDAO; 14 | 15 | public class UserListActivity3 extends ListActivity { 16 | 17 | private ArrayAdapter mAdapter; 18 | private LazyList mUserLazyList; 19 | 20 | public static void start(@NonNull Activity activity) { 21 | activity.startActivity(new Intent(activity, UserListActivity.class)); 22 | } 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | Database database = DatabaseConnection.instance().open(); 29 | UserDAO mUserDAO = new UserDAO(database, getApplicationContext()); 30 | 31 | mUserLazyList = mUserDAO.selectAllLazy(); 32 | mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mUserLazyList); 33 | setListAdapter(mAdapter); 34 | } 35 | 36 | @Override 37 | protected void onDestroy() { 38 | cleanUpDatabase(); 39 | super.onDestroy(); 40 | } 41 | 42 | private void cleanUpDatabase() { 43 | if(mAdapter != null && mAdapter.getCount() != 0) { 44 | mUserLazyList.closeCursor(); 45 | } 46 | 47 | DatabaseConnection.instance().close(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/data/CursorItemProxy.java: -------------------------------------------------------------------------------- 1 | package com.dd.data; 2 | 3 | import android.database.Cursor; 4 | import android.support.annotation.NonNull; 5 | 6 | abstract class CursorItemProxy { 7 | 8 | private Cursor mCursor; 9 | private int mIndex; 10 | 11 | public CursorItemProxy(@NonNull Cursor cursor, int index) { 12 | mCursor = cursor; 13 | mIndex = index; 14 | } 15 | 16 | public Cursor getCursor() { 17 | return mCursor; 18 | } 19 | 20 | public int getIndex() { 21 | return mIndex; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/data/LazyList.java: -------------------------------------------------------------------------------- 1 | package com.dd.data; 2 | 3 | import android.database.Cursor; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class LazyList extends ArrayList { 8 | 9 | private final Cursor mCursor; 10 | private final ItemFactory mCreator; 11 | 12 | public LazyList(Cursor cursor, ItemFactory creator) { 13 | mCursor = cursor; 14 | mCreator = creator; 15 | } 16 | 17 | @Override 18 | public T get(int index) { 19 | int size = super.size(); 20 | if (index < size) { 21 | // find item in the collection 22 | T item = super.get(index); 23 | if (item == null) { 24 | item = mCreator.create(mCursor, index); 25 | set(index, item); 26 | } 27 | return item; 28 | } else { 29 | // we have to grow the collection 30 | for (int i = size; i < index; i++) { 31 | add(null); 32 | } 33 | // create last object, add and return 34 | T item = mCreator.create(mCursor, index); 35 | add(item); 36 | return item; 37 | } 38 | } 39 | 40 | @Override 41 | public int size() { 42 | return mCursor.getCount(); 43 | } 44 | 45 | public void closeCursor() { 46 | mCursor.close(); 47 | } 48 | 49 | public interface ItemFactory { 50 | T create(Cursor cursor, int index); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/data/User.java: -------------------------------------------------------------------------------- 1 | package com.dd.data; 2 | 3 | public class User { 4 | 5 | private long id; 6 | private int age; 7 | private String name; 8 | private String email; 9 | 10 | public long getId() { 11 | return id; 12 | } 13 | 14 | public void setId(long id) { 15 | this.id = id; 16 | } 17 | 18 | public int getAge() { 19 | return age; 20 | } 21 | 22 | public void setAge(int age) { 23 | this.age = age; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public String getEmail() { 35 | return email; 36 | } 37 | 38 | public void setEmail(String email) { 39 | this.email = email; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Name: " + getName(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/data/UserProxy.java: -------------------------------------------------------------------------------- 1 | package com.dd.data; 2 | 3 | import android.database.Cursor; 4 | import android.support.annotation.NonNull; 5 | 6 | public class UserProxy extends CursorItemProxy { 7 | 8 | private User mUser; 9 | 10 | public UserProxy(@NonNull Cursor cursor, int index) { 11 | super(cursor, index); 12 | mUser = new User(); 13 | } 14 | 15 | public String getName() { 16 | if (mUser.getName() == null) { 17 | Cursor cursor = getCursor(); 18 | cursor.moveToPosition(getIndex()); 19 | int columnIndex = cursor.getColumnIndex("name"); 20 | mUser.setName(cursor.getString(columnIndex)); 21 | } 22 | 23 | return mUser.getName(); 24 | } 25 | 26 | public String getEmail() { 27 | // TODO add parsing data from cursor 28 | return mUser.getEmail(); 29 | } 30 | 31 | public int getAge() { 32 | // TODO add parsing data from cursor 33 | return mUser.getAge(); 34 | } 35 | 36 | public long getId() { 37 | // TODO add parsing data from cursor 38 | return mUser.getId(); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "Name: " + getName(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/Database.java: -------------------------------------------------------------------------------- 1 | package com.dd.model; 2 | 3 | 4 | import android.database.Cursor; 5 | import android.database.SQLException; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import com.dd.model.utils.DatabaseParams; 8 | 9 | public class Database { 10 | 11 | private SQLiteDatabase mDatabase; 12 | 13 | public Database(SQLiteDatabase database) { 14 | mDatabase = database; 15 | } 16 | 17 | public int delete(DatabaseParams.Delete params) { 18 | return mDatabase.delete(params.table, params.whereClause, params.whereArgs); 19 | } 20 | 21 | public long insert(DatabaseParams.Insert params) { 22 | return mDatabase.insert(params.table, params.nullColumnHack, params.values); 23 | } 24 | 25 | public Cursor select(DatabaseParams.Select params) { 26 | return mDatabase.query(params.distinct, params.table, params.columns, params.selection, 27 | params.selectionArgs, params.groupBy, params.having, params.orderBy, params.limit); 28 | } 29 | 30 | public int update(SQLiteDatabase database, DatabaseParams.Update params) { 31 | return database.update(params.table, params.values, params.whereClause, params.whereArgs); 32 | } 33 | 34 | public void beginTransaction() { 35 | mDatabase.beginTransaction(); 36 | } 37 | 38 | public void endTransaction() { 39 | mDatabase.endTransaction(); 40 | } 41 | 42 | public void setTransactionSuccessful() { 43 | mDatabase.setTransactionSuccessful(); 44 | } 45 | 46 | public Cursor rawQuery(String sql, String[] selectionArgs) { 47 | return mDatabase.rawQuery(sql, selectionArgs); 48 | } 49 | 50 | public void execSQL(String sql, Object[] bindArgs) throws SQLException { 51 | mDatabase.execSQL(sql, bindArgs); 52 | } 53 | 54 | public void execSQL(String sql) throws SQLException { 55 | mDatabase.execSQL(sql); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/DatabaseConnection.java: -------------------------------------------------------------------------------- 1 | package com.dd.model; 2 | 3 | import android.database.sqlite.SQLiteOpenHelper; 4 | import com.dd.utils.L; 5 | 6 | public class DatabaseConnection { 7 | 8 | private int mOpenCounter; 9 | 10 | private static DatabaseConnection sInstance; 11 | private SQLiteOpenHelper mDatabaseHelper; 12 | private Database mDatabase; 13 | 14 | private DatabaseConnection(SQLiteOpenHelper helper) { 15 | mDatabaseHelper = helper; 16 | } 17 | 18 | public static synchronized void initializeInstance(SQLiteOpenHelper helper) { 19 | if (sInstance == null) { 20 | sInstance = new DatabaseConnection(helper); 21 | } 22 | } 23 | 24 | public static synchronized DatabaseConnection instance() { 25 | if (sInstance == null) { 26 | throw new IllegalStateException(DatabaseConnection.class.getSimpleName() + 27 | " is not initialized, call initializeInstance(..) method first."); 28 | } 29 | 30 | return sInstance; 31 | } 32 | 33 | public synchronized Database open() { 34 | if (mOpenCounter == 0) { 35 | // Opening new database 36 | mDatabase = new Database(mDatabaseHelper.getWritableDatabase()); 37 | } 38 | mOpenCounter++; 39 | L.d("Database open counter: " + mOpenCounter); 40 | return mDatabase; 41 | } 42 | 43 | public synchronized void close() { 44 | mOpenCounter--; 45 | if (mOpenCounter == 0) { 46 | // Closing database 47 | mDatabaseHelper.close(); 48 | 49 | } 50 | L.d("Database open counter: " + mOpenCounter); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/DatabaseHelper.java: -------------------------------------------------------------------------------- 1 | package com.dd.model; 2 | 3 | 4 | import android.content.Context; 5 | import android.database.sqlite.SQLiteDatabase; 6 | import android.database.sqlite.SQLiteOpenHelper; 7 | import com.dd.model.dao.UserDAO; 8 | 9 | public class DatabaseHelper extends SQLiteOpenHelper { 10 | 11 | public static final String DATABASE_NAME = "sample_database"; 12 | public static final int DATABASE_VERSION = 2; 13 | private Context mContext; 14 | 15 | public DatabaseHelper(Context context) { 16 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 17 | mContext = context; 18 | } 19 | 20 | @Override 21 | public void onCreate(SQLiteDatabase sqLiteDatabase) { 22 | // create all tables 23 | sqLiteDatabase.execSQL(UserDAO.getCreateTable(mContext)); 24 | } 25 | 26 | @Override 27 | public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { 28 | if (newVersion > oldVersion) { 29 | // drop all tables 30 | sqLiteDatabase.execSQL(UserDAO.getDropTable(mContext)); 31 | //re-create all tables 32 | onCreate(sqLiteDatabase); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/DatabaseTest.java: -------------------------------------------------------------------------------- 1 | package com.dd.model; 2 | 3 | import android.content.Context; 4 | import com.dd.utils.L; 5 | import com.dd.data.User; 6 | import com.dd.model.dao.UserDAO; 7 | import junit.framework.Assert; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.CountDownLatch; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | public class DatabaseTest { 15 | 16 | private Context mContext; 17 | 18 | public DatabaseTest(Context context) { 19 | 20 | } 21 | 22 | public Context getContext() { 23 | return mContext; 24 | } 25 | 26 | protected void setUp() throws Exception { 27 | 28 | L.LOG_TAG = "database"; 29 | DatabaseConnection.initializeInstance(new DatabaseHelper(getContext())); 30 | 31 | L.v("Deleting all users"); 32 | DatabaseConnection connection = DatabaseConnection.instance(); 33 | Database database = connection.open(); 34 | UserDAO dao = new UserDAO(database, getContext()); 35 | dao.deleteAll(); 36 | connection.close(); 37 | } 38 | 39 | public void testInsertUserList() { 40 | L.v("insertUserList"); 41 | 42 | DatabaseConnection connection = DatabaseConnection.instance(); 43 | Database database = connection.open(); 44 | 45 | UserDAO dao = new UserDAO(database, getContext()); 46 | dao.insert(generateDummyUserList(10)); 47 | 48 | List listFromDB = dao.selectAll(); 49 | Assert.assertTrue("User list is empty", !listFromDB.isEmpty()); 50 | Assert.assertTrue("User list size is wrong", listFromDB.size() == 10); 51 | 52 | connection.close(); 53 | } 54 | 55 | public void testInsertUser() { 56 | L.v("testInsertUser"); 57 | 58 | DatabaseConnection connection = DatabaseConnection.instance(); 59 | Database database = connection.open(); 60 | 61 | User user = new User(); 62 | user.setAge(100); 63 | user.setName("Jon Doe"); 64 | 65 | UserDAO dao = new UserDAO(database, getContext()); 66 | dao.insert(user); 67 | 68 | List listFromDB = dao.selectAll(); 69 | Assert.assertTrue("User list is empty", !listFromDB.isEmpty()); 70 | Assert.assertTrue("User list size is wrong", listFromDB.size() == 1); 71 | 72 | User userFromDB = listFromDB.get(0); 73 | 74 | Assert.assertTrue("Incorrect data", 75 | user.getName().contentEquals(userFromDB.getName())); 76 | Assert.assertTrue("Incorrect data", user.getAge() == userFromDB.getAge()); 77 | 78 | connection.close(); 79 | } 80 | 81 | public void testUpdateUser() { 82 | // L.v("testUpdateUser"); 83 | // 84 | // DatabaseConnection connection = DatabaseConnection.instance(); 85 | // Database database = connection.open(); 86 | // 87 | // UserDAO dao = new UserDAO(database, getContext()); 88 | // User user = new User(); 89 | // user.setAge(18); 90 | // user.setName("Jon Doe"); 91 | // 92 | // dao.insert(user); 93 | // 94 | // dao.updateNameByAge("Will Smith", 18); 95 | // 96 | // List listFromDB = dao.selectByAge(18); 97 | // Assert.assertTrue("User list is empty", !listFromDB.isEmpty()); 98 | // 99 | // User userFromDB = listFromDB.get(0); 100 | // 101 | // Assert.assertTrue("User is null", userFromDB != null); 102 | // Assert.assertTrue("User age is wrong", userFromDB.getAge() == 18); 103 | // Assert.assertTrue("User name is wrong", userFromDB.getName().equals("Will Smith")); 104 | // 105 | // connection.close(); 106 | } 107 | 108 | private List generateDummyUserList(int itemsCount) { 109 | List userList = new ArrayList(); 110 | for (int i = 0; i < itemsCount; i++) { 111 | User user = new User(); 112 | user.setAge(i); 113 | user.setName("Jon Doe"); 114 | userList.add(user); 115 | } 116 | return userList; 117 | } 118 | 119 | private int totalTasks = 100; 120 | private AtomicInteger tasksAlive = new AtomicInteger(totalTasks); 121 | 122 | public void testConcurrentAccess() { 123 | L.v("testConcurrentAccess"); 124 | 125 | CountDownLatch signal = new CountDownLatch(1); 126 | 127 | for (int i = 0; i < totalTasks; i++) { 128 | spamNewThread(signal); 129 | } 130 | 131 | try { 132 | signal.await(); 133 | } catch (InterruptedException e) { 134 | L.d(e.getMessage(), e); 135 | } 136 | } 137 | 138 | private void spamNewThread(final CountDownLatch signal) { 139 | new Thread(new Runnable() { 140 | @Override 141 | public void run() { 142 | DatabaseConnection connection = DatabaseConnection.instance(); 143 | Database database = connection.open(); 144 | 145 | UserDAO dao = new UserDAO(database, getContext()); 146 | int usersCount = 10; 147 | dao.insert(generateDummyUserList(usersCount)); 148 | 149 | L.v("Task #" + tasksAlive.get() + " is finished"); 150 | 151 | boolean allTasksFinished = tasksAlive.decrementAndGet() == 0; 152 | if (allTasksFinished) { 153 | int totalUsers = usersCount * totalTasks; 154 | List listFromDB = dao.selectAll(); 155 | Assert.assertTrue("User list is empty", !listFromDB.isEmpty()); 156 | Assert.assertTrue("User list size is wrong", listFromDB.size() == totalUsers); 157 | signal.countDown(); 158 | } 159 | 160 | connection.close(); 161 | } 162 | }).start(); 163 | } 164 | 165 | } -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/dao/UserDAO.java: -------------------------------------------------------------------------------- 1 | package com.dd.model.dao; 2 | 3 | 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import com.dd.R; 7 | import com.dd.data.LazyList; 8 | import com.dd.data.User; 9 | import com.dd.data.UserProxy; 10 | import com.dd.model.Database; 11 | import com.dd.model.utils.ArrayUtils; 12 | import com.dd.model.utils.CursorParser; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class UserDAO { 18 | 19 | private Database mDatabase; 20 | private Context mContext; 21 | 22 | public UserDAO(Database database, Context context) { 23 | mDatabase = database; 24 | mContext = context; 25 | } 26 | 27 | public static String getCreateTable(Context context) { 28 | return context.getString(R.string.create_table_user); 29 | } 30 | 31 | public static String getDropTable(Context context) { 32 | return context.getString(R.string.drop_table_users); 33 | } 34 | 35 | public void deleteAll() { 36 | mDatabase.execSQL(mContext.getString(R.string.delete_all_users)); 37 | } 38 | 39 | public void insert(List userList) { 40 | mDatabase.beginTransaction(); 41 | String sql = mContext.getString(R.string.insert_user); 42 | String[] bindArgs; 43 | for (User user : userList) { 44 | bindArgs = ArrayUtils.build(user.getAge(), user.getName(), user.getEmail()); 45 | mDatabase.execSQL(sql, bindArgs); 46 | } 47 | mDatabase.setTransactionSuccessful(); 48 | mDatabase.endTransaction(); 49 | } 50 | 51 | public void insert(User user) { 52 | String[] bindArgs = ArrayUtils.build(user.getName(), user.getAge()); 53 | mDatabase.execSQL(mContext.getString(R.string.insert_user), bindArgs); 54 | } 55 | 56 | public List selectAll() { 57 | Cursor cursor = mDatabase.rawQuery(mContext.getString(R.string.select_all_users), null); 58 | List dataList = manageCursor(cursor); 59 | closeCursor(cursor); 60 | 61 | return dataList; 62 | } 63 | 64 | public List selectAllUserProxy() { 65 | Cursor cursor = mDatabase.rawQuery(mContext.getString(R.string.select_all_users), null); 66 | return manageProxyCursor(cursor); 67 | } 68 | 69 | public LazyList selectAllLazy() { 70 | Cursor cursor = mDatabase.rawQuery(mContext.getString(R.string.select_all_users), null); 71 | return new LazyList<>(cursor, new LazyList.ItemFactory() { 72 | @Override 73 | public User create(Cursor cursor, int index) { 74 | User user = new User(); 75 | cursor.moveToPosition(index); 76 | int columnIndex = cursor.getColumnIndex("name"); 77 | user.setName(cursor.getString(columnIndex)); 78 | // TODO add parsing data from cursor 79 | return user; 80 | } 81 | }); 82 | } 83 | 84 | public void closeCursor(Cursor cursor) { 85 | if (cursor != null) { 86 | cursor.close(); 87 | } 88 | } 89 | 90 | protected User cursorToData(Cursor cursor) { 91 | CursorParser parser = new CursorParser(cursor); 92 | 93 | User user = new User(); 94 | user.setId(parser.readLong()); 95 | user.setAge(parser.readInt()); 96 | user.setName(parser.readString()); 97 | user.setEmail(parser.readString()); 98 | 99 | return user; 100 | } 101 | 102 | protected List manageProxyCursor(Cursor cursor) { 103 | List dataList = new ArrayList<>(); 104 | 105 | if (cursor != null) { 106 | cursor.moveToFirst(); 107 | while (!cursor.isAfterLast()) { 108 | dataList.add(new UserProxy(cursor, cursor.getPosition())); 109 | cursor.moveToNext(); 110 | } 111 | } 112 | 113 | if(dataList.isEmpty()) { 114 | closeCursor(cursor); 115 | } 116 | 117 | return dataList; 118 | } 119 | 120 | protected List manageCursor(Cursor cursor) { 121 | List dataList = new ArrayList<>(); 122 | 123 | if (cursor != null) { 124 | cursor.moveToFirst(); 125 | while (!cursor.isAfterLast()) { 126 | User user = cursorToData(cursor); 127 | dataList.add(user); 128 | cursor.moveToNext(); 129 | } 130 | } 131 | return dataList; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/utils/ArrayUtils.java: -------------------------------------------------------------------------------- 1 | package com.dd.model.utils; 2 | 3 | public class ArrayUtils { 4 | 5 | public static String[] build(Object... values) { 6 | String[] arr = new String[values.length]; 7 | for (int i = 0; i < values.length; i++) { 8 | if (values[i] instanceof Boolean) { 9 | boolean value = (Boolean) values[i]; 10 | arr[i] = String.valueOf(value ? 1 : 0); 11 | } else { 12 | arr[i] = String.valueOf(values[i]); 13 | } 14 | } 15 | return arr; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/utils/CursorParser.java: -------------------------------------------------------------------------------- 1 | package com.dd.model.utils; 2 | 3 | import android.database.Cursor; 4 | 5 | public class CursorParser { 6 | 7 | private Cursor mCursor; 8 | private int mIndex; 9 | 10 | public CursorParser(Cursor cursor) { 11 | mCursor = cursor; 12 | mIndex = -1; 13 | } 14 | 15 | public long readLong() { 16 | mIndex++; 17 | return mCursor.getLong(mIndex); 18 | } 19 | 20 | public int readInt() { 21 | mIndex++; 22 | return mCursor.getInt(mIndex); 23 | } 24 | 25 | public double readDouble() { 26 | mIndex++; 27 | return mCursor.getDouble(mIndex); 28 | } 29 | 30 | public float readFloat() { 31 | mIndex++; 32 | return mCursor.getFloat(mIndex); 33 | } 34 | 35 | public String readString() { 36 | mIndex++; 37 | return mCursor.getString(mIndex); 38 | } 39 | 40 | public boolean readBoolean() { 41 | mIndex++; 42 | return mCursor.getInt(mIndex) != 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/utils/DatabaseParams.java: -------------------------------------------------------------------------------- 1 | package com.dd.model.utils; 2 | 3 | import android.content.ContentValues; 4 | 5 | public class DatabaseParams { 6 | 7 | public static class Insert { 8 | 9 | public String table; 10 | public String nullColumnHack; 11 | public ContentValues values; 12 | } 13 | 14 | public static class Delete { 15 | 16 | public String table; 17 | public String whereClause; 18 | public String[] whereArgs; 19 | } 20 | 21 | public static class Select { 22 | 23 | public boolean distinct; 24 | public String table; 25 | public String[] columns; 26 | public String selection; 27 | public String[] selectionArgs; 28 | public String groupBy; 29 | public String having; 30 | public String orderBy; 31 | public String limit; 32 | } 33 | 34 | public static class Update { 35 | 36 | public String table; 37 | public ContentValues values; 38 | public String whereClause; 39 | public String[] whereArgs; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/model/utils/DatabaseUtils.java: -------------------------------------------------------------------------------- 1 | package com.dd.model.utils; 2 | 3 | import android.database.Cursor; 4 | import android.database.sqlite.SQLiteDatabase; 5 | 6 | public class DatabaseUtils { 7 | 8 | public int delete(SQLiteDatabase database, DatabaseParams.Delete params) { 9 | return database.delete(params.table, params.whereClause, params.whereArgs); 10 | } 11 | 12 | public long insert(SQLiteDatabase database, DatabaseParams.Insert params) { 13 | return database.insert(params.table, params.nullColumnHack, params.values); 14 | } 15 | 16 | public Cursor select(SQLiteDatabase database, DatabaseParams.Select params) { 17 | return database.query(params.distinct, params.table, params.columns, params.selection, 18 | params.selectionArgs, params.groupBy, params.having, params.orderBy, params.limit); 19 | } 20 | 21 | public int update(SQLiteDatabase database, DatabaseParams.Update params) { 22 | return database.update(params.table, params.values, params.whereClause, params.whereArgs); 23 | } 24 | 25 | public Cursor rawQuery(SQLiteDatabase database, String sql, String[] selectionArgs) { 26 | return database.rawQuery(sql, selectionArgs); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/dd/utils/L.java: -------------------------------------------------------------------------------- 1 | package com.dd.utils; 2 | 3 | 4 | import android.util.Log; 5 | import com.dd.BuildConfig; 6 | 7 | /** 8 | * Helper methods that make logging more consistent throughout the app. 9 | * To change global log tag: L.LOG_TAG = "My Application"; 10 | * @author Dmytro Danylyk 11 | * @version 1.0 12 | */ 13 | @SuppressWarnings("UnusedDeclaration") 14 | public final class L { 15 | 16 | public static String LOG_TAG = "Undefined"; 17 | 18 | /** 19 | *

ERROR: This level of logging should be used when something fatal has happened, i.e. something that 20 | * will have user-visible consequences and won't be recoverable without explicitly deleting some data, uninstalling 21 | * applications, wiping the data partitions or reflashing the entire phone (or worse). Issues that justify some 22 | * logging at the ERROR level are typically good candidates to be reported to a statistics-gathering server.

23 | * 24 | *

This level is always logged.

25 | */ 26 | public static void e(String message, Throwable cause) { 27 | Log.e(LOG_TAG, "[" + message + "]", cause); 28 | } 29 | 30 | /** 31 | *@see #e(String, Throwable) 32 | */ 33 | public static void e(String msg) { 34 | Throwable t = new Throwable(); 35 | StackTraceElement[] elements = t.getStackTrace(); 36 | 37 | String callerClassName = elements[1].getFileName(); 38 | Log.e(LOG_TAG, "[" + callerClassName + "] " + msg); 39 | } 40 | 41 | /** 42 | *

WARNING: This level of logging should used when something serious and unexpected happened, i.e. 43 | * something that will have user-visible consequences but is likely to be recoverable without data loss by 44 | * performing some explicit action, ranging from waiting or restarting an app all the way to re-downloading a 45 | * new version of an application or rebooting the device. Issues that justify some logging at the WARNING 46 | * level might also be considered for reporting to a statistics-gathering server.

47 | * 48 | *

This level is always logged.

49 | */ 50 | public static void w(String message, Throwable cause) { 51 | Log.w(LOG_TAG, "[" + message + "]", cause); 52 | } 53 | 54 | /** 55 | *@see #w(String, Throwable) 56 | */ 57 | public static void w(String msg) { 58 | Throwable t = new Throwable(); 59 | StackTraceElement[] elements = t.getStackTrace(); 60 | 61 | String callerClassName = elements[1].getFileName(); 62 | Log.w(LOG_TAG, "[" + callerClassName + "] " + msg); 63 | } 64 | 65 | /** 66 | *

INFORMATIVE: This level of logging should used be to note that something interesting to most 67 | * people happened, i.e. when a situation is detected that is likely to have widespread impact, though isn't 68 | * necessarily an error. Such a condition should only be logged by a module that reasonably believes that it 69 | * is the most authoritative in that domain (to avoid duplicate logging by non-authoritative components).

70 | * 71 | *

This level is always logged.

72 | */ 73 | public static void i(String message, Throwable cause) { 74 | Log.i(LOG_TAG, "[" + message + "]", cause); 75 | } 76 | 77 | /** 78 | *@see #i(String, Throwable) 79 | */ 80 | public static void i(String msg) { 81 | Throwable t = new Throwable(); 82 | StackTraceElement[] elements = t.getStackTrace(); 83 | 84 | String callerClassName = elements[1].getFileName(); 85 | Log.i(LOG_TAG, "[" + callerClassName + "] " + msg); 86 | } 87 | 88 | /** 89 | *

DEBUG: This level of logging should be used to further note what is happening on the device that 90 | * could be relevant to investigate and debug unexpected behaviors. You should log only what is needed to gather 91 | * enough information about what is going on about your component. If your debug logs are dominating the log then 92 | * you probably should be using verbose logging.

93 | * 94 | *

This level is NOT logged in release build.

95 | */ 96 | public static void d(String msg, Throwable cause) { 97 | if (BuildConfig.DEBUG) { 98 | Log.d(LOG_TAG, msg, cause); 99 | } 100 | } 101 | 102 | /** 103 | *@see #d(String, Throwable) 104 | */ 105 | public static void d(String msg) { 106 | if (BuildConfig.DEBUG) { 107 | Throwable t = new Throwable(); 108 | StackTraceElement[] elements = t.getStackTrace(); 109 | 110 | String callerClassName = elements[1].getFileName(); 111 | Log.d(LOG_TAG, "[" + callerClassName + "] " + msg); 112 | } 113 | } 114 | 115 | /** 116 | *

VERBOSE: This level of logging should be used for everything else.

117 | * 118 | *

This level is NOT logged in release build.

119 | */ 120 | public static void v(String msg, Throwable cause) { 121 | if (BuildConfig.DEBUG) { 122 | Log.v(LOG_TAG, msg, cause); 123 | } 124 | } 125 | 126 | /** 127 | *@see #v(String, Throwable) 128 | */ 129 | public static void v(String msg) { 130 | if (BuildConfig.DEBUG) { 131 | Throwable t = new Throwable(); 132 | StackTraceElement[] elements = t.getStackTrace(); 133 | 134 | String callerClassName = elements[1].getFileName(); 135 | Log.v(LOG_TAG, "[" + callerClassName + "] " + msg); 136 | } 137 | } 138 | 139 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmytrodanylyk/lazy-data-loading/1e0766c394f69c68718da18f985c61898b5bd5c2/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmytrodanylyk/lazy-data-loading/1e0766c394f69c68718da18f985c61898b5bd5c2/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmytrodanylyk/lazy-data-loading/1e0766c394f69c68718da18f985c61898b5bd5c2/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmytrodanylyk/lazy-data-loading/1e0766c394f69c68718da18f985c61898b5bd5c2/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 |