Contacts Android Versions Save

Android Contacts API Library written in Kotlin with Java interoperability. No more ContentProviders and cursors. Say goodbye to ContactsContract. Build your own contacts app!

0.3.2

3 weeks ago

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

  1. Implement a new Insert API function (commitInChunks) that allows users to insert large numbers of new RawContacts much faster than commit #329, documentation
  2. Add option to disable Account validation checks in Insert and ProfileInsert APIs #318, documentation
  3. Add option to disable Account validation checks in MoveRawContactsAcrossAccounts API #319, documentation
  4. Add option to disable GroupMembership validation checks in Insert and ProfileInsert APIs #320, documentation
  5. Add option to disable included field validation checks in all insert, update, and query APIs #321, documentation

🛠️ Improvements

  1. The Insert API now only queries Groups internally at most once regardless of the quantity of RawContacts being inserted #330
  2. The Insert API now only queries Accounts internally at most once regardless of the quantity of RawContacts being inserted #332
  3. The Insert API now skips processing GroupMemberships of RawContacts that specified no GroupMemberships #331
  4. The Insert API no longer adds an update Options operation for RawContacts that has null Options #333
  5. Allow SimContactsInsert and SimContactsUpdate to be cancelled during calculation of max character limits #327

🐞 Bug fixes

  1. Insert API includes RawContactsFields (AccountName, AccountType, SourceId) even when not included #325
  2. AccountQuery API returns all available/visible accounts if android.permission.GET_ACCOUNTS is NOT explicitly granted, regardless of values passed to withTypes #338

💣 Breaking changes

  1. Included custom data field sets are now nullable #324

Refactors

  1. Refactor Contacts.insertSimContact function in SimContactsInsert.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

https://github.com/vestrel00/contacts-android/compare/0.3.1...0.3.2

🗣️ Discuss this release

Head on over to the v0.3.2 Release Checklist and leave a comment and/or some reactions 🙏 😄

0.3.1

8 months ago

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

  1. Add support for ContactsContract.RawContacts.SOURCE_ID #300, documentation
  2. Add support for ContactsContract.Groups.SOURCE_ID #303, documentation
  3. Add extensions for getting all data kinds of a Contact or RawContact as a list #312, documentation
  4. Support setting ContactsContract.DataColumns.IS_READ_ONLY when inserting any NewDataEntity (e.g. name, email, phone, etc) #306, documentation
  5. Add extensions for checking the value of ContactsContract.DataColumns.IS_READ_ONLY for any ExistingDataEntity#307, documentation
  6. Support setting ContactsContract.CALLER_IS_SYNCADAPTER in all CRUD APIs #308, documentation

🐞 Bug fixes

  1. ExistingContactEntity.setPhotoDirect fails when there is no previous photo #289
  2. Potential ArrayIndexOutOfBoundsException when querying contacts (and Fields.Event.Date is included) #291
  3. Query APIs do not return local contacts in Xiaomi devices when passing null to accounts functions #296
  4. AccountsQuery API returns Accounts with no sync adapters for Contacts #298
  5. ProfileUpdate API fails when Contact is provided but not RawContact(s) #302
  6. GroupsUpdate API allows updating read-only groups, which results in a falsely successful operation #305
  7. Extension fun Activity.selectPhoto() in PhotoPicker.kt does not work in APIs 30 and up #314

🛠️ Improvements

  1. Update documentation for contacts.ui.util.requestToBeTheDefaultDialerApp to include additional instructions for API 33 (Tiramisu) and higher #315
  2. Allow updating and deleting read-only groups when ContactsContract.CALLER_IS_SYNCADAPTER is set to true #309

💣 Breaking changes

  1. Remove the associatedWith and associatedWithRawContactIds functions from the AccountsQuery API and profile from the Accounts API #297
  2. Group mutableCopy function should not return null even if readOnly is true in order to support usages by sync adapters #304
  3. Rename readOnly property of GroupEntity to isReadOnly and ReadOnly of GroupsFields to GroupIsReadOnly #310
  4. NewCustomDataEntity implementations now require additional property isReadOnly #311
  5. 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

https://github.com/vestrel00/contacts-android/compare/0.3.0...0.3.1

🗣️ Discuss this release

Head on over to the v0.3.1 Release Checklist and leave a comment and/or some reactions 🙏 😄

0.3.0

10 months ago

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

  1. Enhanced API for moving any RawContacts to different Accounts (or null account); MoveRawContactsAcrossAccounts #168, documentation

