Android Contacts API Library written in Kotlin with Java interoperability.

Overview

Contacts, Reborn!

Android Contacts, Reborn banner

JitPack

Android Weekly Kotlin Weekly Java Trends Android Trends Kotlin Trends ProAndroidDev Dev.to

Looking for the easiest way to get full-access to the breadth of Contacts in Android without having to deal with the Contacts Provider and cursors? Well, look no further =)

Whether you just need to get all or some Contacts for a small part of your app (written in Kotlin or Java), or you are looking to create your own full-fledged Contacts app with the same capabilities as the native Android Contacts app, this library has you covered!

Documentation and how-to guides are all available and linked in the repository. You can browse the Howto pages or visit the GitHub Pages. Both contain the same info but the GitHub pages are not guaranteed to be up-to-date. The GitHub wiki hosts the project roadmap. It contains all planned work and release schedules, which are organized using issues, milestones, and projects.

You can also learn more about this library through the articles I wrote about it =)

  1. Android Contacts, Reborn
  2. I spent 3 years writing an Android Contacts API in Kotlin with Java interop. What I’ve learned…

Note: This repo was open-sourced on October 4, 2021. It was private prior to that.

The core library supports;

There are also extensions that add functionality to every core function;

Also included are some pre-baked goodies to be used as is or just for reference;

There are also more features that are on the way!

  1. Blocked phone numbers.
  2. SIM card query, insert, update, and delete.
  3. Read/write from/to .VCF file.
  4. Social media custom data (WhatsApp, Twitter, Facebook, etc).

Installation

First, include JitPack in the repositories list,

repositories {
    maven { url "https://jitpack.io" }
}

To import all modules,

dependencies {
     implementation 'com.github.vestrel00:contacts-android:0.1.8'
}

To import specific modules,

dependencies {
     implementation 'com.github.vestrel00.contacts-android:core:0.1.8'
}

Notice that when importing specific modules/subprojects, the first ":" comes after "contacts-android".

SNAPSHOTs of branches are also available,

dependencies {
     implementation 'com.github.vestrel00:contacts-android:main-SNAPSHOT'
}

This library is a multi-module project published with JitPack; JitPack

Quick Start

To retrieve all contacts containing all available contact data,

val contacts = Contacts(context).query().find()

To simply search for Contacts, yielding the exact same results as the native Contacts app,

val contacts = Contacts(context)
    .broadQuery()
    .whereAnyContactDataPartiallyMatches(searchText)
    .find()

Note that for queries, you will need to add the android.permission.READ_CONTACTS permission to your app's AndroidManifest. Additionally, the user will have to have given your app that permission at runtime (starting with Android Marshmallow). Without permissions being granted, query functions will return empty results. To make permission handling much easier, Kotlin coroutine extensions are available in the permissions module.

That's it! BUT, THAT IS BORING! Let's take a look at something more advanced…

To retrieve the first 5 contacts (including only the contact id, display name, and phone numbers in the results) ordered by display name in descending order, matching ALL of these rules;

  • a first name starting with "leo" 
  • has emails from gmail or hotmail
  • lives in the US
  • has been born prior to making this query
  • is favorited (starred)
  • has a nickname of "DarEdEvil" (case sensitive)
  • works for Facebook
  • has a note
  • belongs to the account of "[email protected]" or "[email protected]"
val contacts = Contacts(context)
    .query()
    .where(
        (Fields.Name.GivenName startsWith "leo") and
                ((Fields.Email.Address endsWith "gmail.com") or (Fields.Email.Address endsWith "hotmail.com")) and
                (Fields.Address.Country equalToIgnoreCase "us") and
                ((Fields.Event.Date lessThan Date().toWhereString()) and (Fields.Event.Type equalTo Event.Type.BIRTHDAY)) and
                (Fields.Contact.Options.Starred equalTo true) and
                (Fields.Nickname.Name equalTo "DarEdEvil") and
                (Fields.Organization.Company `in` listOf("facebook", "FB")) and
                (Fields.Note.Note.isNotNullOrEmpty())
    )
    .accounts(
        Account("[email protected]", "com.google"),
        Account("[email protected]", "com.myspace"),
    )
    .include(Fields.Contact.Id, Fields.Contact.DisplayNamePrimary, Fields.Phone.Number, Fields.Phone.NormalizedNumber)
    .orderBy(ContactsFields.DisplayNamePrimary.desc())
    .offset(0)
    .limit(5)
    .find()

For more info, read How do I get a list of contacts in the simplest way? and How do I get a list of contacts in a more advanced way?

Imagine what this would look like if you use ContactsContract directly. Now, you don't have to! The above snippet is in Kotlin but, like I mentioned, all of the core APIs are usable in Java too (though it won't look as pretty).

Once you have the contacts, you now have access to all of their data!

val contact: Contact
Log.d(
   "Contact",
   """
       ID: ${contact.id}

       Display name: ${contact.displayNamePrimary}
       Display name alt: ${contact.displayNameAlt}

       Photo Uri: ${contact.photoUri}
       Thumbnail Uri: ${contact.photoThumbnailUri}

       Last updated: ${contact.lastUpdatedTimestamp}

       Starred?: ${contact.options?.starred}
       Send to voicemail?: ${contact.options?.sendToVoicemail}
       Ringtone: ${contact.options?.customRingtone}

       Aggregate data from all RawContacts of the contact
       -----------------------------------
       Addresses: ${contact.addressList()}
       Emails: ${contact.emailList()}
       Events: ${contact.eventList()}
       Group memberships: ${contact.groupMembershipList()}
       IMs: ${contact.imList()}
       Names: ${contact.nameList()}
       Nicknames: ${contact.nicknameList()}
       Notes: ${contact.noteList()}
       Organizations: ${contact.organizationList()}
       Phones: ${contact.phoneList()}
       Relations: ${contact.relationList()}
       SipAddresses: ${contact.sipAddressList()}
       Websites: ${contact.websiteList()}
       -----------------------------------
   """.trimIndent()
   // There are also aggregate data functions that return a sequence instead of a list.
)

