Releases: vestrel00/contacts-android
v0.5.0 - Build & Dependency Updates
It's been a year since the last release. I have not had any time to work on the things I want to work on for this project. However, I did manage to make some time to update things to ensure that this project is not getting left behind by the times 😁
There are no functional changes in this release. There should not be any impact to you.
♻️ Build/Dependency updates
December 2025 Build & Dependency Updates #370
- AGP 8.13.2
- KGP 2.3.0
- Gradle 8.13
- target/compileSdk 36
- coroutines 1.10.2
🗒️ Full Changelog
🗣️ Discuss this release
Head on over to the v0.5.0 Release Checklist and leave a comment and/or some reactions 🙏 😄
v0.4.0 - New APIs, a minor bugfix, and some deprecations plus a breaking change due to big build/dependency updates
It's been almost a year since the last release. How time flies when life and work gets busy 😅 I'm super hyped to have found some time to get some work done on this precious library 🔥
The main intent with this release was to get the build and dependencies up-to-date so this library does not get left behind in the dust with the fast pace of tech. We are now once again using the cutting-edge versions of AGP, KGP, Gradle, target/compileSdk, coroutines, tedpermissions, mkdocs, and mkdocs material 😁
Aside from that, there's some new features, a minor bugfix, and deprecations and breaking changes that resulted from the big build/dep updates.
⚠️ Proper semantic versioning is not being followed prior to version 1.0.0. This means that minor version bumps may contain breaking changes. More context in #284 (comment)
💡 New features & improvements
- Dedicated API for querying contacts using lookup keys #364, documentation
- Allow overriding common (builtin) data kinds via custom data integration #344, documentation
- Support multiple Notes per RawContact #343, documentation
🐞 Bug fixes
ContactRefreshutil may return null if local contact lookup key changes due to primary display name source update #365
🗝️ Deprecations
- Deprecate Im entity, fields, and related functions (deprecated in Android API 35) #361
- Deprecate SipAddress entity, fields, and related functions (deprecated in Android API 35) #362
💣 Breaking changes
- API's data class' 'copy' methods no longer have public visibility #358
♻️ Build/Dependency updates
- December 2024 Build & Dependency Updates #356
- AGP 8.7.3
- KGP 2.1.0
- Gradle 8.9
- target/compileSdk 35
- coroutines 1.10.1
- tedpermissions 3.4.2
🧗 Migrating from 0.3.2 -> 0.4.0
API's data class' 'copy' methods no longer have public visibility #358
You should not be affected by this, at least that is my hope 🤞
However, for those of you that have been using the copy method of some data classes such as Contact, MutableContact, RawContact, MutableRawContact, Address, MutableAddress, Email, MutableEmail, Phone, MutablePhone, etc... you will no longer be allowed to do that 😅
There is no actual "migration guide". You simply cannot do it anymore. The API has always been designed for this to be the case. Vandolf apologizes if this was not made clear anywhere 🙇. It was simply not strictly enforceable at the language level prior to Kotlin version 2. If you want more context, read the Side note section of #358 🙏
It is recommended to rethink your approach to using this API. You should not need to or be doing it (using copy method).
If you believe that the API should provide a copy method for certain data classes with internal constructors, feel free to start a discussion.
🗒️ Full Changelog
🗣️ Discuss this release
Head on over to the v0.4.0 Release Checklist and leave a comment and/or some reactions 🙏 😄
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
InsertAPI 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
InsertandProfileInsertAPIs #318, documentation - Add option to disable Account validation checks in
MoveRawContactsAcrossAccountsAPI #319, documentation - Add option to disable GroupMembership validation checks in
InsertandProfileInsertAPIs #320, documentation - Add option to disable included field validation checks in all insert, update, and query APIs #321, documentation
🛠️ Improvements
- The
InsertAPI now only queries Groups internally at most once regardless of the quantity of RawContacts being inserted #330 - The
InsertAPI now only queries Accounts internally at most once regardless of the quantity of RawContacts being inserted #332 - The
InsertAPI now skips processing GroupMemberships of RawContacts that specified no GroupMemberships #331 - The
InsertAPI no longer adds an update Options operation for RawContacts that has null Options #333 - Allow
SimContactsInsertandSimContactsUpdateto be cancelled during calculation of max character limits #327
🐞 Bug fixes
InsertAPI includesRawContactsFields(AccountName, AccountType, SourceId) even when not included #325AccountQueryAPI returns all available/visible accounts ifandroid.permission.GET_ACCOUNTSis NOT explicitly granted, regardless of values passed towithTypes#338
💣 Breaking changes
- Included custom data field sets are now nullable #324
Refactors
- Refactor
Contacts.insertSimContactfunction inSimContactsInsert.ktfrom 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_ONLYwhen inserting anyNewDataEntity(e.g. name, email, phone, etc) #306, documentation - Add extensions for checking the value of
ContactsContract.DataColumns.IS_READ_ONLYfor anyExistingDataEntity#307, documentation - Support setting
ContactsContract.CALLER_IS_SYNCADAPTERin 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
AccountsQueryAPI returns Accounts with no sync adapters for Contacts #298ProfileUpdateAPI fails when Contact is provided but not RawContact(s) #302GroupsUpdateAPI 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.requestToBeTheDefaultDialerAppto include additional instructions for API 33 (Tiramisu) and higher #315 - Allow updating and deleting read-only groups when
ContactsContract.CALLER_IS_SYNCADAPTERis set to true #309
💣 Breaking changes
- Remove the
associatedWithandassociatedWithRawContactIdsfunctions from theAccountsQueryAPI andprofilefrom theAccountsAPI #297 - Group
mutableCopyfunction should not return null even if readOnly is true in order to support usages by sync adapters #304 - Rename
readOnlyproperty ofGroupEntitytoisReadOnlyandReadOnlyofGroupsFieldstoGroupIsReadOnly#310 NewCustomDataEntityimplementations now require additional propertyisReadOnly#311- Custom data integrations now require
callerIsSyncAdapter: Booleanparameter #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.isReadOnlyGroupsFields.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);
GroupMembershipEventRelation
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.accountis nullable, allowing you to insert local groupsGroupsQueryAPI 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
QueryandBroadQuerywhen not including data fields #249 - Add
AccounttoBlankRawContactEntity#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
accountsfunctions #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
includeBlanksfunction fromQuery,BroadQuery, andProfileQueryAPIs #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
includeBlankstotrue, 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()









