Releases: vestrel00/contacts-android
v0.3.2 - New features for disabling validation checks in read/write APIs, improvements for Insert API, bug fixes, and a small breaking change
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.
💡 New features
- Implement a new
Insert
API function (commitInChunks
) that allows users to insert large numbers of new RawContacts much faster thancommit
#329, documentation - Add option to disable Account validation checks in
Insert
andProfileInsert
APIs #318, documentation - Add option to disable Account validation checks in
MoveRawContactsAcrossAccounts
API #319, documentation - Add option to disable GroupMembership validation checks in
Insert
andProfileInsert
APIs #320, documentation - Add option to disable included field validation checks in all insert, update, and query APIs #321, documentation
🛠️ Improvements
- The
Insert
API now only queries Groups internally at most once regardless of the quantity of RawContacts being inserted #330 - The
Insert
API now only queries Accounts internally at most once regardless of the quantity of RawContacts being inserted #332 - The
Insert
API now skips processing GroupMemberships of RawContacts that specified no GroupMemberships #331 - The
Insert
API no longer adds an update Options operation for RawContacts that has null Options #333 - Allow
SimContactsInsert
andSimContactsUpdate
to be cancelled during calculation of max character limits #327
🐞 Bug fixes
Insert
API includesRawContactsFields
(AccountName, AccountType, SourceId) even when not included #325AccountQuery
API returns all available/visible accounts ifandroid.permission.GET_ACCOUNTS
is NOT explicitly granted, regardless of values passed towithTypes
#338
💣 Breaking changes
- Included custom data field sets are now nullable #324
Refactors
- Refactor
Contacts.insertSimContact
function inSimContactsInsert.kt
from public to internal #328
🧗 Migrating from 0.3.1
-> 0.3.2
Included custom data field sets are now nullable #324
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))
}
🗒️ Full Changelog
🗣️ Discuss this release
Head on over to the v0.3.2 Release Checklist and leave a comment and/or some reactions 🙏 😄
v0.3.1 - New features for sync adapter use cases (SOURCE_ID, Data IS_READ_ONLY, CALLER_IS_SYNC_ADAPTER), bug fixes, improvements, and some breaking changes
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).
💡 New features
- Add support for
ContactsContract.RawContacts.SOURCE_ID
#300, documentation - Add support for
ContactsContract.Groups.SOURCE_ID
#303, documentation - Add extensions for getting all data kinds of a Contact or RawContact as a list #312, documentation
- Support setting
ContactsContract.DataColumns.IS_READ_ONLY
when inserting anyNewDataEntity
(e.g. name, email, phone, etc) #306, documentation - Add extensions for checking the value of
ContactsContract.DataColumns.IS_READ_ONLY
for anyExistingDataEntity
#307, documentation - Support setting
ContactsContract.CALLER_IS_SYNCADAPTER
in all CRUD APIs #308, documentation
🐞 Bug fixes
- ExistingContactEntity.setPhotoDirect fails when there is no previous photo #289
- Potential ArrayIndexOutOfBoundsException when querying contacts (and Fields.Event.Date is included) #291
- Query APIs do not return local contacts in Xiaomi devices when passing null to accounts functions #296
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 #305- Extension
fun Activity.selectPhoto()
in PhotoPicker.kt does not work in APIs 30 and up #314
🛠️ Improvements
- Update documentation for
contacts.ui.util.requestToBeTheDefaultDialerApp
to include additional instructions for API 33 (Tiramisu) and higher #315 - Allow updating and deleting read-only groups when
ContactsContract.CALLER_IS_SYNCADAPTER
is set to true #309
💣 Breaking changes
- Remove the
associatedWith
andassociatedWithRawContactIds
functions from theAccountsQuery
API andprofile
from theAccounts
API #297 - Group
mutableCopy
function should not return null even if readOnly is true in order to support usages by sync adapters #304 - Rename
readOnly
property ofGroupEntity
toisReadOnly
andReadOnly
ofGroupsFields
toGroupIsReadOnly
#310 NewCustomDataEntity
implementations now require additional propertyisReadOnly
#311- Custom data integrations now require
callerIsSyncAdapter: Boolean
parameter #313
🧗 Migrating from 0.3.0
-> 0.3.1
Remove the associatedWith
and associatedWithRawContactIds
functions from the AccountsQuery
API and profile
from the Accounts
API #297
PREVIOUSLY, 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!
Group mutableCopy
function should not return null even if readOnly is true in order to support usages by sync adapters #304
PREVIOUSLY, 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!
Rename readOnly
property of GroupEntity
to isReadOnly
and ReadOnly
of GroupsFields
to GroupIsReadOnly
#310
Rename the following usages;
GroupEntity.readOnly
->GroupEntity.isReadOnly
GroupsFields.ReadOnly
->GroupsFields.GroupIsReadOnly
NewCustomDataEntity
implementations now require additional property isReadOnly
#311
If 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!
Custom data integrations now require callerIsSyncAdapter: Boolean
parameter #313
Add callerIsSyncAdapter: Boolean
as the first parameter to your AbstractCustomDataOperation.Factory.create
functions and AbstractCustomDataOperation
constructors.
🗒️ Full Changelog
🗣️ Discuss this release
Head on over to the v0.3.1 Release Checklist and leave a comment and/or some reactions 🙏 😄
v0.3.0 - Lots of improvements, with some breaking changes, and bug fixes
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 😁
💡 New features
- Enhanced API for moving any RawContacts to different Accounts (or null account);
MoveRawContactsAcrossAccounts
#168, documentation
🐞 Bug fixes
- When updating groups, the title is always redacted (replaced with asterisks "*") #281
- Null pointer exception in Fields.all (on first use in multi-threaded API usage) #286
🛠️ Improvements
- Add display name, account, and options properties to RawContactEntity #268
- Support for RawContact IDs in AccountsQuery #276
💣 Improvements with breaking changes
- Replace AccountsLocalRawContactsUpdate API with a better one #282
- Remove Account restrictions for Group, GroupMembership, Relation, and Event #167
- Support setting/removing Contact/RawContact Photos as part of Insert and Update API calls #119
- Allow different Accounts for each RawContact in insert APIs #270
- Move set Contact/RawContact Options functions to be part of Insert and Update APIs #120
- Remove BlankRawContact entity #269
- Generalize AccountsRawContactsQuery to RawContactsQuery #271
- Move Contact link/unlink extensions to a dedicated API #138
♻️ Dependency upgrades
These should not affect you...
- 2023 June Dependency Upgrades (most notably AS Flamingo + AGP 8 + Gradle 8 + JDK 17 + SDK 33 + JitCI -> Circle CI) #274
🧗 Migrating from 0.2.4
-> 0.3.0
Replace AccountsLocalRawContactsUpdate API with a better one #282
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!
Remove Account restrictions for Group, GroupMembership, Relation, and Event #167
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,
- the aforementioned data kinds are no longer ignored during insert and update operations for local RawContacts
GroupEntity.account
is nullable, allowing you to insert local groupsGroupsQuery
API now supports fetching only local groups- Groups that have a null account in the database table are properly assigned a null account value
🗒️ Read the new documentation for the full guide!
Support setting/removing Contact/RawContact Photos as part of Insert and Update API calls #119
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!
Allow different Accounts for each RawContact in insert APIs #270
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!
Move set Contact/RawContact Options functions to be part of Insert and Update APIs #120
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!
Remove BlankRawContact entity #269
Now that BlankRawContact
no longer exists, all you have to do is replace any references you may have to it with references to RawContact
.
Generalize AccountsRawContactsQuery to RawContactsQuery #271
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!
Move Contact link/unlink extensions to a dedicated API #138
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.
v0.2.4 - New PhoneLookup API, SIM card functions, and lots of improvements and bug fixes!
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 🤞
💡 New features
- New API for specialized matching of phone numbers;
PhoneLookupQuery
#259, documentation - Detect SIM card availability #212, documentation
- Detect SIM card name and number max character limits #201, documentation
- Extensions for getting successfully inserted SimContacts #260, documentation
🛠️ Improvements
- Optimize
Query
andBroadQuery
when not including data fields #249 - Add
Account
toBlankRawContactEntity
#254 - Apply offset and limit manually in all query APIs for devices that do not support them #253
🐞 Bug fixes
- Permissions module extension
xxxWithPermission()
may crash app #143 - Query APIs do not return local contacts in Samsung devices when passing null to
accounts
functions #257 - Unable to delete multiple duplicate SIM contacts in one delete operation #261
- Deleting SIM contacts with name but no number or with number but no name always fails #263
💣 Breaking changes
- Remove
includeBlanks
function fromQuery
,BroadQuery
, andProfileQuery
APIs #251
♻️ Internal refactors
- Replace permissions library Dexter with TedPermissions #101
🧗 Migrating from 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.
🗒️ Full Changelog
🗣️ Discuss this release
Head on over to the v0.2.4 Release Checklist and leave a comment or some reactions 🙏
v0.2.3 - Enhanced delete APIs (PART 2)!
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!
New features
- Delete Blocked Numbers by ID #234
- Delete BlockedNumbers using a WHERE clause #236
- Delete Group by ID #235
- Delete Groups using a WHERE clause #237
- Delete SimContacts by Name and Number #238
Improvements
- Allow BlankRawContacts to be used in most APIs #232
- Add property to all query API results that indicate if the limit is breached #243
Internal refactors
- Prevent non-fatal error logs; "SQLiteLog: (1) no such column:" when using the Query API with a non-null WHERE and includeBlanks set to true #231
Full Changelog
Want to discuss this release?
Head on over to the v0.2.3 Release Checklist and leave a comment!
v0.2.2 - Enhanced delete APIs!
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 ❤️
New features
- Delete Contacts and RawContacts using a WHERE clause #158
- Delete Data using a WHERE clause #225
- Delete Contacts and RawContacts by ID #222
- Delete Data by ID #227
Improvements
- Allow BlankRawContacts to be used in the Delete API #220
Bugfixes
- Delete, ProfileDelete, and DataDelete API results isSuccessful is true even though delete failed #223
New Contributors!
- @yuanhoujun made their first contribution in #221
Full Changelog
Want to discuss this release?
Head on over to the v0.2.2 Release Checklist and leave a comment!
v0.2.1 - New features, improvements, and bug fixes!
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 ❤️
New features
- Share existing contacts #211
Improvements
- Update APIs include function overhaul #209
- Add Java interop for EventDate companion functions #215
- Log execution time of all CRUD APIs #214
- Provide the the RawContact whose photo is used as the primary Contact photo #205
Bugfixes
- Query and BroadQuery APIs' LIMIT and OFFSET functions are not working #217
- RawContactPhoto thumbnail extensions always returns null #204
- Removing a RawContact's Group memberships may not work and may result in the deletion of incorrect arbitrary data #208
New Contributors!!!
Full Changelog
Want to discuss this release?
Head on over to the v0.2.1 Release Checklist and leave a comment!
v0.2.0 - Colossal milestone reached ❤️🔥
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 ⭐️
🖼 Release overview
⚠️ 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 ✌️
💡 New features
Blocked phone numbers
You now have read & write access to blocked numbers 🛑
SIM card access
You are now able to read & write contacts from the SIM card 📱
Contact LOOKUP_KEY
Lookup keys allow you to load contacts even if they get linked/unlinked, perfect for creating shortcuts to contacts from the launcher 🔍
Google Contacts app custom data
The customdata-googlecontacts
module gives you read & write access to custom data managed by the Google Contacts app 🌈
Pokemon custom data
The customdata-pokemon
module allows you to give your contacts their favorite Pokemons 🔥
Role Playing Game (RPG) custom data
The customdata-rpg
module allows you to assign a profession and stats to your contacts ⚔️
⚒️ Improvements
Revamped documentation with MkDocs
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 email
Narrow 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()
🐞 Bug fixes
Fixed Query
returning no results when AND'ing fields of different mime types in WHERE clause
This 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()
Fixed 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()
Fixed GroupsQuery
returning Groups that are pending deletion
Groups 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()
v0.1.10 - Improved DSL for Fields, Redaction, Logging, and Accounts API simplification!
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 ❤️
New nice-to-have goodies! 😋 😋 😋
- #147 Redacted APIs and Entities 🍧
- Howto page: howto-redact-apis-and-entities.md
- #144 Logging support 🍪
- Howto page: howto-log-api-input-output.md
API improvements 🍬
- #152 Simplified API for attaching local RawContacts to an Account (breaking change)
- #153 Simplified API for querying for Accounts (breaking change)
- #150 Improved syntactic Kotlin sugar (DSL) for constructing Where clauses using Fields 🍰🍰
- This is a minor improvement but very cool nonetheless! Essentially, you don't need to import or write the
Fields
inwhere
,include
, andorderBy
functions anymore if you choose not to. There is a separate function now that takes in a function that hasFields
as the receiver. More details in #150 - Howto page: howto-query-contacts-advanced.md#an-advanced-query
- This is a minor improvement but very cool nonetheless! Essentially, you don't need to import or write the
Please 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. 😅
Upgrades
- #164 Set target and compile SDK version from 30 to 31.
Internal refactors
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 🎺
- #148 Lazily evaluating WHERE clause.
- #154 Standardized interface for all CRUD APIs accessible via Contacts instance.
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.
New Contributors!!!
- @alorma made their first contribution in #145
- The initial implementation of our logger is nice and sweet! Thank you and welcome to the will-be-legendary Android library 🔥
- @jayasuryat made their first contribution in #163
- This genius from Reddit reduced 500 lines of code down to 2 lines 🤯 🤯🤯 Truly amazing! This library is lucky to have him as a contributor 🔥 🔥 🔥
Full Changelog
Want to discuss this release?
Head on over to the v0.1.10 Release Checklist and leave a comment!
Migration guide (from v0.1.9 to v0.1.10)
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
I. Simplified API for attaching local RawContacts to an Account
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 🤣
II. Simplified API for querying for Accounts
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 😁
v0.1.9 - Entity hierarchy restructure, bug fixes, and dependency updates
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 🤞
Bugfixes
- #129 Photo thumbnail data is unintentionally included in all query results
- This might have been negatively affecting your app's performance so be sure to adopt this new release if your apps shows all contacts without pagination.
- #135 Inserting or updating an event date to a single digit month or single digit day of month results in duplicate events
- #130 RawContactRefresh, DataRawContact, and BlankRawContactToRawContact extensions could return incorrect RawContact
- #131 DataRefresh extensions could return incorrect data
- #109 DataRefresh extension function throws exception for subclasses of MutableCommonDataEntity
Improvements and breaking changes
- #113 Fix hierarchy of CommonDataEntity
- #123 Use sealed interfaces!
- #115 Create an interface for creating a mutable copy of immutable entities
- #128 Revamped Entity hierarchy!
- #117 Create interface for "new entities" for insert APIs and remove nullable ID properties
- #134 Restructured all entities to either be "Existing" or "New"
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 👍
Dependency updates
I don't think these dependency bumps will affect you but let me know if it does!
- #121 Upgrade Kotlin to 1.6.0 (requires a bunch of other upgrades)
- #122 Remove explicit dependency on the Kotlin stdlib
New Contributors!
We got some new people in the house! 😍
- @DriblingTrex made their first contribution in #127
- Unfortunately, the GitHub account is not showing up in the Contributors list because the commits are not linked to the GitHub account 😭
- @lau1944 created a very important issue; #116. It posed very important questions about the API design. Thank you for raising questions and even making suggestions. Because of you, this release contained a bunch of API improvements. Hence, the title of this release "Entity hierarchy restructure" 🔥
- I still consider you a contributor even if you did not add code! Maybe, I should install the all-contributors bot; https://allcontributors.org...
Full Changelog
Want to discuss this release?
Head on over to the v0.1.9 Release Checklist and leave a comment!
Migration guide (from v0.1.8 to v0.1.9)
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 😃
I. 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
.
II. Mutable entities have been split to either be "existing" or "new".
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.
III. Existing entities now have non-null ID properties.
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.
IV. Getting instances of mutable entities have changed.
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 = "vestrel00@gmail.com"
}
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.
V. Update and delete APIs now only accept existing entities.
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
,...
VI. Insert APIs now only accept new entities.
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
,...
VII. Extension functions in the 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 😁