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.
Related
I have an event id sored in my android app.
Now I want to get this specific event from calendar. Is that possible?
I have found various answers on how to get events from calendar, but none was working for my requirements.
Here is documented how to get events from specific calendar.
Constraints are set using CalendarContract.Calendars I believe I need to use CalendarContract.Events._ID
Here is what I've tried
private fun getCalendarEvents(ids: List<Long>): List<Pair<String, Long>> {
val EVENT_PROJECTION: Array<String> = arrayOf(
CalendarContract.Events._ID,
CalendarContract.Events.TITLE
)
val PROJECTION_ID_INDEX: Int = 0
val PROJECTION_TITLE_INDEX: Int = 1
val uri: Uri = CONTENT_URI
val keys: String = ids.fold("") { acc, it -> "$acc, $it" }
val selection: String = "${CalendarContract.Calendars._ID} IN ?"
val selectionArgs: Array<String> = arrayOf("($keys)")
val cur: Cursor = contentResolver.query(uri, EVENT_PROJECTION, selection, selectionArgs, null)!!
val result: MutableList<Pair<String, Long>> =
emptyList<Pair<String, Long>>().toMutableList()
while (cur.moveToNext()) {
val ID: Long = cur.getLong(PROJECTION_ID_INDEX)
val TITLE: String = cur.getString(PROJECTION_TITLE_INDEX)
result += Pair(TITLE, ID)
}
return result
}
It tries to load all events which have _ID in ids list and returns (Title, Id) pairs.
Is there any easy was how to accomplis this? There is an easy way for inserting, editing and deleting events here so I guess there must be something like that for fetching events as well.
Thank for help.
I am able to get user phone numbers from the contacts list, but i also need names with the numbers,
I know if i use custom adapter then i can get name and number both, but i want to use the default contact picker.
This is my code.
private fun launchMultiplePhonePicker() {
val phonebookIntent = Intent("intent.action.INTERACTION_TOPMENU")
phonebookIntent.putExtra("additional", "phone-multi")
phonebookIntent.putExtra("maxRecipientCount", 20)
phonebookIntent.putExtra("FromMMS", true)
startActivityForResult(phonebookIntent, 110)
}
This does work fine, but only returns phone numbers, and not contact names in onActivityResult.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val bundle = data?.extras
val result = bundle?.getString("result")
val contacts = bundle?.getStringArrayList("result")
}
Edit:
I found out that intent.action.INTERACTION_TOPMENU may not work in all devices, so i used the following approach, its giving me the names with number, but not allowing me to select multiple contacts.
val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
startActivityForResult(intent, 10101)
My simple solution to get contacts, may be it will help you
Data class to hold extracted values:
data class ContactModel(val phoneNumber: String, val displayName: String)
Get contacts and map to model
val result = arrayListOf<ContactModel>()
val cursor.context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, ContactsContract.Contacts.SORT_KEY_PRIMARY + " ASC")
cursor?.let {
val nameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
val phoneIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
while (it.moveToNext()) {
val name = cursor.getString(nameIndex)
val phone = cursor.getString(phoneIndex)
var num = phone
.replace(" ", "")
.replace("-", "")
.replace("(", "")
.replace(")", "")
val contactModel = ContactModel(num, name)
//Prevents duplicated contacts on some devices
if (it.position != 0) {
if (contactModel != result[result.size - 1]) {
result.add(contactModel)
}
} else {
result.add(contactModel)
}
}
}
If you have a list of phone numbers and you need to get the display names, you can use ContactsContract.PhoneLookup, something like this:
private fun phoneToName(phone: String): String? {
val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone))
var cur = getContentResolver().query(uri, arrayOf(PhoneLookup.DISPLAY_NAME), null, null, null)
if (cur.moveToFirst()) {
return cur.getString(0)
}
cur.close()
return null
}
P.S. just note that "intent.action.INTERACTION_TOPMENU" is not an official Android API, and is probably not supported by all devices.
EDIT: there's no official way of using the phone-picker for multiple contacts, either you implement your own contact list and let the user choose multiple contacts within your app, or you can allow the user to pick contacts multiple times until they finish.
I am currently using the following code to load all images from the Android contentProvider in my repository:
override suspend fun getLocalImagePaths() = SuspendableResult.of<List<String>, Exception> {
val result = mutableListOf<String>()
val uri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.MediaColumns.DATA)
contentResolver.query(uri, projection, null, null, null)?.use {
val dataIndex = it.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
while (it.moveToNext()) {
result.add(it.getString(dataIndex))
}
}
result
}
This gets the absolute paths to all available images and it seems to work in Android 9, allthough some images can't be loaded (I am using Glide), but in Android 10 I can't load any of the image paths that are returned from the mentioned method. How could I do this?
override suspend fun getLocalImagePaths() = SuspendableResult.of<List<Uri>, Exception> {
val result = mutableListOf<Uri>()
val uri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.Images.Media._ID)
contentResolver.query(uri, projection, null, null, null)?.use {
while (it.moveToNext()) {
result.add(
ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
it.getLong(0)
)
)
}
}
result
}
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.
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)
}