Android Reading cursor without rows - android

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

Some devices like OPPO don't support reading contacts in pages (pagination) Android

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
}

Why is my Cursor for loading images from external storage empty

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

Fetching all the gallery images and showing in android app

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.

Code returns column name but column count is zero in Kotlin

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.

How to return image files on the phone as LiveData in Android?

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)
}

Categories

Resources