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.
Related
The following code is used to save files from the app to downloads:
Uri collection = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, filename);
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
ContentResolver contentResolver = context.getApplicationContext().getContentResolver();
Uri uri = contentResolver.insert(collection, values);
OutputStream outputStream = context.getApplicationContext().getContentResolver().openOutputStream(uri, "w");
Everything is saved, however, if you delete the file from downloads manually, and then try to download it again from the application, an error appears:
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE[2067])
Is there a way to fix this or will I have to use unique names for each download?
This error happens because the machanism of OS: if we delete manually a file (media), its database will not be deleted immediately. Until we restart device.
Have a approach for this problem (still not be optimized - hope receiving sharing from people), such as:
Step 1: Get info of file via its name
Step 2: Ask OS to update its database via MediaScannerConnection.scanFile
Step 3: Use current code that has above problem
Codes for steps (collected on internet)
Step 1:
fun findByFileName(fileName: String): MutableList<FileInfo> {
val files = mutableListOf<FileInfo>()
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE,
MediaStore.Video.Media.DATA
)
val selection = "${MediaStore.Video.Media.DISPLAY_NAME} LIKE ?"
val selectionArgs = arrayOf(
fileName
)
// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"
val query = context.contentResolver.query(
collection,
projection,
selection,
selectionArgs,
sortOrder
)
query?.use { cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
val durationColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
while (cursor.moveToNext()) {
// Get values of columns for a given video.
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val duration = cursor.getInt(durationColumn)
val size = cursor.getInt(sizeColumn)
val data = cursor.getStringOrNull(dataColumn)
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
id
)
// Stores column values and the contentUri in a local object
// that represents the media file.
files += FileInfo(contentUri, name, duration, size, data)
}
}
return files
}
data class FileInfo(
val uri: Uri,
val name: String,
val duration: Int,
val size: Int,
val data: String? = null
)
Step 2 + Step 3:
val exitedData = findByFileName(fileName = name)
if (exitedData != null) {
MediaScannerConnection.scanFile(context, arrayOf(exitedData.first().data.toString()), null, object: MediaScannerConnection.OnScanCompletedListener {
override fun onScanCompleted(path: String?, uri: Uri?) {
// Step 3
// Use current code have this problem
...
}
}
} else {
// Save file normally
// Use current code have this problem
...
}
I've got the following code to retrieve a contact list with Name + PhoneNumber:
#SuppressLint("Range")
fun getNamePhoneDetails(): ArrayList<List<String>>? {
val names = ArrayList<List<String>>()
val cr = contentResolver
val cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
null, null, null)
if (cur!!.count > 0) {
while (cur.moveToNext()) {
val id = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NAME_RAW_CONTACT_ID))
val name = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
val number = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
names.add(listOf(id, name, number))
}
}
return names
}
The output is correct once no contact has two phone numbers. However once any contact has two phone numbers I get the contact twice. It has the same id and name, but a different phone number. How can I make it so that the returned list does not have a contact twice but all phone numbers inside a list?
Something like
[1, Name Name, [phonenumber1, phonenumber2]],[2, Name Name, [phonenumber1]]
Then I could just iterate through the phonenumberlist and have all numbers as valid strings.
You should rather create a POJO Object and than assign the data into it and return a list of that POJO Object and then you can make use of distinct extension on the list to get a filtered result they way you want it .
This is how you can achieve what you want :
Create a POJO Object :
data class Contact(
val id : String ,
val name : String,
val number : String)
And then when retrieveing the data you can do the following :
#SuppressLint("Range")
fun getNamePhoneDetails(): MutableList<Contact>? {
val names = MutableList<Contact>()
val cr = contentResolver
val cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
null, null, null)
if (cur!!.count > 0) {
while (cur.moveToNext()) {
val id = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NAME_RAW_CONTACT_ID))
val name = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
val number = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
names.add(Contact(id , name , number))
}
}
return names
}
And then while retreiving the data you need to just filter the list in the following manner :
val list = getNamePhoneDetails()
list.distinctBy { it.number }.forEach { println(it) }
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.
Through my app I am creating calendar events using ContentResolver. Events are being successfuly created and you can see them in system calendar app. The problems appear when the user is using Google Calendar app. Sometimes it takes few minutes for event to appear there. Also when the event at last appear in Google Calendar and user delete it there it is still visible in system calendar again for few minutes.
In my application I want to create intent to open newly created event in caledndar but because of that synchronization problem it is not being displayed since it is not yet visible. Also before creating new event I am checking if it already exist and again because of synchronization it appears that it wasn't deleted and I am not creating new one when I should be.
Here is how I am creating the events:
fun addEventToCalendar(context: Context,
title: String,
description: String?,
startDateMillis: Long,
endDateInMillis: Long,
tag: String): Long? {
val timeZone = TimeZone.getDefault()
val values = ContentValues().apply {
put(CalendarContract.Events.DTSTART, startDateMillis)
put(CalendarContract.Events.DTEND, endDateInMillis)
put(CalendarContract.Events.TITLE, title)
description?.let { put(CalendarContract.Events.DESCRIPTION, description) }
put(CalendarContract.Events.EVENT_TIMEZONE, timeZone.id)
put(CalendarContract.Events.CALENDAR_ID, getCalendarId(context))
put(CalendarContract.Events.UID_2445, tag)
}
val uri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, values)
return uri?.lastPathSegment?.toLong()
}
fun checkIfEventAlreadyExist(context: Context, tag: String): Long? {
val projection = arrayOf(CalendarContract.Events._ID, CalendarContract.Events.DTSTART, CalendarContract.Events.DTEND, CalendarContract.Events.UID_2445)
val cursor = context.contentResolver.query(
CalendarContract.Events.CONTENT_URI,
projection,
"$CalendarContract.Events.UID_2445 = ? and $CalendarContract.Events.CALENDAR_ID = ?",
arrayOf(tag, getCalendarId(context).toString()),
"$CalendarContract.Events._ID ASC")
cursor?.let {
cursor.moveToFirst()
val eventId = if (cursor.count == 0) null else cursor.getLong(0)
cursor.close()
return eventId
}
return null
}
Also I've noticed that when I add event normally through system calendar app or google calendar app then there is no problem. All the events are being displayed everywhere immediately and same goes for deletion.
How could I improve that google calendar synchronisation?
EDIT:
During my research I've come accros suggestions to use ContentResolver.requestSync() after adding calendar event. It looked promissing but doesn't work... Am I doing something wrong here? Here is my code for that:
private fun forceSync(context: Context) {
val extras = Bundle()
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
val account = Account("example.email#gmail.com", "com.google")
ContentResolver.requestSync(account, CalendarContract.AUTHORITY, extras)
}
or:
private fun forceSync(context: Context) {
val cr = context.contentResolver
val values = ContentValues()
values.put(CalendarContract.Calendars.SYNC_EVENTS, 1)
values.put(CalendarContract.Calendars.VISIBLE, 1)
cr.update(
ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI,
3), values, null, null)
}
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.