Each Contact may have more than one of the following data if the Contact is made up of 2 or more RawContacts; name, nickname, note, organization, sip address.

For more info, read How do I learn more about the API entities?

Setup

There is no setup required. It's up to you how you want to create and retain instances of the contacts.core.Contacts(context) API. For more info, read How do I setup the Contacts API?

It is also useful to read How do I learn more about the API entities?

More than enough APIs that will allow you to build your own contacts app!

This library is capable of doing more than just querying contacts. Actually, you can build your own full-fledged contacts app with it!

Let's take a look at a few other APIs this library provides...

To get the first 20 gmail emails ordered by email address in descending order,

val emails = Contacts(context)
    .data()
    .query()
    .emails()
    .where(Fields.Email.Address endsWith  "gmail.com")
    .orderBy(Fields.Email.Address.desc(ignoreCase = true))
    .offset(0)
    .limit(20)
    .find()

It's not just for emails. It's for all data kinds (including custom data).

For more info, read How do I get a list of specific data kinds?

To CREATE/INSERT a contact with a name of "John Doe" who works at Amazon with a work email of "[email protected]" (in Kotlin),

val insertResult = Contacts(context)
    .insert()
    .rawContacts(MutableRawContact().apply {
        name = MutableName().apply {
            givenName = "John"
            familyName = "Doe"
        }
        organization = MutableOrganization().apply {
            company = "Amazon"
            title = "Superstar"
        }
        emails.add(MutableEmail().apply {
            address = "[email protected]"
            type = Email.Type.WORK
        })
    })
    .commit()

Or alternatively, in a more Kotlinized style,

val insertResult = Contacts(context)
    .insert()
    .rawContact {
        setName {
            givenName = "John"
            familyName = "Doe"
        }
        setOrganization {
            company = "Amazon"
            title = "Superstar"
        }
        addEmail {
            address = "[email protected]"
            type = Email.Type.WORK
        }
    }
    .commit()

For more info, read How do I create/insert contacts?

If John Doe switches jobs and heads over to Microsoft, we can UPDATE his data,

Contacts(context)
    .update()
    .contacts(johnDoe.toMutableContact().apply {
        setOrganization {
            company = "Microsoft"
            title = "Newb"
        }
        emails().first().apply {
            address = "[email protected]"
        }
    })
    .commit()

For more info, read How do I update contacts?

If we no longer like John Doe, we can DELETE him from our life,

Contacts(context)
    .delete()
    .contacts(johnDoe)
    .commit()

For more info, read How do I delete contacts?

Note that for insert, update, and delete functions, you will need to add the android.permission.WRITE_CONTACTS and android.permission.GET_ACCOUNTS permissions to your app's AndroidManifest. Additionally, the user will have to have given your app that permission at runtime (starting with Android Marshmallow). Without permissions being granted, these functions will do nothing and return a failed result. To make permission handling much easier, Kotlin coroutine extensions are available in the permissions module.

Threading and permissions

This library provides Kotlin coroutine extensions in the permissions module for all API functions to handle permissions and async module for executing work in background threads.

launch {
    val contacts = Contacts(context)
        .queryWithPermission()
        ...
        .findWithContext()

    val deferredResult = Contacts(context)
        .insert()
        ...
        .commitAsync()
    val result = deferredResult.await()
}

For more info, read How do I use the permissions module to simplify permission handling using coroutines? and How do I use the async module to simplify executing work outside of the UI thread using coroutines?

So, if we call the above function and we don't yet have permission. The user will be prompted to give the appropriate permissions before the query proceeds. Then, the work is done in the coroutine context of choice (default is Dispatchers.IO). If the user does not give permission, the query will return no results.

Extensions for Kotlin Flow and RxJava are also in the v1 roadmap, which includes APIs for listening to Contacts database changes.

Full in-code documentation and Howto guides and samples

The above examples barely scratches the surface of what this library provides. For more in-depth Howtos, visit the howto directory. For a sample app reference, take a look at and run the sample module.

All APIs in the library are optimized!

Some other APIs or util functions out there typically perform one internal database query per contact returned. They do this to fetch the data per contact. This means that if there are 1,000 matching contacts, then an extra 1,000 internal database queries are performed! This is not cool!

To address this issue, the query APIs provided in the Contacts, Reborn library, perform only at least two and at most six or seven internal database queries no matter how many contacts are matched! Even if there are 100,000 contacts matched, the library will only perform two to seven internal database queries (depending on your query parameters).

Of course, if you don't want to fetch all hundreds of thousands of contacts, the query APIs support pagination with limit and offset functions 😎

Cancellations are also supported! To cancel a query amid execution,

.find { returnTrueIfQueryShouldBeCancelled() }

The find function optionally takes in a function that, if it returns true, will cancel query processing as soon as possible. The function is called numerous times during query processing to check if processing should stop or continue. This gives you the option to cancel the query.

This is useful when used in multi-threaded environments. One scenario where this would be frequently used is when performing queries as the user types a search text. You are able to cancel the current query when the user enters new text.

For example, to automatically cancel the query inside a Kotlin coroutine when the coroutine is cancelled,

launch {
    withContext(coroutineContext) {
        val contacts = query.find { !isActive }
    }
    // Or, using the coroutine extensions in the async module...
    val contacts = query.findWithContext()
}

All core APIs are framework-agnostic and works well with Java and Kotlin

The API does not and will not force you to use any frameworks (e.g. RxJava or Coroutines/Flow)! All core functions of the API live in the core module, which you can import to your project all by itself. Don't believe me? Take a look at the dependencies in the core/build.gradle :D