🐞 Bug fixes

  1. When updating groups, the title is always redacted (replaced with asterisks "*") #281
  2. Null pointer exception in Fields.all (on first use in multi-threaded API usage) #286

🛠️ Improvements

  1. Add display name, account, and options properties to RawContactEntity #268
  2. Support for RawContact IDs in AccountsQuery #276

💣 Improvements with breaking changes

  1. Replace AccountsLocalRawContactsUpdate API with a better one #282
  2. Remove Account restrictions for Group, GroupMembership, Relation, and Event #167
  3. Support setting/removing Contact/RawContact Photos as part of Insert and Update API calls #119
  4. Allow different Accounts for each RawContact in insert APIs #270
  5. Move set Contact/RawContact Options functions to be part of Insert and Update APIs #120
  6. Remove BlankRawContact entity #269
  7. Generalize AccountsRawContactsQuery to RawContactsQuery #271
  8. Move Contact link/unlink extensions to a dedicated API #138

♻️ Dependency upgrades

These should not affect you...

  1. 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 groups
  • GroupsQuery 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.

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!

🗒️ Full Changelog

https://github.com/vestrel00/contacts-android/compare/0.2.4...0.3.0

🗣️ Discuss this release

Head on over to the v0.3.0 Release Checklist and leave a comment or some reactions 🙏

0.2.4

1 year ago

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

  1. New API for specialized matching of phone numbers; PhoneLookupQuery #259, documentation
  2. Detect SIM card availability #212, documentation
  3. Detect SIM card name and number max character limits #201, documentation
  4. Extensions for getting successfully inserted SimContacts #260, documentation

🛠️ Improvements

  1. Optimize Query and BroadQuery when not including data fields #249
  2. Add Account to BlankRawContactEntity #254
  3. Apply offset and limit manually in all query APIs for devices that do not support them #253

🐞 Bug fixes

  1. Permissions module extension xxxWithPermission() may crash app #143
  2. Query APIs do not return local contacts in Samsung devices when passing null to accounts functions #257
  3. Unable to delete multiple duplicate SIM contacts in one delete operation #261
  4. Deleting SIM contacts with name but no number or with number but no name always fails #263

💣 Breaking changes

  1. Remove includeBlanks function from Query, BroadQuery, and ProfileQuery APIs #251

♻️ Internal refactors

  1. 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 to true, then nothing will change for you because it is now hard coded to true. If you used to set it to false, also nothing will change for you because it was never working correctly anyways.

🗒️ Full Changelog

https://github.com/vestrel00/contacts-android/compare/0.2.3...0.2.4

🗣️ Discuss this release

Head on over to the v0.2.4 Release Checklist and leave a comment or some reactions 🙏

0.2.3

1 year ago

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

  1. Delete Blocked Numbers by ID #234
  2. Delete BlockedNumbers using a WHERE clause #236
  3. Delete Group by ID #235
  4. Delete Groups using a WHERE clause #237
  5. Delete SimContacts by Name and Number #238

Improvements

  1. Allow BlankRawContacts to be used in most APIs #232
  2. Add property to all query API results that indicate if the limit is breached #243

Internal refactors

  1. 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

https://github.com/vestrel00/contacts-android/compare/0.2.2...0.2.3

Want to discuss this release?

Head on over to the v0.2.3 Release Checklist and leave a comment!

0.2.2

1 year ago

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

  1. Delete Contacts and RawContacts using a WHERE clause #158
  2. Delete Data using a WHERE clause #225
  3. Delete Contacts and RawContacts by ID #222
  4. Delete Data by ID #227

Improvements

  1. Allow BlankRawContacts to be used in the Delete API #220

Bugfixes

  1. Delete, ProfileDelete, and DataDelete API results isSuccessful is true even though delete failed #223

New Contributors!

Full Changelog

https://github.com/vestrel00/contacts-android/compare/0.2.1...0.2.2

Want to discuss this release?

Head on over to the v0.2.2 Release Checklist and leave a comment!

0.2.1

1 year ago

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

  1. Share existing contacts #211

Improvements

  1. Update APIs include function overhaul #209
  2. Add Java interop for EventDate companion functions #215
  3. Log execution time of all CRUD APIs #214
  4. Provide the the RawContact whose photo is used as the primary Contact photo #205

