How can I find out the size occupied by Room databases by looking at the app settings? The storage section of the app settings is divided into Total, App size, User data and Cache. Does the app database account for the User data section or is it the Cache? I want to find an estimate of how big my database is so I can find the maximum number of rows I can keep in the db without it taking too much space.
I want to find an estimate of how big my database is so I can find the maximum number of rows I can keep in the db without it taking too much space.
The number of rows doesn't exactly equate to database size. That is because the data is stored in pages (by default 4k). A table with 0 rows will take up 4k, with 1000 rows it could still just take up 4k.
Each SQLite entity (table, Index, Trigger etc) will take up at least 1 page.
You may wish to read SQLite Database File Format
Ignoring the page factor you could add a method to the #Database class like :-
public static long getDBSize(Context context) {
return (context.getDatabasePath(instance.getOpenHelper().getDatabaseName())).length()
// Add the shared memory (WAL index) file size
+ (context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-shm")).length()
// Add the WAL file size
+ (context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-wal")).length()
// Add the journal file size
+ (context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-journal")).length();
}
Working Example (based upon an existing App used for answering some qustions)
The #Database class :-
Retrieving the combined filesizes :-
#Database(entities = {State.class,Location.class,School.class,TimerDesign.class,IntervalDesign.class,Table1.class, MessageItem.class, Contact.class},exportSchema = false,version = 1)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
private static volatile TheDatabase instance;
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context,
TheDatabase.class,
"state.db"
)
.allowMainThreadQueries()
.addCallback(new Callback() {
#Override
public void onCreate(SupportSQLiteDatabase db) {
super.onCreate(db);
}
#Override
public void onOpen(SupportSQLiteDatabase db) {
super.onOpen(db);
}
})
.build();
}
return instance;
}
public static long getDBSize(Context context) {
// For Demonstration Log the individual sizes
Log.d("DBSIZEINFO",
"Space from main DB file = " + String.valueOf(context.getDatabasePath(instance.getOpenHelper().getDatabaseName()).length())
+ "\nSpace from -shm file = " + String.valueOf(context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-shm").length())
+ "\nSpace from -wal file = " + String.valueOf(context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-wal").length())
+ "\nSpace from journal file = " + String.valueOf(context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-journal").length())
);
return (context.getDatabasePath(instance.getOpenHelper().getDatabaseName())).length()
// Add the shared memory (WAL index) file size
+ (context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-shm")).length()
// Add the WAL file size
+ (context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-wal")).length()
// Add the journal file size
+ (context.getDatabasePath(instance.getOpenHelper().getDatabaseName() + "-journal")).length();
}
}
Note individual file sizes added for demo/explanation
and invoking code :-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Instantiate Database and get dao
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
Log.d("DBSIZEINFO","Database Size is " + TheDatabase.getDBSize(this));
}
}
Result (i.e. the log includes) :-
2021-06-22 09:20:53.960 25867-25867/a.a.so67952337javaroomstatefacilities D/DBSIZEINFO: Space from main DB file = 4096
Space from -shm file = 32768
Space from -wal file = 70072
Space from journal file = 0
2021-06-22 09:20:53.960 25867-25867/a.a.so67952337javaroomstatefacilities D/DBSIZEINFO: Database Size is 106936
Device Explorer shows :-
Explanation of the Results
As can be seen the result is showing that the database file itself is small in comparison to the -wal and -shm files (it is clear that WAL mode is in effect as both are greater than 0). This is because effectively the database consists of the -wal (i.e changes waiting to be applied to the database) and the database file. The -shm file will not be applied it is a work file used for the -wal file.
the -wal file is applied (may be partly) when CHECKPOINTS are made.
That is in WAL mode changes are written to the -wal file (roll back is removing part of the -wal file).
If journal mode were in effect then the journal is the log that is used to undo changes made to the database file.
Does the app database account for the User data section or is it the Cache?
Although you may wish to read:-
SQLite Write-Ahead Logging
SQLite Temporary Files Used By SQLite
Additional
If you wanted to not have to pass the context when checking the size(s) then you could use something based upon :-
#Database(entities = {State.class,Location.class,School.class,TimerDesign.class,IntervalDesign.class,Table1.class, MessageItem.class, Contact.class},exportSchema = false,version = 1)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
private static volatile TheDatabase instance;
private static File databaseFile; //<<<<<<<<<< ADDED
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context,
TheDatabase.class,
"state.db"
)
.allowMainThreadQueries()
.addCallback(new Callback() {
#Override
public void onCreate(SupportSQLiteDatabase db) {
super.onCreate(db);
}
#Override
public void onOpen(SupportSQLiteDatabase db) {
super.onOpen(db);
}
})
.build();
}
databaseFile = context.getDatabasePath(instance.getOpenHelper().getDatabaseName()); //<<<<<<<<<< ADDED
return instance;
}
/* ALTERNATIVE without the need for the Context*/
public static long getDBSize() {
if (databaseFile == null) return -1;
return databaseFile.length() +
new File(databaseFile.getPath() + "-shm").length() +
new File(databaseFile.getPath() + "-wal").length() +
new File(databaseFile.getPath() + "-journal").length();
}
}
Related
The app I'm building currently has a Workout database.
Workout DB has a Workout table and a WoroutSets table.
The data in these two tables is inserted(saved) through user input.
And that's where some data is stored.
By the way, I want to put pre-populated data called WorkoutList into this Workout DB.
I consulted the docs for this.
I exported Workout.db and pre-populated it with data in DB Browser for SQLite.
And we are going to use createFromAsset("Workout.db") as per the docs. (Haven't tried yet)
However, what I am concerned about is whether there is a conflict between the Work DB of the existing app and the Workout DB to which the WorkoutList table has been added.
Assuming that you want to preserve each app users workouts and workoutsetss that they have input then you would not want to overwrite them by using createFromAsset.
Rather I suspect that what you want to do is introduce a new workoutlist table that is populated with predefined/pre-existing rows in the workoutlist as per a database supplied as an asset. In this case you do not want to use the createFromAsset method (although you could potentially have a second database created from the asset, attach it to the original and then merge the data - this would be more complex than it need be).
You also have to consider how to handle new installs, in which case there will be no existing user input workouts and workoutsetss, in which case you could use createFromAsset method. However, you would not want any other user's workouts and workoutsetss rows.
Based upon this assumption perhaps consider this demo that introduces a new table (workoutlist) whose data is retrieved from an asset maintaining the original user data in the other tables (workout and workoutset) but for a new install of the App creates database from the asset.
the schema is made up so will very likely differ from yours but the principle applies.
Java has been used but it would take little to change it to Kotlin
Workout
#Entity
class Workout {
#PrimaryKey
Long workoutId=null;
String workoutName;
Workout(){};
#Ignore
Workout(String workoutName) {
this.workoutId=null;
this.workoutName=workoutName;
}
}
WorkoutSet (plural not used but easily changed)
#Entity
class WorkoutSet {
#PrimaryKey
Long workoutSetId=null;
String workoutSetName;
long workoutIdMap;
WorkoutSet(){}
#Ignore
WorkoutSet(String workoutSetName, long parentWorkout) {
this.workoutSetId=null;
this.workoutSetName = workoutSetName;
this.workoutIdMap = parentWorkout;
}
}
WorkkoutList (the new table)
#Entity
class WorkoutList {
#PrimaryKey
Long workoutListId=null;
String workoutListName;
}
AllDAO (just for completeness)
#Dao
abstract class AllDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Workout workout);
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(WorkoutSet workoutSet);
#Query("SELECT count(*) FROM workout")
abstract long getNumberOfWorkouts();
}
WorkoutDatabase the #Database annotated class
#Database(entities = {Workout.class,WorkoutSet.class, WorkoutList.class /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
abstract class WorkoutDatabase extends RoomDatabase {
abstract AllDAO getAllDAO();
private static Context passed_context;
private static volatile WorkoutDatabase INSTANCE;
static WorkoutDatabase getInstance(Context context) {
passed_context = context;
if (INSTANCE==null && MainActivity.DATABASE_VERSION == 1) {
INSTANCE = Room.databaseBuilder(context,WorkoutDatabase.class,MainActivity.DATABASE_NAME)
.allowMainThreadQueries()
.build();
}
if (INSTANCE ==null && MainActivity.DATABASE_VERSION > 1) {
INSTANCE = Room.databaseBuilder(context,WorkoutDatabase.class,MainActivity.DATABASE_NAME)
.allowMainThreadQueries()
.createFromAsset(MainActivity.DATABASE_ASSET_NAME) /* so new App installs use asset */
.addMigrations(MIGRATION_1_TO_2) /* to handle migration */
.build();
}
return INSTANCE;
}
static Migration MIGRATION_1_TO_2 = new Migration(1,2) {
#SuppressLint("Range")
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
/* Create the new table */
database.execSQL("CREATE TABLE IF NOT EXISTS `WorkoutList` (`workoutListId` INTEGER, `workoutListName` TEXT, PRIMARY KEY(`workoutListId`))");
/* Cater for copying the data from the asset */
String tempDBName = "temp_" + MainActivity.DATABASE_NAME; /* name of the temporary/working database NOT an SQLITE TEMP database */
String newTableName = "workoutlist"; /* The table name */
String qualifiedNewTableName = tempDBName + "." + newTableName; /* The fully qualified new table name for the attached temp/wrk db */
String tempDBPath = passed_context.getDatabasePath(MainActivity.DATABASE_NAME).getParent() + File.separator + tempDBName; /* path to temp/wrk db */
try {
/* Copy the asset to a second DB */
InputStream asset = passed_context.getAssets().open(MainActivity.DATABASE_ASSET_NAME); /* open the asset */
File tempDB_File = new File(tempDBPath); /* File for temp/wrk database */
OutputStream tempdb = new FileOutputStream(tempDB_File); /* now an output stream ready for the copy */
int bufferLength = 1024 * 8; /* length of buffer set to 8k */
byte[] buffer = new byte[bufferLength]; /* the buffer for the copy */
/* copy the temp/wrk database from the asset to it's location */
while(asset.read(buffer) > 0) {
tempdb.write(buffer);
}
/* clean up after copy */
tempdb.flush();
tempdb.close();
asset.close();
/*Use the temporary/working database to populate the actual database */
/* Issues with WAL file change because migration is called within a transaction as per */
/* java.lang.IllegalStateException: Write Ahead Logging (WAL) mode cannot be enabled or disabled while there are transactions in progress. .... */
/* SO COMMENTED OUT */
//database.execSQL("ATTACH DATABASE '" + tempDBPath + "' AS " + tempDBName);
//database.execSQL("INSERT INTO " + newTableName + " SELECT * FROM " + qualifiedNewTableName);
//database.execSQL("DETACH " + tempDBName);
/* Alternative to ATTACH */
SQLiteDatabase assetdb = SQLiteDatabase.openDatabase(tempDB_File.getPath(),null,SQLiteDatabase.OPEN_READONLY);
Cursor csr = assetdb.query(newTableName,null,null,null,null,null,null);
ContentValues cv = new ContentValues();
while (csr.moveToNext()) {
cv.clear();
for (String s: csr.getColumnNames()) {
cv.put(s,csr.getString(csr.getColumnIndex(s)));
}
database.insert(newTableName,SQLiteDatabase.CONFLICT_IGNORE,cv);
}
assetdb.close();
tempDB_File.delete(); /* delete the temporary/working copy of the asset */
} catch (Exception e) {
/* handle issues here e.g. no asset, unable to read/write an so on */
e.printStackTrace();
}
}
};
}
This has been written to allow easy switching/running of the App with either version, simply two changes to run as old or new version of the App.
to run as version 1 DATABASE_VERSION (in MainActivity) is set to 1
AND the WorkoutSet class is commented out in the #Database annotation.
the Mirgration handles the copy of the data from the asset if the database already exists, otherwise for a new file then createFromAssets is used to copy the database from the asset.
MainActivity the activity code that does something with the database to ensure that it is opened/accessed
public class MainActivity extends AppCompatActivity {
public static final String DATABASE_NAME = "workout.db";
public static final int DATABASE_VERSION = 2;
public static final String DATABASE_ASSET_NAME = "testit.db"/*DATABASE_NAME*/ /* could be different */;
WorkoutDatabase wdb;
AllDAO dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
wdb = WorkoutDatabase.getInstance(this);
dao = wdb.getAllDAO();
if (dao.getNumberOfWorkouts() < 1) addInitialData();
//wdb.close(); // FORCE close so WAL is full checkpointed (i.e. no -wal or -shm)
}
private void addInitialData() {
String prefix = String.valueOf(System.currentTimeMillis()); // to differentiate devices
dao.insert(new Workout(prefix + "-W001"));
dao.insert(new Workout(prefix + "-W002"));
dao.insert(new WorkoutSet(prefix + "-WS001",1));
dao.insert(new WorkoutSet(prefix + "-WS002",2));
}
}
note the wdb.close() is uncommented when creating the database to be loaded into the SQlite tool.
testit.db the SQLite database file as modified to introduce the new workoutList table. This first copied from a run of the App at Version 1 and then adding the new table (according SQL copied from the createAllTables method of WorkoutDataabase_Impl class in the generated java (aka the exact table as per Room's expectations)).
Note really the rows should be deleted as they are user specific. Instead -WRONG has been added to the end of the data (helps to prove the some points)
as above
The new table
Navicat was the SQLite tool used rather than DB Browser.
Run 1 running at version 1 to populate the database
DATABASE_VERSION = 1
#Database(entities = {Workout.class,WorkoutSet.class/*, WorkoutList.class*/ /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
i.e. the WorkoutList table has been excluded
App Inspection shows:-
and
Impotantly no WorkoutList table
Run 2 version 2 and introduction of new workoutlist table
DATABASE_VERSION = 2
#Database(entities = {Workout.class,WorkoutSet.class, WorkoutList.class /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
i.e. WorkoutList now introduced
App Inspection shows:-
old rows retained (they do not have -WRONG as per the asset)
old rows retained
the new table, populated as per the asset
Run 3 new install (after App uninstalled) at VERSION 2
as an be seen the incorrectly left in the assets rows exist (obviously you would delete the rows in real life but leaving them in shows that they are from the asset rather than generated by the code in the activity)
likewise
Conclusion
So the new install of the App correctly copies the asset database via createFromAsset whilst if migrating only the new data in the workoutlist table is copied from the asset.
I'm trying to test Room DB after including a new Column date. But I'm not getting how to insert data as I cannot use context in Testing.java file.
my code looks like this,
#RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "note_database";
#Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
NoteDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
#Test
public void migrateAll() throws IOException {
// Create earliest version of the database.
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
db.execSQL("INSERT INTO note_table (title,description,priority,date)" +
" VALUES ('title_test','description_test','priority_test','10/12/2019'); ");
db.close();
// Open latest version of the database. Room will validate the schema
// once all migrations execute.
NoteDatabase appDb = Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().getTargetContext(),
NoteDatabase.class,
TEST_DB)
.addMigrations(ALL_MIGRATIONS).build();
appDb.getOpenHelper().getWritableDatabase();
appDb.close();
}
// Array of all migrations
private static final Migration[] ALL_MIGRATIONS = new Migration[]{
MIGRATION_1_2};
}
This code was working fine but , look at this reference code. i need to do something like this.
and I'm not getting how to read getMigratedRoomDatabase data. can anyone help me on this.
// MigrationTestHelper automatically verifies the schema
//changes, but not the data validity
// Validate that the data was migrated properly.
User dbUser = getMigratedRoomDatabase().userDao().getUser();
assertEquals(dbUser.getId(), USER.getId());
assertEquals(dbUser.getUserName(), USER.getUserName());
// The date was missing in version 2, so it should be null in
//version 3
assertEquals(dbUser.getDate(), null);
That is nothing but appDb in your code. it will look like
appDb.userDao().getUser();
I want to store a video in sqlite database. P.S. I do not want to store the path but the actual video contents.
I have converted the video in byte array and stored byte array in sqlite database. Upon retrieval bytearray is being converted into File. But video is not playing. Please help.
I want to store a video in sqlite database. P.S. I do not want to
store the path but the actual video contents.
Unless the videos are very short and take up little space (say up to 200k each, perhaps 1/10th of a second but would depend upon the format it is saved in) then you would likely encounter issues and exceptions/crashes.
Using a phone around 2 seconds of black took up 2.2Mb, 2 seconds of actually recording a video took up 7Mb.
Although SQLite has the ability to store relative large BLOB's as per :-
Maximum length of a string or BLOB
The maximum number of bytes in a string or BLOB in SQLite is defined
by the preprocessor macro SQLITE_MAX_LENGTH. The default value of this
macro is 1 billion (1 thousand million or 1,000,000,000). You can
raise or lower this value at compile-time using a command-line option
like this:
-DSQLITE_MAX_LENGTH=123456789 The current implementation will only support a string or BLOB length up to 231-1 or 2147483647. And some
built-in functions such as hex() might fail well before that point. In
security-sensitive applications it is best not to try to increase the
maximum string and blob length. In fact, you might do well to lower
the maximum string and blob length to something more in the range of a
few million if that is possible.
During part of SQLite's INSERT and SELECT processing, the complete
content of each row in the database is encoded as a single BLOB. So
the SQLITE_MAX_LENGTH parameter also determines the maximum number of
bytes in a row.
The maximum string or BLOB length can be lowered at run-time using the
sqlite3_limit(db,SQLITE_LIMIT_LENGTH,size) interface.
Limits In SQLite
The Android SDK's CursorWindow has a limitation of 2Mb and that is for all the columns of the row(s) if buffers. As such even if you can store Videos successfully, you may not be able to retrieve those Videos.
The recommended way is what you don't want, that is to store the path to the Video.
If i store the video in my internal/external storage and store the
path instead then how will i be able to access the same from some
other device.
You would have the same issue with the database as it's typically stored within the Applications data which is protected. That is unless the database is a pre-existing database (i.e. populated with data), in which case the database is distributed with the App via the APK.
If the latter, a pre-existing database distributed via the APK, then the videos can also be distributed as part of the APK and hence as protected as and as exposable as the database.
If your intention is to distribute videos between devices that are not part of the APK then SQlite is probably not the correct solution as it's an embedded database and has no client/server functionality built in.
Besides what if my device gets formatted then I will lose all the
data.
In such a scenario, the database would be as vulnerable as any other data, as that is all the database is, a file, just like a video, a word document etc which all need a suitable application to view/change the content. However, if the database is a pre-existing database, then simply re-installing the App would restore the database and other files from the APK.
Working Example
This uses the Suggested/Recommended method assuming the videos are to be distributed with the APK.
Note Videos Courtesey of Sample Videos
After creating new project 4 videos were downloaded and copied into the res/raw folder (after creating the raw folder) as per :-
The Database Helper (subclass of SQLiteOpenHelper) was created for a 2 column table an with
- _id column (note named _id for use with SimpleCursorAdapter).
- video_path for storing the path/name of the video (not the full path but sufficient to be able to determine the path from the data stored)
- Note UNIQUE has been coded to stop duplicates being added.
With some basic method to allow rows to be added and deleted and for all rows to be extracted (via a Cursor for use with the SimpleCursorAdapter).
DBHelper.java
public class DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "myvideos";
public static final int DBVERSION = 1;
public static final String TBL_VIDEO = "video";
public static final String COL_VIDEO_ID = BaseColumns._ID;
public static final String COL_VIDEO_PATH = "video_path";
SQLiteDatabase mDB;
public DBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
mDB = this.getWritableDatabase();
}
#Override
public void onCreate(SQLiteDatabase db) {
String crt_video_table = "CREATE TABLE IF NOT EXISTS " + TBL_VIDEO + "(" +
COL_VIDEO_ID + " INTEGER PRIMARY KEY," +
COL_VIDEO_PATH + " TEXT UNIQUE" +
")";
db.execSQL(crt_video_table);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public long addVideo(String path) {
ContentValues cv = new ContentValues();
cv.put(COL_VIDEO_PATH,path);
return mDB.insert(TBL_VIDEO,null,cv);
}
public Cursor getVideos() {
return mDB.query(TBL_VIDEO,null,null,null,null,null,null);
}
public int deleteVideoFromDB(long id) {
String whereclause = COL_VIDEO_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
return mDB.delete(TBL_VIDEO,whereclause,whereargs);
}
}
A pretty straigforward MainActivity.java (see comments)
public class MainActivity extends AppCompatActivity {
TextView mMyTextView;
ListView mVideoList;
VideoView mVideoViewer;
DBHelper mDBHlpr;
Cursor mCsr;
SimpleCursorAdapter mSCA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyTextView = this.findViewById(R.id.mytext);
mVideoList = this.findViewById(R.id.videolist);
mVideoViewer = this.findViewById(R.id.videoviewer);
mDBHlpr = new DBHelper(this);
addVideosFromRawResourceToDB();
}
#Override
protected void onDestroy() {
mCsr.close(); //<<<<<<<<<< clear up the Cursor
super.onDestroy();
}
#Override
protected void onResume() {
super.onResume();
manageListView(); //<<<<<<<<<< rebuild and redisplay the List of Videos (in case they have changed)
}
/**
* Setup or Refresh the ListView adding the OnItemClick and OnItemLongClick listeners
*/
private void manageListView() {
mCsr = mDBHlpr.getVideos();
// Not setup so set it up
if (mSCA == null) {
// Instantiate the SimpleCursorAdapter
mSCA = new SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_1, // Use stock layout
mCsr, // The Cursor with the list of videos
new String[]{DBHelper.COL_VIDEO_PATH}, // the column (columns)
new int[]{android.R.id.text1}, // the view id(s) into which the column(s) data will be placed
0
);
mVideoList.setAdapter(mSCA); // Set the adpater for the ListView
/**
* Add The Long Click Listener (will delete the video row from the DB (NOT the video))
*/
mVideoList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
mDBHlpr.deleteVideoFromDB(id);
manageListView(); // <<<<<<<<<< refresh the ListView as data has changed
return true;
}
});
/**
* Play the respective video when the item is clicked
* Note Cursor should be at the correct position so data can be extracted directly from the Cursor
*/
mVideoList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
setCurrentVideo(mCsr.getString(mCsr.getColumnIndex(DBHelper.COL_VIDEO_PATH)));
}
});
} else {
mSCA.swapCursor(mCsr); //<<<<<<<<<< apply the changed Cursor
}
}
/**
* Set the currrent video and play it
* #param path the path (resource name of the video)
*/
private void setCurrentVideo(String path) {
mVideoViewer.setVideoURI(
Uri.parse(
"android.resource://" + getPackageName() + "/" + String.valueOf(
getResources().getIdentifier(
path,
"raw",
getPackageName())
)
)
);
mVideoViewer.start();
}
/**
* Look at all the resources in the res/raw folder and add the to the DB (not if they are duplicates due to UNQIUE)
*/
private void addVideosFromRawResourceToDB() {
Field[] fields=R.raw.class.getFields();
for(int count=0; count < fields.length; count++){
Log.i("Raw Asset: ", fields[count].getName());
mDBHlpr.addVideo(fields[count].getName());
}
}
}
Results
When first started (nothing plays) :-
After long clicking the 1Mb video (deleting the DB entry) :-
After clicking A Video in the List :-
You can use this approach
When save the video, save it in app private storage folder.
Context.getFilesDir()
This will give you the path to the app storage in ..\Andorid\data\data\com.example.app
and it will be in internal storage.
Where com.example.app will be your application package id. You can make a new folder here like Videos then save videos in this folder. Save its path in the DB. Only your app can access to this folder. No any other app or device user can access this folder. So no one can edit or delete your files except your application.
Moreover if user reset mobile this data will be deleted as well as your database and maybe your app too in some cases. So no need to worry about it that your files will be deleted but database has still their path. If file deleted then DB deleted too as well but only when app Uninstall, device reset or SD card erase.
We try to update sqlite_sequence with the following code.
WeNoteRoomDatabase weNoteRoomDatabase = WeNoteRoomDatabase.instance();
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'attachment'"));
However, it has no effect at all. I exam the sqlite_sequence table content using SQLite browser. The counter is not reset to 0.
If we try to run the same query manually using SQLite browser on same SQLite file, it works just fine.
Our Room database is pretty straightforward.
#Database(
entities = {Attachment.class},
version = 6
)
public abstract class WeNoteRoomDatabase extends RoomDatabase {
private volatile static WeNoteRoomDatabase INSTANCE;
private static final String NAME = "wenote";
public abstract AttachmentDao attachmentDao();
public static WeNoteRoomDatabase instance() {
if (INSTANCE == null) {
synchronized (WeNoteRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
WeNoteApplication.instance(),
WeNoteRoomDatabase.class,
NAME
)
.build();
}
}
}
return INSTANCE;
}
}
Any idea what we had missed out?
Additional information : clearing sqlite_sequence is not working in android room
Room doesn't use SQLiteDatabase - but it uses SupportSQLiteDatabase, while it's source code states, that it delegates all calls to an implementation of {#link SQLiteDatabase}... I could even dig further - but I'm convinced, that this is a consistency feature and not a bug.
SQLiteDatabase.execSQL() still works fine, but with SupportSQLiteDatabase.execSQL() the same UPDATE or DELETE queries against internal tables have no effect and do not throw errors.
my MaintenanceHelper is available on GitHub. it is important that one initially lets Room create the database - then one can manipulate the internal tables with SQLiteDatabase.execSQL(). while researching I've came across annotation #SkipQueryVerification, which could possibly permit UPDATE or DELETE on table sqlite_sequence; I've only managed to perform a SELECT with Dao... which in general all hints for the internal tables are being treated as read-only, from the perspective of the publicly available API; all manipulation attempts are being silently ignored.
i think query is wrong, you should try below query
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = attachment"));
I'm using room database version 2.2.5
Here I'm unable to execute this query using Room Dao structure, so make one simple class and access method as like this and I got successful outcomes so this one is tested result. I'm using RxJava and RxAndroid for same.
public class SqlHelper {
private static SqlHelper helper = null;
public static SqlHelper getInstance() {
if (helper == null) {
helper = new SqlHelper();
}
return helper;
}
public Completable resetSequence(Context context) {
return Completable.create(emitter -> {
try {
AppDatabase.getDatabase(context)
.getOpenHelper()
.getWritableDatabase()
.execSQL("DELETE FROM sqlite_sequence WHERE name='<YOUR_TABLE_NAME>'");
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
}
}
Execute:
SqlHelper.getInstance()
.resetQuizSequence(context)
.subscribeOn(Schedulers.io()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {}, error -> {});
Table sql_sequence is not managed by Room, so you need to edit it using a SupportSQLiteDatabase.
Try this:
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
weNoteRoomDatabase().getOpenHelper().getWritableDatabase().execSQL(sqlQuery);
This works for me - Room 2.2.6
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
<YourDatabase>.getInstance(mContext).getOpenHelper().getWritableDatabase().execSQL(sqlQuery);
I have ran into a situation where I have to create a db with large number of tables. When I wrote the code for creating the same, it became complex and confusing. I was wondering, whether it will be a good approach to create an sqlite db and ship it with the apk.
Which approach do you think would be good and why?
PS: on upgrading, i am dropping all the tables and creating new ones. No data is being retained.
Thanks a lot
If you only creating tables for the database without predefined data, you can create the tables when you need them. But if you have more than 1 MB data (not BLOB) in your databases, you should create the database outside then ship it in your apk. Because inserting 1 MB data takes times and the Android system will complaining with "Application Not Responding" (ANR) dialog. User don't like waiting too long to use the application.
You can use Android SQLiteAssetHelper to ship your SQLite database in your apk.
A few additional/alternative approaches.
My personal approach, which is an alternative to the two mentioned although an extension of creating the tables within the App, has been to create classes that a) simplifies table creation and b) caters for the limited dynamic addition of tables and columns.
It has the advantage of a single naming point (what i see as a pitfall of defining a database outside of the App) for the columns, tables and indexes.
It has the advantage of being directly re-usable. Although it has the disadvantage of the time taken to create the underlying classes (this to me was a benefit as I learnt a great deal being new to both Java and Android).
It does not require the complexities of dealing with copying/managing the database from the assets folder (you say there are none, my observation is that one of the more frequently occurring Android SQlite issues on SO is in regards to problems copying databases from the assets folder).
Some questions that could be asked, how does the App cope with deleting the App's data? If you keep a copy of the database to cater for this scenario is tying up the additional space (I believe it could be around 36k for just a database plus 4k per table (that is just from observations with a single database/table, so not investigated at any depth)). I doubt that the space for the extra code would be as much, but perhaps something that could be investigated.
It does not as yet cater for foreign keys/constraints or triggers
(never used any as of yet).
There are 5 classes DBColumn, DBTable, DBIndex, DBDatabase and SQLKWORDS.
SQLKWORDS contains definitions of commonly used SQL KEYWORDS e.g.
public class SQLKWORD {
public static final String SQLTABLE = " TABLE ";
public static final String SQLCREATE = " CREATE ";
public static final String SQLDROP = " DROP ";
public static final String SQLINDEX = " INDEX ";
public static final String SQLUNIQUE = " UNIQUE ";
.....
}
DBColumn is used for defining columns
These are used to progressively build a DBDatabase, which is basically the required schema plus various methods e.g. actionBuildSQL which will add any tables that don't exist, a complimentary actionAlterSQL caters for the addition of new columns.
For example to define a table (4 columns first 3 make up the primary index (3rd parameter true if to be part of the PRIMARY INDEX, else false)):-
import mjt.dbcolumn.DBColumn;
import mjt.dbtable.DBTable;
import static mjt.sqlwords.SQLKWORD.*;
public class DBCardPayeeLinkTableConstants {
static final String CARDPAYEELINK_TABLE = "cardpayeelink";
private static final String CARDREF_COL = "cardref";
private static final String PAYEEREFCOL_COL = "payeeref";
private static final String USERREF_COL = "userref";
private static final String USAGE_COUNT_COL = "usagecount";
// DBCOlumns
static final DBColumn CARDREF = new DBColumn(
CARDREF_COL,
SQLINTEGER,
true,
""
);
static final DBColumn PAYEEREF = new DBColumn(
PAYEEREFCOL_COL,
SQLINTEGER,
true,
""
);
static final DBColumn USERREF = new DBColumn(
USERREF_COL,
SQLINTEGER,
true,
""
);
static final DBColumn USAGECOUNT = new DBColumn(
USAGE_COUNT_COL,
SQLINTEGER,
false,
"0"
);
static final ArrayList<DBColumn> CARDPAYEELINKCOLUMNS =
new ArrayList<>(
Arrays.asList(
CARDREF,
PAYEEREF,
USERREF,
USAGECOUNT
)
);
static final DBTable CARDPAYEELINKS = new DBTable(
CARDPAYEELINK_TABLE,
CARDPAYEELINKCOLUMNS
);
// define full column names i.e. table.columnname
public static final String CARDREF_FCOL =
CARDREF.getFullyQualifiedDBColumnName();
public static final String PAYEEREF_FCOL =
PAYEEREF.getFullyQualifiedDBColumnName();
public static final String USEREF_FCOL =
USERREF.getFullyQualifiedDBColumnName();
public static final String USAGECOUNT_FCOL =
USAGECOUNT.getFullyQualifiedDBColumnName();
}
There are a number of DBCOlumn constructors e.g. DBColumn(true) will create the commonly used _id column. As can be seen from the above there are also various methods.
DBTable takes a list of DBColumns and DBDatabase takes a list of DBTables (and if required DBIndexes which themselves are take a DBTable and a list of DBColumns). DBDatabase's methods such as actionAlterSQL compares the required schema against the actual current schema.
I will then have a class such as the following:-
class DBConstants {
/*
Define the database name
*/
static final String DATABASENAME = "cardoniser";
static final int DATABASEVERSION = 1;
/*
Define the DBDatabase instance (base schema),
it consists of the DBTable instances which are
comproised of DBColumns (and optionally DBindex instances)
*/
static final DBDatabase cardonsier = new DBDatabase(DATABASENAME,
new ArrayList<>(Arrays.asList(
DBCardsTableConstants.CARDS,
DBUsertableConstants.USERS,
DBPreftableConstants.PREFS,
DBPayeestableContants.PAYEES,
DBCategoriestableConstants.CATEGORIES,
DBCardCategoryLinkTableConstants.CARDCATEGORYLINKS,
DBCardPayeeLinkTableConstants.CARDPAYEELINKS,
DBTransactionsTableConstants.TRANSACTIONS
))
);
}
in my DBHelper I have (nothing done in onUpgrade, onCreate simply calls onExpand) :-
#Override
public void onCreate(SQLiteDatabase db) {
usable = this.onExpand(db,false);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldversion, int newversion) {
}
public boolean onExpand(SQLiteDatabase db, boolean buildandexpand) {
boolean rv = true;
if (db == null) {
db = instance.getWritableDatabase();
}
// Is the Database definition valid to use?
if (DBConstants.cardonsier.isDBDatabaseUsable()) {
ArrayList<String> buildsql = DBConstants.cardonsier.generateDBBuildSQL(db);
// Anything to build
// will not be if database is unchanged rather than
// if no definitions/valid definitions as this would
// be detected by isDBDatabaseUsable
if (!buildsql.isEmpty()) {
// YES so build the Database
DBConstants.cardonsier.actionDBBuildSQL(db);
}
if (buildandexpand) {
ArrayList<String> altersql = DBConstants.cardonsier.generateDBAlterSQL(db);
if (!altersql.isEmpty()) {
DBConstants.cardonsier.generateExportSchemaSQL();
}
}
}
else {
rv = false;
}
return rv;
}
public boolean isDBUsable() {
return usable;
}
In the initial Activity I have, which will apply any table or row additions and additionally index additions, deletions or alterations(delete and recreate index) :-
DBDAO dbdao = new DBDAO(this);
DBHelper.getHelper(this).onExpand(dbdao.getDB(),true);
In addition to this I use a single class per table (actually two as I also have a separate class for table specific methods).
All in all there are few complexity issues and for myself, having done the groundwork, is the better of all three options.
Another option that could be considered.
There are plenty of SQLite Database tools e.g. SQLite Manager (browser extension) that would allow you to create a database and export the SQL.
e.g. the above database (well close to it) was retrieved from SQL Manager as per :-
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE cardcategorylink (cardref INTEGER , categoryref INTEGER , usagecount INTEGER DEFAULT 0 , PRIMARY KEY (cardref, categoryref) );
CREATE TABLE cardpayeelink (cardref INTEGER , payeeref INTEGER , usagecount INTEGER DEFAULT 0 , PRIMARY KEY (cardref, payeeref) );
CREATE TABLE cards (_id INTEGER PRIMARY KEY, cardname TEXT , cardtyperef INTEGER DEFAULT 0 , cardnumber TEXT );
CREATE TABLE categories (_id INTEGER PRIMARY KEY, categoryname TEXT );
CREATE TABLE payees (_id INTEGER PRIMARY KEY, payeename TEXT );
CREATE TABLE prefs (_id INTEGER PRIMARY KEY, prefname TEXT , preftype TEXT , prefvalue TEXT , prefshortdesc TEXT , preflongdesc TEXT );
CREATE TABLE transactions (_id INTEGER PRIMARY KEY, timestamp INTEGER DEFAULT 0 , cardref INTEGER DEFAULT 0 , payeeref INTEGER DEFAULT 0 , categoryref INTEGER DEFAULT 0 , amount REAL DEFAULT 0.00 , flags INTEGER DEFAULT 0 );
CREATE TABLE users (_id INTEGER PRIMARY KEY, username TEXT , userhash TEXT , usersalt TEXT );
(note the above database was actually copied into SQLite Manager via Android Device Manager, so it was really generated using the Approach that I have adopted).
If you source is MySQL or others then I believe that PHPMyAdmin has similar functionality.