So, feel free to use the core API however you want with whatever libraries or frameworks you want, such as Reactive, Coroutines/Flow, AsyncTask (hope not), WorkManager, and whatever permissions handling APIs you want to use.

All other modules in this library are optional and are just there for your convenience or for reference.

I also made sure that all core functions and entities are interoperable with Java 7+. So, if you were wondering why I’m using a semi-builder pattern instead of using named arguments with default values, that is why. I’ve also made some other intentional decisions about API design to ensure the best possible experience for both Kotlin and Java consumers without sacrificing Kotlin language standards. It is Kotlin-first, Java-second (with love and care).

Modules other than the core module are not guaranteed to be compatible with Java.

Requirements

  • Min SDK 19+
  • Java 7+

Proguard

If you use Proguard and the async and/or permissions, you may need to add rules for Coroutines.

License

Copyright 2021 Contacts Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Support

This is a newly open sourced library with only one contributor so far (me). Don’t expect much support in the beginning. I am only able to work on this (and respond to issues) outside of work hours. This means late nights, weekends, and maybe holidays.

As time passes, hopefully this library gets more and more contributors. At some point, I hope to gain contributors that have become experts on this library to help me support the community by maintaining it and making admin-level decisions.

In any case, create issues for any bugs found and I'll get to it when I get the chance depending on severity of the issue.

