Android Contacts API Library written in Kotlin with Java interoperability. No more ContentProviders and cursors. Say goodbye to ContactsContract. Build your own contacts app!
This release contains functions that give you more control over Account, Group, and Include field validation checks in read/write APIs. Apart from that, this comes with related improvements and some bug fixes. There's a minor breaking change that might affect you if you have created your own custom data.
Insert
API function (commitInChunks
) that allows users to insert large numbers of new RawContacts much faster than commit
#329, documentation
Insert
and ProfileInsert
APIs #318, documentation
MoveRawContactsAcrossAccounts
API #319, documentation
Insert
and ProfileInsert
APIs #320, documentation
Insert
API now only queries Groups internally at most once regardless of the quantity of RawContacts being inserted #330Insert
API now only queries Accounts internally at most once regardless of the quantity of RawContacts being inserted #332Insert
API now skips processing GroupMemberships of RawContacts that specified no GroupMemberships #331Insert
API no longer adds an update Options operation for RawContacts that has null Options #333SimContactsInsert
and SimContactsUpdate
to be cancelled during calculation of max character limits #327Insert
API includes RawContactsFields
(AccountName, AccountType, SourceId) even when not included #325AccountQuery
API returns all available/visible accounts if android.permission.GET_ACCOUNTS
is NOT explicitly granted, regardless of values passed to withTypes
#338Contacts.insertSimContact
function in SimContactsInsert.kt
from public to internal #3280.3.1
-> 0.3.2
Fix compile errors by making Set<AbstractCustomDataField>
nullable -> Set<AbstractCustomDataField>?
For example, change this...
internal class HandleNameMapperFactory {
override fun create(
... includeFields: Set<HandleNameField>
) = HandleNameMapper(HandleNameDataCursor(cursor, includeFields))
}
to this...
internal class HandleNameMapperFactory {
override fun create(
... includeFields: Set<HandleNameField>?
) = HandleNameMapper(HandleNameDataCursor(cursor, includeFields))
}
https://github.com/vestrel00/contacts-android/compare/0.3.1...0.3.2
Head on over to the v0.3.2 Release Checklist and leave a comment and/or some reactions 🙏 😄
There's a LOT of new useful stuff here for folks interested in using this library for sync adapter use cases! Thanks to @marrale for starting these discussions (which led to the addition of a bunch of useful new stuff);
I was not planning for these features to be added to the library but I do see value in them, especially for folks needing to implement a sync adapter.
Apart from that, there is also a handful of bug fixes, improvements, and tiny bit of breaking changes (with migration guide).
ContactsContract.RawContacts.SOURCE_ID
#300, documentation
ContactsContract.Groups.SOURCE_ID
#303, documentation
ContactsContract.DataColumns.IS_READ_ONLY
when inserting any NewDataEntity
(e.g. name, email, phone, etc) #306, documentation
ContactsContract.DataColumns.IS_READ_ONLY
for any ExistingDataEntity
#307, documentation
ContactsContract.CALLER_IS_SYNCADAPTER
in all CRUD APIs #308, documentation
AccountsQuery
API returns Accounts with no sync adapters for Contacts #298ProfileUpdate
API fails when Contact is provided but not RawContact(s) #302GroupsUpdate
API allows updating read-only groups, which results in a falsely successful operation #305fun Activity.selectPhoto()
in PhotoPicker.kt does not work in APIs 30 and up #314contacts.ui.util.requestToBeTheDefaultDialerApp
to include additional instructions for API 33 (Tiramisu) and higher #315ContactsContract.CALLER_IS_SYNCADAPTER
is set to true #309associatedWith
and associatedWithRawContactIds
functions from the AccountsQuery
API and profile
from the Accounts
API #297mutableCopy
function should not return null even if readOnly is true in order to support usages by sync adapters #304readOnly
property of GroupEntity
to isReadOnly
and ReadOnly
of GroupsFields
to GroupIsReadOnly
#310NewCustomDataEntity
implementations now require additional property isReadOnly
#311callerIsSyncAdapter: Boolean
parameter #3130.3.0
-> 0.3.1
associatedWith
and associatedWithRawContactIds
functions from the AccountsQuery
API and profile
from the Accounts
API #297PREVIOUSLY, the AccountsQuery
API provided functions to filter Accounts based on RawContacts via the associatedWith
and associatedWithRawContactIds
functions.
NOW, those API functions have been removed. Reasons for removal are stated in #297
Additionally, the profile
function of the Accounts
API have been removed because the sole reason it existed was for use with associatedWith
and associatedWithRawContactIds
functions of the AccountsQuery
API.
If you need to get the Account of a RawContact based on just an ID, use the RawContactsQuery
API instead.
🗒️ Read the new documentation for the full guide!
mutableCopy
function should not return null even if readOnly is true in order to support usages by sync adapters #304PREVIOUSLY, the Group.mutableCopy()
function may return null if the group is read-only.
NOW, it will no longer return null even if the group is read-only.
🗒️ Read the new documentation for the full guide!
readOnly
property of GroupEntity
to isReadOnly
and ReadOnly
of GroupsFields
to GroupIsReadOnly
#310Rename the following usages;
GroupEntity.readOnly
-> GroupEntity.isReadOnly
GroupsFields.ReadOnly
-> GroupsFields.GroupIsReadOnly
NewCustomDataEntity
implementations now require additional property isReadOnly
#311If you have an implementation of NewCustomDataEntity
, you will have to implement a new property which you should set to false by default.
override var isReadOnly: Boolean = false
🗒️ Read the new documentation for more info about this new property!
callerIsSyncAdapter: Boolean
parameter #313Add callerIsSyncAdapter: Boolean
as the first parameter to your AbstractCustomDataOperation.Factory.create
functions and AbstractCustomDataOperation
constructors.
https://github.com/vestrel00/contacts-android/compare/0.3.0...0.3.1
Head on over to the v0.3.1 Release Checklist and leave a comment and/or some reactions 🙏 😄
There are quite a bit of improvements and bug fixes in this release. Unfortunately, as a result, there are a handful of breaking changes... Fortunately, migrations are simple and documented 😁
MoveRawContactsAcrossAccounts
#168, documentation
These should not affect you...
0.2.4
-> 0.3.0
PREVIOUSLY, to move local RawContacts (those with null Account), you would use the AccountsLocalRawContactsUpdate
API.
val updateResult = Contacts(context)
.accounts()
.updateLocalRawContactsAccount()
.addToAccount(account)
.localRawContacts(rawContacts)
.commit()
NOW, the above API has been replaced with MoveRawContactsAcrossAccounts
. It is a much more powerful API that allows you to move local and non-local RawContacts across Accounts.
val moveResult = Contacts(context)
.accounts()
.move()
.rawContactsTo(account, rawContacts)
.commit()
🗒️ Read the new documentation for the full guide!
PREVIOUSLY, the following data kinds were ignored during insert and update operations for local RawContacts (those that are not associated with an Account);
GroupMembership
Event
Relation
This meant that the insert and update APIs would not let you set the values of the above data kinds for local RawContacts... You might have noticed this and thought it was a bug. However, it was intentional 😅
Another artificially imposed "restriction" that existed was that GroupEntity.account
was NOT nullable. This meant that the library did not allow you to insert new groups that were not associated with an account. You were also not able to use the GroupsQuery
API to fetch only groups that had no account (local groups). Furthermore, groups that were fetched that did have a null account in the database table were assigned a non-null account with the value Account("null", "null)
(that is bad).
NOW,
GroupEntity.account
is nullable, allowing you to insert local groupsGroupsQuery
API now supports fetching only local groups🗒️ Read the new documentation for the full guide!
PREVIOUSLY, to set /remove the Contact or RawContact photo, you would use one of these extension functions that immediately commits the changes directly into the database. These can only be used for Contact and RawContacts that are already inserted.
[contact|rawContact].setPhoto(
contactsApi,
[photoInputStream|photoBytes|photoBitmap|photoBitmapDrawable]
)
[contact|rawContact].removePhoto(contactsApi)
NOW, the above functions still exist with a different name and parameter types.
[contact|rawContact].setPhotoDirect(
contactsApi,
PhotoData.from([photoInputStream|photoBytes|photoBitmap|photoBitmapDrawable])
)
[contact|rawContact].removePhotoDirect(contactsApi)
More importantly, you are now also able to set/remove Contact and RawContact photos as part of insert and update API calls!
Contacts(context).insert().rawContact { setPhoto(PhotoData.from(...)) }.commit()
Contacts(context)
.update()
.contacts(contact.mutableCopy { setPhoto(PhotoData.from(...)) })
.contacts(contact.mutableCopy { removePhoto() })
.rawContacts(rawContact.mutableCopy { setPhoto(PhotoData.from(...)) })
.rawContacts(rawContact.mutableCopy { removePhoto() })
.commit()
🗒️ Read the new documentation for the full guide!
PREVIOUSLY, to set the Account of NewRawContact
(s) to be inserted, you would use the forAccount
function of the Insert
and ProfileInsert
APIs,
insert
.forAccount(account)
.rawContacts(rawContact)
.commit()
NOW, the forAccount
functions have been removed but you are able to specify the account of each NewRawContact
,
rawContact.account = account
insert
.rawContacts(rawContact)
.commit()
🗒️ Read the new documentation for the full guide!
PREVIOUSLY, to get/set Contact/RawContact options, you would use the extension functions provided in contacts.core.util.ContactOptions
and contacts.core.util.RawContactOptions
,
val contactOptions = contact.options(contactsApi)
val rawContactOptions = rawContact.options(contactsApi)
contact.setOptions(contactsApi, contactOptions.mutableCopy{ ... })
rawContact.options(contactsApi, rawContactOptions{ ... })
The above extension functions read/write directly from the database and blocked the UI thread. Those functions no longer exist.
NOW, you can directly get/set options through the Contact/RawContact entities and read/write APIs;
val contactOptions = contact.options // yes, this also existed prior to this version
val rawContactOptions = rawContact.options
Contacts(context)
.update()
.contacts(
contact.mutableCopy {
setOptions { ... }
}
)
.rawContacts(
rawContact.mutableCopy {
setOptions { ... }
}
)
.commit()
You are now also able to set the options of a new RawContact for insert APIs,
Contacts(context)
.insert()
.rawContact {
setOptions { ... }
}
.commit()
🗒️ Read the new documentation for the full guide!
Now that BlankRawContact
no longer exists, all you have to do is replace any references you may have to it with references to RawContact
.
PREVIOUSLY, the AccountsRawContactsQuery
had a very limited set of functions. Its sole purpose was to provide a way to get RawContacts directly via the BlankRawContact
entity. It did not allow you to specify fields to include as it had a predefined set of included fields.
You may have used it like so,
val rawContacts = Contacts(context).accounts().queryRawContacts().find()
NOW, it has been refactored to the more powerful RawContactsQuery
,
val rawContacts = Contacts(context).rawContactsQuery().find()
🗒️ Read the new documentation for the full guide!
PREVIOUSLY, to link/unlink Contacts, you would use one of these extension functions that immediately commits the changes directly into the database.
contact1.link(contactsApi, contact2, contact3)
contact.unlink(contactsApi)
NOW, the above functions still exist with different names.
contact1.linkDirect(contactsApi, contact2, contact3)
contact.unlinkDirect(contactsApi)
More importantly, you are now also able to link/unlink using new APIs that match the design of all other existing APIs provided in the library!
contactsApi
.aggregationExceptions()
.link()
.contacts(contact1, contact2, contact3)
.commit()
contactsApi
.aggregationExceptions()
.unlink()
.contact(contact)
.commit()
The advantage of using the new ContactLink
and ContactUnlink
APIs is that it allows you to use [link|unlink]WithPermission
and commit[WithContext|Async]
extensions in the same call =)
🗒️ Read the new documentation for the full guide!
https://github.com/vestrel00/contacts-android/compare/0.2.4...0.3.0
Head on over to the v0.3.0 Release Checklist and leave a comment or some reactions 🙏
This release contains a brand new API for matching contacts, a bunch of SIM card improvements and bug fixes, and the long awaited fix for permissions
extension crashes 🔥 🥂 🥳 ⭐ ❤️
There is a potential one-line breaking change, which you probably don't have to worry about 🤞
PhoneLookupQuery
#259, documentation
Query
and BroadQuery
when not including data fields #249
Account
to BlankRawContactEntity
#254
xxxWithPermission()
may crash app #143
accounts
functions #257
includeBlanks
function from Query
, BroadQuery
, and ProfileQuery
APIs #251
0.2.0
, 0.2.1
, 0.2.2
, or 0.2.3
-> 0.2.4
If you use the includeBlanks
function of Query
, BroadQuery
, or ProfileQuery
APIs, all you would need to do is delete that line of code and you are done!
If you used to set
includeBlanks
totrue
, then nothing will change for you because it is now hard coded to true. If you used to set it tofalse
, also nothing will change for you because it was never working correctly anyways.
https://github.com/vestrel00/contacts-android/compare/0.2.3...0.2.4
Head on over to the v0.2.4 Release Checklist and leave a comment or some reactions 🙏
This release contains more new features for delete APIs, some minor improvements, and an internal refactor that could increase performance.
I'm once again happy to announce that this release has no breaking changes!
https://github.com/vestrel00/contacts-android/compare/0.2.2...0.2.3
Head on over to the v0.2.3 Release Checklist and leave a comment!
This release contains several new features, an improvement, and a critical bug fix to delete APIs.
I'm once again happy to announce that this release has no breaking changes and that we have another new contributor ❤️
https://github.com/vestrel00/contacts-android/compare/0.2.1...0.2.2
Head on over to the v0.2.2 Release Checklist and leave a comment!
This release contains some new good stuff 🍪, some nice-to-have improvements 🍬, and some critical bug fixes 🐞🔨!
I'm also happy to announce that this release has no breaking changes and that we have another new contributor ❤️
https://github.com/vestrel00/contacts-android/compare/0.2.0...0.2.1
Head on over to the v0.2.1 Release Checklist and leave a comment!
It's been half a year since the initial release of this project... Now, through (more) hard work, ❤️, 🔥, and dedication, the biggest project milestone has been reached ⭐️
⚠️WARNING: This release may potentially trigger seizures for people with photosensitive epilepsy. Viewer discretion is advised.
This release contains...
💡New features💡 | ⚒️Improvements⚒️ |
---|---|
🐞Bug fixes🐞 | 🚨Breaking changes🚨 |
This release marks the completion of the first and biggest milestone in the 🗺Project Roadmap! At this point, most of the core features have been implemented with complete 📜Documentation.
There is still a lot of work to do to reach v1.0.0, the first true semantic version of this library. Note that versions prior to v1.0.0 does not follow semantic versioning ✌️
You now have read & write access to blocked numbers 🛑
You are now able to read & write contacts from the SIM card 📱
Lookup keys allow you to load contacts even if they get linked/unlinked, perfect for creating shortcuts to contacts from the launcher 🔍
The customdata-googlecontacts
module gives you read & write access to custom data managed by the Google Contacts app 🌈
The customdata-pokemon
module allows you to give your contacts their favorite Pokemons 🔥
The customdata-rpg
module allows you to assign a profession and stats to your contacts ⚔️
Didn't like the old documentation website? Me neither. Now, it's prettified, structured, and searchable using Material for MkDocs 🏆
BroadQuery
matching for phone and emailNarrow down your broad search to just phone or email instead of all data kinds 🍥
Get contacts with matching data of any kind (default, unchanged),
val contacts = Contacts(context)
.broadQuery()
.wherePartiallyMatches(searchText)
.find()
Get contacts with matching phone,
.match(Match.PHONE)
Get contacts with matching email,
.match(Match.EMAIL)
Of course, you can use the Query
API to make your own advanced, custom matching criteria.
GroupsDelete
is now available for all supported API versions (19 to 31+)The library now allows you to delete groups starting with KitKat (API 19) 👍
val deleteResult = Contacts(context).groups().delete().groups(groups).commit()
Query
returning no results when AND'ing fields of different mime types in WHERE clauseThis is one of those rare occasions when binary trees and recursion saves the day. Traversing the binary tree structure of the Where
clause in post order, simplifying as needed, enables you to AND fields of different mime types together 🤯
val contacts = Contacts(context)
.query()
.where { Email.Address.isNotNull() and Phone.Number.isNotNull() and Name.DisplayName.isNotNull() and Organization.Company.isNotNull() }
// Or for shorthand...
// .where(listOf(Email.Address, Phone.Number, Name.DisplayName, Organization.Company) whereAnd { isNotNull() })
.find()
Query
and BroadQuery
returning RawContacts that are pending deletion when includeBlanks(true)
RawContacts that have been marked for deletion but not yet deleted (perhaps because of no network connectivity preventing a sync) are no longer returned in queries of any configuration 🤗
val contacts = Contacts(context)
.query()
// or .broadQuery()
.includeBlanks(true)
.find()
GroupsQuery
returning Groups that are pending deletionGroups that have been marked for deletion but not yet deleted (perhaps because of no network connectivity preventing a sync) are no longer returned in queries 🤗
val groups = Contacts(context).groups().find()
DataContact
, RawContactContact
, and ContactRefresh
extensions returning incorrect result if Contact has been linked/unlinkedThe ExistingDataEntity.contact
, ExistingRawContactEntity.contact
, and <T : ExistingContactEntity> T.refresh
will return the correct contact even if it has been linked/unlinked 🤗
val contactsApi = Contacts(context)
val parentContactOfData = email.contact(contactsApi)
val parentContactOfRawContact = rawContact.contact(contactsApi)
val refreshedContact = contact.refresh(contactsApi)
BroadQuery
function whereAnyContactDataPartiallyMatches
to wherePartiallyMatches
.
The amount of additions from v0.1.10 to v0.2.0 are... colossal!
It has been many months since this project was featured in . It has also been many years since I have relied on Android Weekly to help me stay on the cutting-edge with my Android development. I am extremely grateful for the service that the Android Weekly team have been providing for the entire Android community for the past decade 🙏
In the spirit of giving back to their legendary service and to promote this project's colossal milestone, I proudly placed a sponsored post in the upcoming ❤️
Have questions, comments, or concerns about this release?
You think this project is useful? Are you using it? Are you impressed with this release notes? Would you care to show your appreciation? Then, please ⭐️ this repo and maybe even 🕊Tweet about it! For more ways to show your support...
❤️ I also shared this colossal release with the r/androiddev community.
✅ This release page has been reviewed and critiqued by the r/opensource community.
This release contains some new nice-to-have goodies and simplifications in several APIs. This may have some breaking changes for you if you use Accounts APIs. Don't worry it's not much! Just read the Migration guide section at the bottom of this page ❤️
Fields
in where
, include
, and orderBy
functions anymore if you choose not to. There is a separate function now that takes in a function that has Fields
as the receiver. More details in #150Please keep in mind that releases below the semantic version v1.0.0 may contain breaking changes!!! Anything below the semantic version v1.0.0 are implicitly experimental/non-stable. Backwards compatibility and deprecations will not be exercised until after v1.0.0 (the first true production release version) in order to keep the library clean and easy to change. TLDR; I'm not really following semantic versioning
major.minor.patch
for any releases below v1.0.0. 😅
Just thought you should know about these in case you experience regression when updating to this version. These should not affect you at all. But just in case, you know about these 🎺
Note for #154. You can actually use the
CrudApiListenerRegistry
as an advanced feature if you want. I don't recommend it though, which is why I am not announcing it as a new feature. I also did not write a howto page for it. The library is currently using this mechanism for logging but you may find some advanced use for it. If you do, let me know how you end up using it! We may find some other use for it than attaching our logger.
https://github.com/vestrel00/contacts-android/compare/0.1.9...0.1.10
Head on over to the v0.1.10 Release Checklist and leave a comment!
This should cover everything that could affect you. Let me know if I missed anything! If you don't care to look at the before-and-after, then go straight into the howto pages. I also made sure to update all documentation in code
Previously, in order to attach a set of local RawContacts to an Account, you would do...
val isSuccessful: Boolean = Contacts(context)
.accounts()
.updateRawContactsAssociations()
.associateAccountWithLocalRawContacts(account, rawContacts)
This really deviated from the API design of using commit
as the terminal operator and everything prior as argument specifications. So, I decided to simplify it and also follow the design of the other update APIs!
Now, to do the same thing as the above example...
val result: Result = Contacts(context)
.accounts()
.updateLocalRawContactsAccount()
.addToAccount(account)
.localRawContacts(rawContacts)
.commit()
val isSuccessful = result. isSuccessful
This is more future-proof and just looks nicer 😄
I have not written the howto page for this but the documentation in code should be enough for now. The issue for the howto page is #1. Leave a comment there if you want to push me to write it sooner 🤣
Previously, in order to query for android.accounts.Account
...
Contacts(context).accounts().query().apply {
val allAccounts : List<Account> = allAccounts(context)
val googleAccounts : List<Account> = accountsWithType(context, "com.google")
val accountsForRawContacts: Result = accountsFor(rawContacts)
}
The problem was similar to the previous refactor. This API derails from the design of the other APIs! This was the last one that had to be fixed to come up with a standard for all APIs in the library... So I cleaned it up!!! 🔥
Now...
Contacts(context).accounts().query().apply {
val allAccounts : List<Account> = find()
val googleAccounts : List<Account> = withTypes("com.google").find()
val accountsForRawContacts: Result = associatedWith(rawContacts).find()
}
This also enables you to use withTypes
and associatedWith
together in different combinations, giving you more flexibility and control!
I updated the howto page for this, howto-query-accounts.md, so you can take a look at it for usage guides if the in-code documentation is not good enough for you 😁
There are a lot of changes in this release that I have put a lot of hours into 😅 All of those hours are worth it, because this library is now so much better because of it 😁. I am very excited to share this new version of Contacts, Reborn to the community! Please take a look, try it out, and let me know what ya think ❤️
Depending on how extensively you have integrated this library into your app, you may be affected be several breaking changes. Don't worry! They are easy to fix and I'll guide you. Just read the Migration guide section at the bottom of this release notes and you will be okay 🤞
Please keep in mind that releases below v1.0.0 may contain breaking changes!!! Anything below the semantic version v1.0.0 are implicitly experimental/non-stable. Backwards compatibility and deprecations will not be exercised until after v1.0.0 (the first true production release version) in order to keep the library clean and easy to change 👍
I don't think these dependency bumps will affect you but let me know if it does!
We got some new people in the house! 😍
https://github.com/vestrel00/contacts-android/compare/0.1.8...0.1.9
Head on over to the v0.1.9 Release Checklist and leave a comment!
This should cover everything that could affect you. Let me know if I missed anything! If you don't care to look at the before-and-after, then go straight into the howto pages. I also made sure to update all documentation in code 😃
Entity
and interfaces or abstract classes that inherit from it are now sealed
except for custom data.This means that you are no longer able to define your own entities, except for inheritors of CustomDataEntity
.
Previously, a mutable entity could represent both an "existing" entity (already inserted into the database and has non-null ID property values) or a "new" entity (not yet inserted into the database and has null ID property values). For example,
val mutableRawContact: MutableRawContact
val mutableEmail: MutableEmail
Now, a mutable entity can only represent either an "existing" or "new" entity...
val existingMutableRawContact: MutableRawContact
val existingMutableEmail: MutableEmail
val newMutableRawContact: NewRawContact
val newMutableEmail: NewEmail
The rest of the migration guide is directly related to this change.
You no longer have to worry about ID properties being null for existing entities.
Previously...
Contact {
val id: Long?
}
RawContact {
val id: Long?
val contactId: Long?
}
Email {
val id: Long?
val rawContactId: Long?
val contactId: Long?
}
Now...
Contact {
val id: Long
}
RawContact {
val id: Long
val contactId: Long
}
Email {
val id: Long
val rawContactId: Long
val contactId: Long
}
"New" entities do not have ID properties at all as they have not yet been inserted into the database.
Previously...
val contact: Contact
val email: Email
val mutableContact: MutableContact = contact.toMutableContact()
val mutableEmail: MutableEmail = email.toMutableEmail()
Now...
val mutableContact: MutableContact = contact.mutableCopy()
val mutableEmail: MutableEmail = email.mutableCopy()
The toMutableXXX
functions have been replaced with a generic mutableCopy
function that is implemented by all inheritors of ImmutableEntityWithMutableType
. I also added some more syntactic sugar for Kotlin users!
val mutableEmail = email.mutableCopy {
address = "[email protected]"
}
Furthermore, you are no longer able to construct instances of existing mutable entities via constructors. This is done to ensure that existing entities can only come from the library APIs.
Yes, I am aware that
data class
provides a copy function that you can use to hack around this but I strongly discourage that. Read the "Creating Entities & data class" in the DEV_NOTES.md if you want to learn more about this.
This makes more sense now doesn't it? You can only update or delete something that already exists in the database.
✅ This will still work...
val existingMutableRawContact: MutableRawContact
Contacts(context).update().rawContacts(existingMutableRawContact).commit()
Contacts(context).delete().rawContacts(existingMutableRawContact).commit()
❌ This will not work (compile-time error)...
val newMutableRawContact: NewRawContact
Contacts(context).update().rawContacts(newMutableRawContact).commit()
Contacts(context).delete().rawContacts(newMutableRawContact).commit()
This applies to all update and delete APIs such as Update
, ProfileUpdate
, DataUpdate
, GroupsUpdate
, Delete
, ProfileDelete
, DataDelete
, GroupsDelete
,...
Inserting already "existing"/inserted entities doesn't really make sense. The only thing that will happen is that a duplicate will be created, which may or may not be what you intend to do.
❌ Anyways, this will no longer work (compile-time error)...
val existingMutableRawContact: MutableRawContact
Contacts(context).insert().rawContacts(existingMutableRawContact).commit()
✅ This will work...
val newMutableRawContact: NewRawContact
Contacts(context).insert().rawContacts(newMutableRawContact).commit()
This applies to all insert APIs such as Insert
, ProfileInsert
, GroupsInsert
,...
util
package are now only usable for existing entities.Extension functions in the util
package typically only work for existing entities (already inserted into the database- non-null ID property values). Prior to this release, those extension functions were also usable by "new" entities (not inserted into the database- null ID property values).
This led to consumers making incorrect, but valid assumptions. For example, in #116 Set photo did not save to system file, the consumer thought that this insert call would also set the photo...
Contacts(context)
.insert()
.rawContact {
...
setPhoto(context, photoBitmap)
}
.commit()
It would make a lot of sense if it did. This should actually work and it will be supported in #119.
However, the above code does not actually set the photo because the RawContact in scope of the rawContact
block has not yet been inserted, which means it does not have a non-null ID property value. So, setPhoto
does nothing and fails.
Now, the setPhoto
util extension function is only usable for existing Contacts or RawContacts. It will not accept new entities. The above code will not even compile! This avoids a lot of confusion 😁