Bugfixes

  1. Query and BroadQuery APIs' LIMIT and OFFSET functions are not working #217
  2. RawContactPhoto thumbnail extensions always returns null #204
  3. Removing a RawContact's Group memberships may not work and may result in the deletion of incorrect arbitrary data #208

New Contributors!!!

Full Changelog

https://github.com/vestrel00/contacts-android/compare/0.2.0...0.2.1

Want to discuss this release?

Head on over to the v0.2.1 Release Checklist and leave a comment!

0.2.0

2 years ago

release-banner

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 video in YouTube

🖼 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

What's new?

Blocked phone numbers

You now have read & write access to blocked numbers 🛑

blocked-numbers

Issue Documentation

SIM card access

You are now able to read & write contacts from the SIM card 📱

sim

Issue Documentation

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 🔍

lookupkey

Issue Documentation

Google Contacts app custom data

The customdata-googlecontacts module gives you read & write access to custom data managed by the Google Contacts app 🌈

googlecontacts

Issue Documentation

Pokemon custom data

The customdata-pokemon module allows you to give your contacts their favorite Pokemons 🔥

pokemon

Issue Documentation

Role Playing Game (RPG) custom data

The customdata-rpg module allows you to assign a profession and stats to your contacts ⚔️

rpg

Issue Documentation

⚒️ Improvements

Level up

Revamped documentation with MkDocs

Didn't like the old documentation website? Me neither. Now, it's prettified, structured, and searchable using Material for MkDocs 🏆

mkdocs

Issue Announcement Documentation

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.

Issue Documentation

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()

Issue Documentation

🐞 Bug fixes

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()

Issue Documentation

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()

Issue Documentation Documentation

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()

Issue Documentation

Fixed possibility of DataContact, RawContactContact, and ContactRefresh extensions returning incorrect result if Contact has been linked/unlinked

The 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)

Issue Issue Issue Documentation

🚨 Breaking changes

Breaking changes

  1. Installing all modules in a single line is only supported when using the dependencyResolutionManagement in settings.gradle
    • Post release panic monologue Documentation
  2. Renamed BroadQuery function whereAnyContactDataPartiallyMatches to wherePartiallyMatches.
    • Issue Documentation

📝 Full Changelog

Changelog

The amount of additions from v0.1.10 to v0.2.0 are... colossal!

Changelog

🎙 Acknowledgements

Give back

It has been many months since this project was featured in Android Weekly. 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 Android Weekly ❤️

🗣 Discuss

Discuss

Have questions, comments, or concerns about this release?

Release checklist

🙏 Please help support this project

Help

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...

Help discussion

❤️ I also shared this colossal release with the r/androiddev community.

✅ This release page has been reviewed and critiqued by the r/opensource community.

0.1.10

2 years ago

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! 😋 😋 😋

  1. #147 Redacted APIs and Entities 🍧
  2. #144 Logging support 🍪

API improvements 🍬

  1. #152 Simplified API for attaching local RawContacts to an Account (breaking change)
  2. #153 Simplified API for querying for Accounts (breaking change)
  3. #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 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 #150
    • Howto page: howto-query-contacts-advanced.md#an-advanced-query

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

  1. #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 🎺

  1. #148 Lazily evaluating WHERE clause.
  2. #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

Full Changelog

https://github.com/vestrel00/contacts-android/compare/0.1.9...0.1.10

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 😁

0.1.9

2 years ago

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

  1. #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.
  2. #135 Inserting or updating an event date to a single digit month or single digit day of month results in duplicate events
  3. #130 RawContactRefresh, DataRawContact, and BlankRawContactToRawContact extensions could return incorrect RawContact
  4. #131 DataRefresh extensions could return incorrect data
  5. #109 DataRefresh extension function throws exception for subclasses of MutableCommonDataEntity

Improvements and breaking changes

  1. #113 Fix hierarchy of CommonDataEntity
  2. #123 Use sealed interfaces!
  3. #115 Create an interface for creating a mutable copy of immutable entities
  4. #128 Revamped Entity hierarchy!
  5. #117 Create interface for "new entities" for insert APIs and remove nullable ID properties
  6. #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!

  1. #121 Upgrade Kotlin to 1.6.0 (requires a bunch of other upgrades)
  2. #122 Remove explicit dependency on the Kotlin stdlib

New Contributors!

We got some new people in the house! 😍

  • @DriblingTrex made their first contribution in https://github.com/vestrel00/contacts-android/pull/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

https://github.com/vestrel00/contacts-android/compare/0.1.8...0.1.9

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 = "[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.

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 😁