Comments
  • Permissions module extension `xxxWithPermission()` may crash app

    Permissions module extension `xxxWithPermission()` may crash app

    Code example:

    When running an insert after, done after queryWithPermission() it crashes the app with the below stacktrace.

      suspend fun create(name: String, phone: String?, email: String?): Long? {
        val rawContact = NewRawContact().apply {
          setName { displayName = name }
          if (phone != null) {
            addPhone {
              number = phone
              type = PhoneEntity.Type.OTHER
            }
          }
          if (email != null) {
            addEmail {
              address = email
              type = EmailEntity.Type.OTHER
            }
          }
        }
        return contacts
          .insertWithPermission()
          .rawContacts(rawContact)
          .commitWithContext()
          .rawContactId(rawContact)
      }
    

    Stacktrace:

        java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=42, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.alorma.tempcontacts/com.karumi.dexter.DexterActivity}: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
            at android.app.ActivityThread.deliverResults(ActivityThread.java:5360)
            at android.app.ActivityThread.handleSendResult(ActivityThread.java:5401)
            at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
            at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2267)
            at android.os.Handler.dispatchMessage(Handler.java:107)
            at android.os.Looper.loop(Looper.java:237)
            at android.app.ActivityThread.main(ActivityThread.java:8167)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
         Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
            at android.view.ViewRootImpl.setView(ViewRootImpl.java:1147)
            at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:471)
            at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:95)
     
    
    bug help wanted 
    opened by alorma 19
  • New API for specialized matching of phone numbers (PhoneLookup)

    New API for specialized matching of phone numbers (PhoneLookup)

    Problem

    As @mianaliasjad pointed out in https://github.com/vestrel00/contacts-android/discussions/258#discussioncomment-3546286, the BroadQuery API currently does not match phone numbers that uses a country code (e.g. "+923123456789") when a search string is provided that does not use the country code (e.g. 03123456789).

    The following will not return the contact;

    val contact = Contacts(context)
            .broadQueryWithPermission()
            .match(BroadQuery.Match.PHONE)
            .wherePartiallyMatches( "03123456789" )
            .limit(1)
            .find()
            .firstOrNull()
    

    This is due to (intentionally) NOT using ContactsContract.PhoneLookup.CONTENT_FILTER_URI for the matching process because it's purpose is to emulate the behavior of the other major Contacts app, which it is doing.

    More info in the discussion #258

    Solution

    Implement a new API, which I might call PhoneLookup (name not final), that uses ContactsContract.PhoneLookup.CONTENT_FILTER_URI for the matching process.

    • [x] Docs page
    • [x] Add usages to cheatsheet
    • [x] Implement permissions extensions
    • [x] Implement async extensions
    • [x] Update gh-pages
    enhancement 
    opened by vestrel00 16
  • It's still there after delete  rawcontacts, it's wired!

    It's still there after delete rawcontacts, it's wired!

    Use this code to delete raw contacts, it returns true.

    val contacts = Contacts(mContext)
    val blankRawContacts = contacts.accounts().queryRawContacts().where { RawContactsFields.Id `in` request.ids }.find()
    val rawContacts = blankRawContacts.mapNotNull { it.toRawContact(contacts) }
    val isSuccessful = contacts.delete().rawContacts(rawContacts).commit().isSuccessful
    

    Then use this code to query all raw contacts

       fun getAllContacts(context: Context): List<ContactBasicInfo> {
            val contacts =
                Contacts(context).query().orderBy(ContactsFields.DisplayNamePrimary.asc()).find()
    
            val rawContacts = mutableListOf<ContactBasicInfo>()
    
            contacts.forEach { contact ->
                contact.rawContacts.forEach { rawContact ->
                    val contactBasicInfo = ContactBasicInfo(
                        id = rawContact.id,
                        contactId = contact.id,
                        displayNamePrimary = rawContact.name?.displayName,
                        phoneNumber = rawContact.phones.sortedByDescending { it.isSuperPrimary }
                            .sortedByDescending { it.isPrimary }
                            .map { it.number }
                            .joinToString(separator = ",")
                    )
                    rawContacts.add(contactBasicInfo)
                }
            }
    
            return rawContacts
        }
    

    Returned results still contain the raw contacts deleted.

    opened by yuanhoujun 16
  • Add Logger ability to contacts API

    Add Logger ability to contacts API

    This PR adds Logger to Contacts API, so it can be used to track features like query, insert, ...

    I not plan to add on this PR the logs to every feature, but my idea is to add it later following up a discussion with you about what and how should be tracked.

    Code example with android logger:

    val contacts = Contacts(
       context = this@Activity,
       logger = AndroidLogger(),
    )
    

    Code example with custom logger:

    val contacts = Contacts(
       context = this@Activity,
       logger = object: Logger() {
          override fun log(message: String) {
              Timber.w() ...
          }
       },
    )
    

    This PR closes #144

    enhancement 
    opened by alorma 16
  • Query where

    Query where "and" operation with multiple data fields that have different mimetypes are not working

    Code example:

    Using filters:

    Captura de pantalla 2021-12-21 a las 19 54 07
    contacts
          .query()
          .where(filters whereAnd { it.isNotNullOrEmpty() })
          .include(
            Fields.Contact.Id,
            Fields.Contact.DisplayNamePrimary,
            Fields.Email.Address,
            Fields.Phone.Number,
            Fields.Address.FormattedAddress,
          )
    .findWithContext()
    

    It returns an empty list, and I have contacts with both, email and phone...

    bug 
    opened by alorma 14
  • Jitpack release artifacts for 0.2.0 is

    Jitpack release artifacts for 0.2.0 is "not working"...

    So, the 0.2.0 release in JitPack is showing βœ… for successful deployment.

    Screen Shot 2022-03-28 at 10 58 52 AM

    However, today I did a sanity check by attempting to pull it into a new project. The build fails with...

    Screen Shot 2022-03-28 at 9 43 31 AM

    Looking at the JitPack logs...

    Screen Shot 2022-03-28 at 10 16 12 AM

    It seems like it should have succeeded...

    This really sucks. I'll have to figure this out QUICKLY before people really try to download it and realize 0.2.0 will not be downloadable.

    Hopefully Jitpack will let me reuse the 0.2.0 tag.

    IF JITPACK DOES NOT ALLOW ME TO REBUILD 0.2.0...

    I really don't want to do this... Man.. This really sucks... What I'll do,

    • [ ] Fix the issue.
    • [ ] Create the 0.2.1 tag.
    • [ ] Update the README to point to the 0.2.1 release.
    • [ ] Delete the release from JitPack.
    • [ ] Update the release page to use the 0.2.1 tag.
    • [ ] Update any mentions of 0.2.0 to 0.2.1 in the release page (including images/GIFs)
    • [ ] Update the gh-pages
    • [ ] Update links to release page in YouTube
    • [ ] Update link to release page in Reddit
    • [ ] Send Android Weekly the updated link to the release page

    Unfortunately, the video that shows 0.2.0 is already out there... FML. I'll leave it as is and just hope that people will ignore that version in the video and use 0.2.1 instead.

    ENSURE that mentions and links to the 0.2.0 release page EVERYWHERE (except the video) should be replaced with 0.2.1

    build release 
    opened by vestrel00 13
  • Blocked phone numbers

    Blocked phone numbers

    The Android 7.0 release introduces a BlockedNumberProvider content provider that stores a list of phone numbers the user has specified should not be able to contact them via telephony communications (calls, SMS, MMS). See https://source.android.com/devices/tech/connect/block-numbers

    We should create an API that allows consumers to query, insert, update, and delete blocked phone numbers. The API may look something like;

    val blockedPhones: List<BlockedNumber> = Contacts(context).blockedNumbers().query().find()
    

    The APIs we'll create may be; BlockedNumberQuery, BlockedNumberInsert, BlockedNumberUpdate, BlockedNumberDelete.

    • [x] core
    • [x] ~~permissions~~ (there are no permissions involved, just privileges)
    • [x] async
    • [x] Howto pages
    • [x] Update README
    • [x] Provide demo video as a comment on this issue
    enhancement 
    opened by vestrel00 8
  • vararg Where clause

    vararg Where clause

    Demo code:

    suspend fun loadAllContacts(
        filters: List<AbstractDataFieldSet<DataField>>
      ): List<Contact> = withContext(Dispatchers.IO) {
        Contacts(context)
          .query()
          .where(...)
          .find()
      }
    }
    

    I have a UI that wil allow to filter contacts. based on some given properties, on this case if contact has phone, email or phone AND email.

    Is there any way to have multiple where causes provided by a list of fields? my idea is something like this:

    suspend fun loadAllContacts(
        filters: List<AbstractDataFieldSet<DataField>>
      ): List<Contact> = withContext(Dispatchers.IO) {
        Contacts(context)
          .query()
          .where(
                allNonNull(
                      filters.toTypedArray()
                )
          )
          .find()
      }
    }
    

    Does it make sense?

    question 
    opened by alorma 7
  • Is it possible to obtain a unique identifier using this library, that works across devices?

    Is it possible to obtain a unique identifier using this library, that works across devices?

    Hello!

    Is it possible to obtain a unique identifier using this library, that works across devices?

    My use case is to create an app to automatically delete contacts after a given time (temporal contacts), and I wanted to do it online so user can keep it on multiple devices...

    Is it even possible? Is there a unique "identifier" for contact across devices?

    question 
    opened by alorma 7
  • Realme RMX2151 (Realme 7) - LIMIT and OFFSET APIs are not working

    Realme RMX2151 (Realme 7) - LIMIT and OFFSET APIs are not working

    I want to implement pagination for showing the contacts to those user who have thousand's of Contacts, I wanted to use the limit and offset functionality, but they don't work in version 0.2.1 and also 0.2.2, can you please fix them

    Screenshot 2022-06-28 at 12 15 11 ? bug 
    opened by atharv-appyhigh 5
  • Shorten usage of Fields in include, where, and orderBy API functions

    Shorten usage of Fields in include, where, and orderBy API functions

    Problem

    The suggested usage of Fields in the library results in too long statements due to redundancy of using Fields.. For example,

    .where((Fields.Email.Address startsWith "a") and (Fields.Email.Address endsWith "gmail.com"))
    

    While this is the only way Fields can be used by Java users, we can shorten it for Kotlin users. Actually, Kotlin users can already remove the redundant calls to Fields. this by using run on it,

    .where(Fields.run { (Email.Address startsWith "a") and (Email.Address endsWith "gmail.com") })
    

    However, it is not obvious and requires consumers to still use Fields. once.

    Consumers could also import all properties of Fields. However, it could cause a lot of name clashes between the name of the field (e.g. Email) and the name of the entity (e.g. Email).

    Solution

    Remove the need for consumers to use Fields.run (and other equivalent field sets), in include, where, and orderBy API functions.

    Usage for Kotlin users would look like,

    .where{ (Email.Address startsWith "a") and (Email.Address endsWith "gmail.com") }
    

    This also gets rid of the need for consumers to know that they should be using Fields to begin with, even though it is mentioned in the documentation.

    We can take this a step further by providing extensions for a field sets,

    .where{ Email { (Address startsWith "a") and (Address endsWith "gmail.com") } }
    

    And even further by providing extensions for individual fields,

    .where{ Email.Address { startsWith("a") and endsWith("gmail.com") } }
    

    Checklist

    • [x] Add the extra functions to all APIs
    • [x] Update sample Kotlin code in API class docs
    • [x] Update all relevant howto pages
    • [x] Update README
    • [x] Update medium and dev.to blog code snippets
    • [x] Update sample app usages
    enhancement 
    opened by vestrel00 5
  • [WIP] #167 Remove Account restrictions for Group, GroupMembership, Relation, and Event

    [WIP] #167 Remove Account restrictions for Group, GroupMembership, Relation, and Event

    This is a Work In Progress (WIP) branch for #167 ! This PR is NOT ready for merge.

    And, yes... I will squash all commits into a single commit. I do not like to bloat the number of commits in the main branch with lots of BS commits 😑

    enhancement breaking change 
    opened by vestrel00 0
  • Q1 2023 Dependency Upgrades

    Q1 2023 Dependency Upgrades

    It's time to look through all of the library dependencies and upgrade them all to latest!

    • [ ] targetSdk
    • [ ] compileSdk
    • [ ] com.android.tools.build:gradle
    • [ ] org.jetbrains.kotlin:kotlin-gradle-plugin
    • [ ] gradle-wrapper.properties
    • [ ] io.github.ParkSangGwon:tedpermission-coroutine
    • [ ] org.jetbrains.kotlinx:kotlinx-coroutines-core
    • [ ] org.jetbrains.kotlinx:kotlinx-coroutines-android

    Once all versions have been upgraded,

    • [ ] Run lint and fix any compile errors and warnings (such as deprecations)
    enhancement build 
    opened by vestrel00 0
  • Improve developer experience regarding integrating custom data

    Improve developer experience regarding integrating custom data

    Currently, creating queries for custom data is well documented, which is cool and definitely above average for an opensource project, thanks for that!

    The process is still quite long however, and could IMO be supported by e.g. using macros (my knowledge of kotlin metaprogramming is low :-(), e.g.

    @CustomContactData(ZeroOrOne)
    class WhatsAppCallContact(): CustomContactTable {
      val mimeType = MimeType.Custom("vnd.android.cursor.item/vnd.com.whatsapp.video.call")
    }
    

    For the gender case, I'd imagine

    @CustomContactData(ZeroOrOne)
    class Gender(): CustomContactTable {
          override val isBlank: Boolean
            get() = propertiesAreAllNullOrBlank(type)
      // I don't know if Kotlin supports this
      type Type = CustomGenderType
    }
    

    or as an alternative, an android-studio IDE plugin would also work. I think there's even a tmbundle format for snippets, which a lot of editors can use.

    enhancement help wanted 
    opened by reactormonk 3
  • Work profile contacts

    Work profile contacts

    I should look into how the current library implementation works with work profiles.

    Then add additional APIs and/or make necessary changes to support work profile contacts.

    Official docs in https://developer.android.com/work/contacts

    enhancement 
    opened by vestrel00 0
  • API for moving non-local RawContacts to different Accounts (or no Account)

    API for moving non-local RawContacts to different Accounts (or no Account)

    Currently, the AccountsLocalRawContactsUpdate API allows library users to move local RawContacts to an Account because the default Android Open Source Project (AOSP) Contacts app supports it.

    However, I recently started playing around with the Google Contacts app. It turns out that it is possible to move non-local RawContacts to different Accounts and even back into local/device-only RawContacts!

    google-contacts-moving-to-different-accounts

    I will need to investigate how the Google Contacts app does this because there were a lot of issues when I first tried to implement it a few years ago as I noted in the DEV_NOTES.

    **SyncColumns modifications**
    
    This library supports modifying the `SyncColumns.ACCOUNT_NAME` and `SyncColumns.ACCOUNT_TYPE` of the
    RawContacts table in some cases only. In some cases does not work as intended and produces unwanted
    side-effects. It probably has something to do with syncing with remote servers and local Account /
    sync data not matching up similar to errors on network requests if the system time does not match
    network time.
    
    The motivation behind changing the Account columns of the RawContacts table rows is that it would
    allow users to;
    
    - Associate local RawContacts (those that are not associated with an Account) to an Account,
      allowing syncing between devices.
    - Dissociate RawContacts from their Account such that they remain local to the device and not synced
      between devices.
    - Transfer RawContacts from one Account to another.
    
    When modifying the SyncColumns directly, the first works as intended. The second works with some
    unwanted side-effects. The third does not work at all and produces unwanted side-effects.
    
    These are the behaviors that I have found;
    
    - Associating local RawContact A to Account X.
        - Works as intended.
        - RawContact A is now associated with Account X and is synced across devices.
    - Dissociating RawContact A (setting the SyncColumns' Account name and type to null) from Account X.
        - Partially works with some unwanted-side effects.
        - Dissociates RawContact A from the device but not other devices.
        - RawContact A is no longer visible in the native Contacts app UNLESS it retains the group
          membership to at least the default group from an Account.
        - At this point, RawContact A is a local contact. Changes to this local RawContact A will not be
          synced across devices.
        - If RawContact A is updated in another device and synced up to the server, then a syncing
          side-effect occurs because the RawContact A in the device is different from the RawContact A
          in the server. This causes the Contacts Provider to create another RawContact, resulting in a
          "duplicate". The two RawContact As may get aggregated to the same Contact depending on how
          similar they are.
        - If local RawContact A is re-associated back to Account X, it will still no longer be synced.
    - Associating RawContact A from original Account X to Account Y.
        - Does not work and have bad side-effects.
        - No change in other devices.
        - For Lollipop (API 22) and below, RawContact A is no longer visible in the native Contacts app
          and syncing Account Y in system settings fails.
        - For Marshmallow (API 23) and above, RawContact A is no longer visible in the native Contacts
          app. RawContact A is automatically deleted locally at some point by the Contacts Provider.
          Syncing Account Y in system settings succeeds.
    
    Given that associating originally local RawContacts to an Account is the only thing that actually
    works, it is the only function that will be exposed to consumers.
    
    If consumers want to transfer RawContacts from one Account to another, they can create a copy of a
    RawContact associated with the desired Account and then delete the original RawContact. Same idea
    can be used to transform an Account-associated RawContact to a local RawContact. Perhaps we can
    implement some functions in this library that does these things? We won't for now because the native
    Contacts app does not support these functions anyways. It can always be implemented later if the
    community really wants.
    
    Here are some other things to note.
    
    1. The Contacts Provider automatically creates a group membership to the default group of the target
       Account when the account changes. This occurs even if the group membership already exists
       resulting in duplicates.
    2. The Contacts Provider DOES NOT delete existing group memberships when the account changes.
       This has to be done manually to prevent duplicates.
    

    Problem

    The Google Contacts app supports changing the android.accounts.Account for RawContacts that are already associated with an Account whereas this library does not (because implementation was modeled after the AOSP Contacts).

    Solution

    Support changing the android.accounts.Account for RawContacts that are already associated with an Account!

    I should consider just refactoring AccountsLocalRawContactsUpdate be less restrictive and allow for null Accounts (maybe rename it to AccountsRawContactsUpdate). If things just work, this should actually simplify internal implementation (depending on how Google Contacts app does it).

    THIS SHOULD BE DONE AFTER #167

    • [ ] Support changing Account of non-local RawContact to a different Account.
    • [ ] Support removing Account of non-local RawContact .
    • [ ] Ensure changing Account of local RawContact to a non-null Account still works as expected.
    • [ ] Check behavior in Samsung device.
    • [ ] Update all in-code documentation.
    • [ ] Update README.
    • [ ] Update DEV_NOTES.
    • [ ] Update howto pages.
    enhancement refactor 
    opened by vestrel00 1
  • Remove Account restrictions for Group, GroupMembership, Relation, and Event

    Remove Account restrictions for Group, GroupMembership, Relation, and Event

    Currently, the library has artificially placed some "Account restrictions" on certain entities.

    • Insert and update operations for these data kinds of a local RawContact (not associated with an Account) are not allowed (ignored); GroupMembership, Relation, and Event.
    • Query, Insert, and update operations for Group entities require an android.accounts.Account.

    The library imposes these restrictions because the native/default Contacts app does the same as described in my documentation;

    Screen Shot 2022-01-07 at 6 29 02 PM

    Problem

    I recently started playing around with the Google Contacts app. So... it turns out that the Google Contacts app DOES NOT impose these Account restrictions whereas the default Android Open Source Project (AOSP) Contacts app does.

    | AOSP Contacts app | Google Contacts app | |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Screen Shot 2022-01-07 at 6 49 23 PM | Screen Shot 2022-01-07 at 6 49 00 PM |

    The Contacts Provider AND Google Contacts app does not impose these Account restrictions whereas the AOSP Contacts app does.

    Solution

    Remove these Account restrictions! Let the users of the library determine what to do instead of imposing these artificial restrictions on them!

    • [ ] Allow query, insert, and update operations for Groups that have a null Account ("local groups").
    • [ ] Allow insert and update operations for GroupMembership data kinds belonging to local RawContacts.
    • [ ] Allow insert and update operations for Event data kinds belonging to local RawContacts.
    • [ ] Allow insert and update operations for Relation data kinds belonging to local RawContacts.

    Other things to do

    • [ ] Update sample app. Do not hide these fields in the UI anymore!
    • [ ] Test behavior in a non-Samsung phone (a pixel device/emulator).
    • [ ] Test behavior in a Samsung phone where the "local account" is not null but rather vnd.sec.contact.phone.
    • [ ] Update all in-code documentation. Go through all of grep --include="*kt" -ri "account" .
    • [ ] Update docs. Go through all of grep --include="*md" -ri "account" .
    • [ ] Update dev-notes.
    • [ ] Update README.
    • [ ] Update howto pages.
    • [ ] Provide a migration guide as a comment in this issue so that it can be copy-pasted into the release notes.
    enhancement breaking change 
    opened by vestrel00 0
Releases(0.2.4)
  • 0.2.4(Sep 9, 2022)

    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

    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.

    ♻️ Internal refactors

    1. Replace permissions library Dexter with TedPermissions #101

    πŸ—’οΈ 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 πŸ™

    Source code(tar.gz)
    Source code(zip)
  • 0.2.3(Jul 14, 2022)

    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!

    Source code(tar.gz)
    Source code(zip)
  • 0.2.2(Jun 16, 2022)

    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!

    • @yuanhoujun made their first contribution in https://github.com/vestrel00/contacts-android/pull/221

    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!

    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Jun 13, 2022)

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

    • @jafri-zz made their first contribution in https://github.com/vestrel00/contacts-android/pull/216

    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!

    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Mar 25, 2022)

    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.

    Source code(tar.gz)
    Source code(zip)
  • 0.1.10(Jan 8, 2022)

    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 😁

    Source code(tar.gz)
    Source code(zip)
  • 0.1.9(Dec 18, 2021)

    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 😁

    Source code(tar.gz)
    Source code(zip)
  • 0.1.8(Nov 26, 2021)

    This release contains bug fixes, optimizations, API changes (for a cleaner API), and Java support enhancements. All of these things were discovered while writing Howto pages ❀️

    Bug Fixes

    1. #108 The GroupsUpdate API no longer allows groups in the same Account to have duplicate titles.
      • This is the same behavior as the GroupsInsert API.

    Optimizations

    1. Internally, the GroupsInsert API now only retrieves groups for Accounts that are related to the groups being inserted.
      • This is a minor performance improvement, but an improvement nonetheless!

    Non-breaking API Changes

    1. Exposed the CustomDataRegistry.allFields() function to consumers.
      • This allows consumers to include all integrated custom fields in one call explicitly in query, insert, and update APIs.

    Breaking API Changes

    1. The Accounts API is now accessible via the Contacts API.
      • Previously, to access the Accounts API, Accounts(context).
      • Now, to access the Accounts API, Contacts(context).accounts().
      • This streamlines all core APIs to be accessible via the Contacts API.
    2. The GlobalCustomDataRegistry has been removed.
      • The same has been applied to handle name and gender custom data modules.
      • This streamlines custom data integration to use only one instance of CustomDataRegistry per instance of Contacts.
      • This also makes things less error-prone.
    3. All extension functions in the contacts.core.util package and the corresponding functions in the async module now take a reference to the Contacts API instance instead of Context and CustomDataRegistry.
      • The same has been applied to handle name and gender custom data modules.
      • This streamlines all extension functions to use a reference to the Contacts instead of separate references to a Context and a CustomDataRegistry. Less arguments, the better!
      • This also makes things less error-prone.

    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 😁

    Java support enhancements

    1. Added @JvmStatic fun all() and @JvmStatic fun forMatching() for all object FieldSet to give Java users first-class access to all and forMatching properties in a similar way as Kotlin users.
      • The same has been applied to handle name and gender custom data modules.

    Documentation

    1. Cleaned up some in-code KDocs.
    2. Wrote up a bunch more howto pages.

    New Contributors!

    I'm excited to have the very first (other than @vestrel00) contributor to this will-be-legendary library!!! πŸ”₯πŸ”₯πŸ”₯πŸ”₯

    • @misha-n-ka made their first contribution in https://github.com/vestrel00/contacts-android/pull/107

    FYI ❀️

    You may have noticed that most of the above changes do not have a corresponding issue, which would have provided a lot more context and a paper trail to the related code changes. I need to keep in mind that this is now an open source project. Other people are looking at my work now, and perhaps even consuming it!

    Therefore, in order to uphold some form of professionalism, all changes in future releases will have issues! It will also help in creating these release notes with the "Auto-generate release notes" button.

    Full Changelog

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

    Source code(tar.gz)
    Source code(zip)
  • 0.1.7(Oct 29, 2021)

    This release is full of big API improvements!

    1. Fixed queries inadvertently including unspecified fields.
      • Previously, some fields were being inadvertently included in query results due to the generic column names set by the ContactsContract. That is no longer the case!
    2. Added include functions to insert and update APIs.
      • Similar to query APIs, insert and update APIs now provide an includes function that allows you to specify which fields should be a part of the insert or update operation. All other fields are ignored.
    3. Added Contact.hasPhoneNumber field and wrote up documentation about matching contacts with no particular kind of data.

    More info in How do I include only the data that I want?.

    Full Changelog: https://github.com/vestrel00/contacts-android/compare/0.1.6...0.1.7

    Source code(tar.gz)
    Source code(zip)
  • 0.1.6(Oct 16, 2021)

    Fixed Event date not handling no-year, minor code prettification, and wrote up several howto pages.

    • Fixed Event.date not handling no-year option.
    • MutableCommonDataEntity implementations are now using property delegate for primaryValue.
    • Wrote up howto pages for basic and advanced queries and others.
    Source code(tar.gz)
    Source code(zip)
  • 0.1.5(Oct 11, 2021)

    Added groupMemberships aggregation extension function in ContactData.

    The groupMemberships aggregation extension functions were previously commented out because it may be unsafe for consumers to use as it may cause confusion to those unfamiliar with how Groups and GroupMemberships worked.

    However, having these extensions may prove to be useful when showing group memberships of multiple raw contacts to matching Groups from different accounts.

    a lot of documentation in the extension function is provided to minimize risk of misuse and confusion =)

    Source code(tar.gz)
    Source code(zip)
  • 0.1.4(Oct 11, 2021)

    Exposed photoUri and photoThumbnailUri in ContactsEntity.

    The photoUri and photoThumbnailUri in ContactsEntity we’re previously commented out to force users to use the ContactPhoto extensions. However, it has now been uncommented as it may be useful for showing contact photos in a list/recycler view…

    Source code(tar.gz)
    Source code(zip)
  • 0.1.3(Oct 11, 2021)

    Fixed query order by functions causing lint warnings for Java consumers.

    Fixes lint raising an issue for API query functions of the form fun x(vararg y: Type)

    The fix was to add the @SafeVarargs function annotation

    Source code(tar.gz)
    Source code(zip)
  • 0.1.2(Oct 11, 2021)

    Fixed Jitpack artifacts not being generated.

    The previous release was not generating the artifacts when publishing to maven….

    It’s fixed now though =)

    Source code(tar.gz)
    Source code(zip)
  • 0.1.1(Oct 11, 2021)

    Java version is now back to 7 and lots more documentation

    The Java version was previously set (recently) to Java 8 for no reason. This release reverts that change. Java version is now back to 7!

    Plus some more documentation!

    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Oct 11, 2021)

    Ready for public alpha development .

    Most, if not all, v1 functions have been implemented and manually tested by me.

    0.1.x will be focused on making sure everything is documented. This includes writing howto guides for everything!

    Source code(tar.gz)
    Source code(zip)
