I wrote extension functions for reading from cursor:
fun SQLiteDatabase.query(table: String,
columns: Array<String>,
selection: String? = null,
selectionArgs: Array<String>? = null): Cursor =
query(table, columns, selection, selectionArgs, null, null, null)
fun Cursor.readRows(onRowRead: (Cursor) -> Unit) {
if(moveToFirst()) {
do {
onRowRead(this)
} while (moveToNext())
}
}
fun Cursor.getString(field: String): String = getString(getColumnIndex(field))
fun Cursor.getInt(field: String) = getInt(getColumnIndex(field))
And it mostly works perfect. But recently happened strange error in the followng SQLiteOpenHelper function:
fun loadPresentationStatistic(stringParser: (String) -> PresentationStatistic): ArrayList<PresentationStatisticInformation> {
val db = readableDatabase
val c = db.query(TABLE_PRESENTATION_STATISTIC, arrayOf(ROW_ID, ROW_PRESENTATION_STATISTIC))
val result = arrayListOf<PresentationStatisticInformation>()
c.readRows {
result.add(PresentationStatisticInformation(it.getInt(ROW_ID),stringParser(it.getString(ROW_PRESENTATION_STATISTIC))))
}
c.close()
return result
}
Error stacktrace:
java.lang.IllegalStateException: Couldn't read row 0, col 0 from
CursorWindow. Make sure the Cursor is initialized correctly before
accessing data from it.
android.database.CursorWindow.nativeGetLong(Native Method)
android.database.CursorWindow.getLong(CursorWindow.java:511)
android.database.CursorWindow.getInt(CursorWindow.java:578)
android.database.AbstractWindowedCursor.getInt(AbstractWindowedCursor.java:84)
com.example.DBWorkerKt.getInt(DBWorker.kt:480)
com.example.DBHelper$loadPresentationStatistic$1.invoke(DBWorker.kt:342)
If I get it right, that means moveToFirst returns true for empty cursor, so getInt function throws this exception. How could that be?
The worst thing is that I can't reproduce it. It happens only on one particular device - all other users works as they should.
Related
I face an issues that some devices like OPPO don't support reading contacts in pages like if I make "LIMIT 100 OFFSET 0", it returns all rows in the contacts table.
val cursor = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
null,
null,
null,
"${ContactsContract.Contacts._ID} desc LIMIT $PAGE_LIMIT OFFSET $offset"
)
I had to make a workaround by checking if the first page size equals to the all rows in the table size so I stop reading the contacts in pages to avoid infinite loop.
private fun isReadingContactsInPagesSupported(): Boolean {
val firstPageContactsSize = getQueryCount(" LIMIT $PAGE_LIMIT OFFSET 0")
val allContactsSize = getQueryCount(null)
return firstPageContactsSize != allContactsSize
}
#SuppressLint("Range")
private fun getQueryCount(sort: String?): Int {
val contentResolver: ContentResolver = context.contentResolver
val cursor = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
null,
null,
null,
sort
)
val count = cursor?.count ?: 0
cursor?.close()
return count
}
I keep getting an empty cursor with this method used for loading external images, no idea why. It also worked for me on one emulator configuration before, but now when I try to Log the contents of ID it says:
android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 0. There's pictures installed on the emulator in 3 different external directories.
private suspend fun loadImages(): List<Image>
{
return withContext(Dispatchers.IO) {
val uri = if(Build.VERSION.SDK_INT >= 29) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else MediaStore.Images.Media.EXTERNAL_CONTENT_URI
requireActivity().contentResolver.query(uri, arrayOf(MediaStore.Images.Media._ID),
null, null, "${MediaStore.Images.Media.DATE_ADDED} ASC"
)?.use { cursor ->
val photos = mutableListOf<Image>()
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while(cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
photos.add(Image(id, contentUri))
}
photos
} ?: listOf()
}
}
pic of working image gallery
As usual with my issues, it was something entirely different. The emulator bugged out, I reset it and uploaded images again and it works now. The code is a bit changed without the dispatcher now.
private fun loadImages(): List<Image>
{
val photos = mutableListOf<Image>()
val uri = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else MediaStore.Images.Media.EXTERNAL_CONTENT_URI
requireActivity().contentResolver.query(uri, arrayOf(MediaStore.Images.Media._ID),
null, null, null
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while(cursor.moveToNext()) {
photos.add(Image(ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(idColumn))))
}
return photos.toList()
} ?: return listOf()
}
this is the code now, it works fine
I am fetching all the images from gallery and showing it in my android app.
Initialised in onCreate:
supportLoaderManager.initLoader(IMAGE_LOADER_ID, null, this)
1st Problem:
In the above initialisation, supportLoaderManager is deprecated now. So what is the alternative?
Secondly,
I am using below code to fetch the images:
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection =
arrayOf(MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME)
val selection: String? = null //Selection criteria
val selectionArgs = arrayOf<String>() //Selection criteria
val sortOrder: String? = MediaStore.Images.Media.DEFAULT_SORT_ORDER
return CursorLoader(
applicationContext,
uri,
projection,
selection,
selectionArgs,
sortOrder
)
}
Here the images are coming in random order. So can anyone help me in sorting the images?
After working a bit, I replaced:
val sortOrder: String? = MediaStore.Images.Media.DEFAULT_SORT_ORDER
with:
val sortOrder: String? = MediaStore.Images.Media.DATE_MODIFIED
And it worked fine.
Moreover, to add the items in reverse order, I added the elements at 0 position, like:
while (it.moveToNext()) {
listOfAllImages.add(0, it.getString(columnIndexData))
binding.RVGalleryImages.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
binding.RVGalleryImages.adapter =
this.let { CreateFeedGalleryAdapter(it, listOfAllImages) }
}
The only issue I am facing is that this code is deprecated and I need a new method to achieve this task.
I'm trying to fetch the whole data in a database and this is my code that does that.
private fun readAllPoetsDB(): Cursor {
val db = readableDatabase
return db.rawQuery("SELECT * FROM INFO",null)
}
fun readAllPoets(): ArrayList<Poet> {
val poets = ArrayList<Poet>()
val cursor = readAllPoetsDB()
println("this is cursor count")
println(cursor.count)
println(cursor.getColumnName(0))
var poetid: String
var faname: String
var enname: String
cursor.moveToNext()
while (!cursor.isAfterLast) {
poetid = cursor.getString(cursor.getColumnIndex(DBPoet.UserEntry.COLUMN_POET_ID))
faname = cursor.getString(cursor.getColumnIndex(DBPoet.UserEntry.COLUMN_FANAME))
enname = cursor.getString(cursor.getColumnIndex(DBPoet.UserEntry.COLUMN_ENNAME))
poets.add(Poet(poetid, faname, enname))
cursor.moveToNext()
}
cursor.close()
return poets
}
the line println(cursor.count) returns zero but cursor.getColumnName returns the right name.
Please don't forget that I have checked my table name and it is 100% correct.
Also, my database file is local and it is not getting created inside the app, it is added manually.
So what is the problem?
From Cursor.java:
cursor.count (getCount()) returns the number of rows returned by the Cursor.
cursor.columnCount (getColumnCount()) returns the number of columns in the Cursor.
I get media files on device with the following function in my ViewModel. I provide it to a ListAdapter for RecyclerView where image thumbnails are listed. User can delete some images from the device while my app is in the background. When my app comes to foreground, I need to update my list accordingly. Is there any way to achieve it with LiveData? Otherwise, I will retrieve image files in onResume() and call submitList() on the ListAdapter.
fun getAllMediaFilesOnDevice(context: Context): List<File> {
val files: ArrayList<File> = ArrayList()
try {
val columns = arrayOf(
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME
)
val cursor = MergeCursor(
arrayOf(
context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
columns, null, null, null
),
context.getContentResolver().query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
columns, null, null, null
),
context.getContentResolver().query(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
columns, null, null, null
),
context.getContentResolver().query(
MediaStore.Video.Media.INTERNAL_CONTENT_URI,
columns, null, null, null
)
)
)
cursor.moveToFirst()
files.clear()
while (!cursor.isAfterLast) {
var path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA))
val lastPoint = path.lastIndexOf(".")
path = path.substring(0, lastPoint) + path.substring(lastPoint).toLowerCase()
files.add(File(path))
cursor.moveToNext()
}
} catch (e: Exception) {
e.printStackTrace()
}
return files
}
Update your recyclerview adapter in a reactive way in the fragment/activity:
viewModel.files.observe(this, Observer { files ->
//update files in adapter
})
In the ViewModel add a MutableLiveData to post the latest files to (as a good practice keep it private an only expose the LiveData):
private val mutableFiles: MutableLiveData<List<File>> = MutableLiveData()
val files: LiveData<List<File>> get() = mutableFiles
Then adjust the files method to post files to the LiveData instead of returning it:
fun getAllMediaFilesOnDevice(context: Context): {
//get the files like before...
//then post it to the mutable livedata to notify observers
mutableFiles.postValue(files)
}