QuisApp / flutter_contacts

MIT License
83 stars 138 forks source link

Feature request: Get contacts related to an account #5

Open ReveredMachine opened 3 years ago

ReveredMachine commented 3 years ago

It would be nice to have a possibility to get all contacts that are assigned to an account.

Like this:

/// All contacts for the provided account will be fetched.
/// In case of no provided account, all contacts will be fetched
static Future<List<Contact>> getContacts(
          {bool withPhotos = false,
          bool sorted = true,
          Account account}
)
joachimvalente commented 3 years ago

Hi! Thank you for the suggestion. What would be the use case, concretely? One thing to note is that Account is an Android-only thing.

ReveredMachine commented 3 years ago

The use case is, to get a list of contacts having

With this I can get all the contacts I own and all the visible ones.

My idea was, If an account is

joachimvalente commented 3 years ago

Ah, that makes sense. The plugin doesn't yet support querying/searching, but you can always filter after the fact. For example:

final contacts = (await FlutterContacts
    .getContacts())
    .where((c) => c.accounts.any((a) => a.type == 'com.myapp'))
    .toList();

That said there's no option to include non-visible contacts on Android. We could just add one so that you could write:

final contacts = (await FlutterContacts
    .getContacts(includeNonVisibleOnAndroid: true))
    .where((c) => c.accounts.any((a) => a.type == 'com.myapp'))
    .toList();

Would that work for you?

I guess one issue with that approach is that you'd get the properties from all raw accounts, not just your account.

ReveredMachine commented 3 years ago

Hi,

yes that would work for me and yes, with that approach it is much easier to read all contacts (not only the own ones) with flutter. Maybe you can put a hint to the documentation to use the includeNonVisibleOnAndroid with care.

For the IOS side I found the CNContainer class, do you know if there is a relation to the Android-Accounts ?

joachimvalente commented 3 years ago

Hmm, possibly! Thanks for flagging. I'll give it a try and if it indeed is the equivalent of raw contacts, I'll update the API so that Accounts represent those CNContainers. I might also add an option to return raw contacts separately, instead of unified contacts, in which case Accounts would always be of size 1, making it easier to filter just what you created in your app, as opposed to returning all the properties for any unified contact that happens to have a raw contact from your app.

In summary:

joachimvalente commented 3 years ago

Added includeNonVisibleOnAndroid and returnUnifiedContacts to version 0.2.2. @ReveredMachine let me know if that works for you!

ReveredMachine commented 3 years ago

@joachimvalente

With includeNonVisibleOnAndroid=true all contacts will be returned, that is working.

But unfortunately I cannot filter for a specific account as they are not filled in the result because the awesome getQuick method does not provide them. A question is how to check on the flutter side if the returned contact was originally a visible one in case the contact's account does not belongs to me ?

After testing I was somtime wondering, maybe I found an issue

After saving a contact, it will be returned as follow,

select(
      resolver,
      rawId.toString(), /*with_properties=*/ true, /*with_thumbnail=*/true,
      /*withPhoto=*/true, /*returnUnifiedContacts=*/true,
      /*includeNonVisible=*/true, /*idIsRawContactId=*/true

If I am not wrong, this leads to a selection by the rawId (idIsRawContactId), but due to returnUnifiedContacts=true, the id in the returned contact is filled with the UnifiedContactsId

val id = if (returnUnifiedContacts) getString(Data.CONTACT_ID) else getString(Data.RAW_CONTACT_ID)

One thing regarding contacts and unified contacts

if (!contact.isUnified) {
  throw Exception('Cannot insert raw contacts');
}

From my knowledge it's the unified contact (android: ContactsContract.Contacts) which cannot be added in android

Maybe I can provide some code from my previous proof of concept ?

joachimvalente commented 3 years ago

But unfortunately I cannot filter for a specific account as they are not filled in the result because the awesome getQuick method does not provide them.

You can't use getQuick if you need accounts. You'll have to fetch properties too:

contacts = (await FlutterContact.getContacts(withProperties: true, includeNonVisibleOnAndroid: true))
    .where((c) => c.accounts.any((a) => a.type == 'com.myapp'))
    .toList();

A question is how to check on the flutter side if the returned contact was originally a visible one in case the contact's account does not belongs to me ?

Currently, you can't easily do that. You could fetch once with includeNonVisibleOnAndroid: true and once with includeNonVisibleOnAndroid: false. That's hacky and not very efficient, but I'm reluctant to add too much Android-specific stuff in the API, so I'd rather not expose whether a contact is "visible." Of course, you can always fork this repo and make local changes. (I'm doing that myself to fit my app's specific needs.)

After testing I was somtime wondering, maybe I found an issue

After saving a contact, it will be returned as follow,

select(
      resolver,
      rawId.toString(), /*with_properties=*/ true, /*with_thumbnail=*/true,
      /*withPhoto=*/true, /*returnUnifiedContacts=*/true,
      /*includeNonVisible=*/true, /*idIsRawContactId=*/true

If I am not wrong, this leads to a selection by the rawId (idIsRawContactId), but due to returnUnifiedContacts=true, the id in the returned contact is filled with the UnifiedContactsId

val id = if (returnUnifiedContacts) getString(Data.CONTACT_ID) else getString(Data.RAW_CONTACT_ID)

Correct. But why is that an issue? The /*returnUnifiedContacts=*/true is because I didn't implement inserting raw contacts yet, so returnUnifiedContacts has to be true at that point. The /*idIsRawContactId=*/true means that we just inserted a contact, and in this case Android actually gives us the raw ID. That's one of the reasons we need to select() again here, so that we can return the contact with unified ID.

From my knowledge it's the unified contact (android: ContactsContract.Contacts) which cannot be added in android

You're right! But I didn't want to simply mimic how Android works, or how iOS work, since they're different. Instead I tried to make an API that's most compatible with both platforms.

Ultimately we do want to be able to update/insert/delete raw contacts too, it's just that I haven't figured out all the details yet. For example, if you change the name in a raw contact, what are you going to do with the name in the other raw contacts? With unified contacts, it's much simpler since you can update the entire contact at once.

Maybe I can provide some code from my previous proof of concept ?

Please do! Any help is welcome :) And feel free to open a pull request if that's easier.

ReveredMachine commented 3 years ago

@joachimvalente Ahh, ok, with /returnUnifiedContacts=/true I meant only that the save method always returns the unified contact regardless of the setting FlutterContactsConfig.returnUnifiedContacts. Correct, it's not an issue, I just have to keep that in mind.

You can take a look to the attached patch file that already has the deletion and last modification timestamp (See my feature request #8), but these changes are for now android specific and may not work with IOS :-(

And yes, you are right an API should be as much as possible decoupled from the native side.

flutter_contacts_issue_5_and_8_patch.txt