Owner
Vandolf Estrellado
Started off as web full stack engineer using Python/Django AWS. Now an Android specialist using Kotlin/Java!
Vandolf Estrellado
Allows usage of vCard resources with the Android contacts provider

vcard4android vcard4android is an Android library that brings together VCard and Android. It's a framework for parsing and generating VCard resources

bitfire web engineering 7 Dec 15, 2022
A modular and portable open source XMPP client library written in Java for Android and Java (SE) VMs

Smack About Smack is an open-source, highly modular, easy to use, XMPP client library written in Java for Java SE compatible JVMs and Android. Being a

Ignite Realtime 2.3k Dec 28, 2022
A modular and portable open source XMPP client library written in Java for Android and Java (SE) VMs

Smack About Smack is an open-source, highly modular, easy to use, XMPP client library written in Java for Java SE compatible JVMs and Android. Being a

Ignite Realtime 2.3k Dec 21, 2021
ArchGuard Scanner for scan Git change history, scan source code by Chapi for Java, TypeScript, Kotlin, Go..、Java bytecode use for JVM languages, scan Jacoco test coverage.

Arch Scanner Requirements: JDK 12 Scanner: scan_git - Git commit history scan scan_jacoco - Jacoco scan scan_bytecode - for JVM languages known issues

ArchGuard 27 Jul 28, 2022
This is a Reddit client on Android written in Java

Infinity-For-Reddit This is a Reddit client on Android written in Java. It does not have any ads and it features clean UI and smooth browsing experien

null 2.6k Jan 9, 2023
The Android application is a list of the most popular TV series written in Java using the mvvm pattern.

Tv show application The Android application is a list of the most popular TV series written in Java using the mvvm pattern. This project was written f

rutikeyone 0 Dec 26, 2021
A simple android app written in Kotlin to get defination of word entered from Free Dictionary API.

FlashCard A simple android app written in Kotlin to get defination of word entered from Free Dictionary API. Problem Statement Many of students don't

RITEK ROUNAK 3 Jan 20, 2022
A Minecraft Kit API written in Kotlin

hglabor-kits Dependency The library is available on Maven Central, add the following dependencies: implementation("net.axay:hglabor-kits:$version") hg

Jakob K 8 Apr 8, 2022
Retracer is a high performance, and near realtime REST API which used for Java/Android stack trace retracing by R8

Retracer is a high performance, and near realtime REST API which used for Java/Android stack trace retracing by R8 Getting Started docker

Johnson Lee 3 Aug 21, 2022
Gender Checker app built using Kotlin, MVVM, Genderize.io API. Take this as a reference for MVVM and Genderize.io API πŸš€

Gender-Checker ?? Gender Checker app built using Kotlin, MVVM, Genderize.io API Enter a name and the app will guess the gender ?? ✨ Highligts: API : G

Jai Keerthick 0 Jan 5, 2022
Utility Android app for generating color palettes of images using the Palette library. Written in Kotlin.

Palette Helper is a simple utility app made to generate color palettes of images using Google's fantastic Palette library. It's mostly a for-fun pet p

Zac Sweers 154 Nov 18, 2022
Utility Android app for generating color palettes of images using the Palette library. Written in Kotlin.

Palette Helper is a simple utility app made to generate color palettes of images using Google's fantastic Palette library. It's mostly a for-fun pet p

Zac Sweers 154 Nov 18, 2022
Samples demonstrating the features and use of Koala Plot, a Compose Multiplatform based charting and plotting library written in Kotlin.

Koala Plot Samples This repository houses samples demonstrating the use and features of Koala Plot libraries. How to run Build koalaplot-core with the

Koala Plot 6 Oct 18, 2022
A full-stack application showing the power πŸ’ͺ of KOTLIN. Entire android app + backend Apis written in Kotlin πŸ”₯

Gamebaaz ?? A full-stack application showing the power ?? of KOTLIN. Entire android app + backend Apis written in Kotlin ?? Android Backend Jetpack Co

Sarnava Konar 85 Nov 17, 2022
OpenWeatherMap-API-Demo - Demo Android Application for OpenWeatherMap API

WeatherForecast Demo Android Application for OpenWeatherMap API Table of Content

Rashid Hussain 0 Jul 10, 2022
Android App to test API requests against the GitHub API.

Reproducing an issue with GitHub's IP allowlist feature GitHub provides a feature for Enterprise orgs to define an IP allowlist. Requests to the GitHu

Tom Hombergs 2 Aug 7, 2022
This is a Movie API app in which data is fetched online from the TMDB site using API authentication.

Movie-API This is a Movie API app in which data is fetched online from the TMDB site using API authentication. MVVM model is used for Database Managme

Atishay Jain 1 Dec 4, 2021
New-SplashScreen-API - SplashScreen API Implementation Sample

SplashScreen API Implementation Sample Installation - Usage <style name="Theme.A

Arda KazancΔ± 0 Jan 3, 2022
Github-Api-Pagination-Example - Pagination 3 Example using Github Api

Github-Api-Pagination Pagination 3 Example using Github Api Tech Stack 100% Kotl

Anggoro Beno Lukito 2 Aug 22, 2022