How to customize the "chips" auto-suggesting mechanism, as used on Gmail's recipients field?
Asked Answered
B

2

12

Background

I've searched for a way to have a similar look & feel to the Gmail receipients field, which allows auto-filling of items in a really cool way:

enter image description here

The class that is built into the Android framework and is responsible for this is called "MultiAutoCompleteTextView" .

The problem

the MultiAutoCompleteTextView is quite basic, yet it doesn't hold enough samples, tutorials and libraries to get to know how to customize it like on Gmail and the likes.

I would like to know how to customize it to handle any kind of data, and that I will have full control over it (for example adding, deleting and getting the items that it has auto-completed).

What I've tried

I've found the next possible ways to achieve it:

  1. use a third library like splitwise-TokenAutoComplete. the downside: it's very buggy and doesn't work well on some devices.
  2. create my own way (as shown here). the downside: will take a long time and I will probably need to handle the same problems as of the library.
  3. use the code of Google (found here). The downside: it's really not customizable.

I've decided to use #3 (Google's chips library).

Currently the code for getting the list of contacts used on Google's library:

public List<RecipientEntry> doQuery() {
    final Cursor cursor = mContentResolver.query(mQuery.getContentUri(), mQuery.getProjection(), null, null, null);
    final LinkedHashMap<Long, List<RecipientEntry>> entryMap = new LinkedHashMap<Long, List<RecipientEntry>>();
    final List<RecipientEntry> nonAggregatedEntries = new ArrayList<RecipientEntry>();
    final Set<String> existingDestinations = new HashSet<String>();
    while (cursor.moveToNext())
        putOneEntry(new TemporaryEntry(cursor, false /* isGalContact */), true, entryMap, nonAggregatedEntries,
                existingDestinations);
    cursor.close();
    final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
    {
        for (final Map.Entry<Long, List<RecipientEntry>> mapEntry : entryMap.entrySet()) {
            final List<RecipientEntry> entryList = mapEntry.getValue();
            for (final RecipientEntry recipientEntry : entryList)
                entries.add(recipientEntry);
        }
        for (final RecipientEntry entry : nonAggregatedEntries)
            entries.add(entry);
    }
    return entries;
}

It works fine, but I'm having difficulties adding items and deleting them.

I think that getting the items is used by calling "getContactIds" , but about modifying the items within the chips, that's very problematic to find.

For example, I've tried to add a similar function to "submitItemAtPosition" , which seems to add a new entity found from the adapter. It does add, but the display-name of the contact isn't shown on the chip itself.

The question

After a lot of thoughts, I decided to use Google's code.

Sadly, as I've written, the view and its classes are very tight to the usage of it.

  1. How can I de-couple the view and make it much more customizable? How can I make it use any type of data instead of just what Google has done?

  2. How do I get which items were entered (that became "chips"), and also be able to remove or add items from outside?

Biflagellate answered 3/4, 2014 at 11:15 Comment(13)
kpbird.com/2013/02/android-chips-edittext-token-edittext.html This may help youJittery
@HardikTrivedi Actually I've read it too, and didn't find the answer as to how to manage the items being selected, including adding, removing and querying them. I think that like the libraries I've tried, this one also has bugs on some devices. also I quite like the way the Google's library looks like, so I would like to use it as the base code. Only problem is that Google has made it very complex , hard to modify and hard to read.Biflagellate
@androiddeveloper see my modified gist: gist.github.com/pskink/5fe9c0bb4677c1debc5eMocha
@Mocha Currently I use the library that Google has presented, and try to customize it instead of creating everything from scratch. Maybe I will check it out, but I don't think I will have the time to handle it in addition to what I already do.Biflagellate
@androiddeveloper good luck then ...Mocha
@Mocha thank you, and maybe when I'm finished, I will put it on github.Biflagellate
@Mocha I've succeeded modifying it. Hope to publish it on Github soon. For now, i've put my answer. too bad I've set a bounty on this as I thought this could take me days.Biflagellate
@androiddeveloper i just realized they are using minSdkVersion ="11" but i need version "8", so i think i cannot use it as back porting would be a nightmare :(Mocha
@Mocha there are some functionalities that you might not need, like dragging (which is very buggy, BTW - dragging first item after the second will actually replace the second with a new double). But you are correct - it will be hard to make it work on pre-honeycomb . for now i still work on the project since i've found a weird issue: searching for contacts returns bad results. For example ,if you search for "abc" you can get contacts without this string in their name. I think it is considered also as a search of the phone number itself (like the letters that are shown on the dialing buttons).Biflagellate
@androiddeveloper the last issuue ("abc") is connected with T9 search - i had a contact which phone number starts with number 6 and typing "m", "n" or "o" selects that contactMocha
@Mocha I know, but it's weird since the keyboard shown isn't T9 (not even sure how to show it). also, some devices have more than just English , so I'm not sure it's wise to use it.Biflagellate
@Mocha I've now published the project on GitHub.Biflagellate
@androiddeveloper great! bookmark added...Mocha
B
5

I've succeeded adding the functionality of adding a recipient. The only thing to remember is to call it only after the view got its size (example of how to do it here) :

/** adds a recipient to the view. note that it should be called when the view has determined its size */
public void addRecipient(final RecipientEntry entry) {
    if (entry == null)
        return;
    clearComposingText();

    final int end = getSelectionEnd();
    final int start = mTokenizer.findTokenStart(getText(), end);

    final Editable editable = getText();
    QwertyKeyListener.markAsReplaced(editable, start, end, "");
    final CharSequence chip = createChip(entry, false);
    if (chip != null && start >= 0 && end >= 0) {
        editable.replace(start, end, chip);
    }
    sanitizeBetween();
}

private void submitItemAtPosition(final int position) {
    final RecipientEntry entry = createValidatedEntry(getAdapter().getItem(position));
    if (entry == null)
        return;
    addRecipient(entry);
}

And, for deletion:

/** removes a chip of a recipient from the view */
public void removeRecipient(final RecipientEntry entry) {
    final DrawableRecipientChip[] chips = getSpannable().getSpans(0, getText().length(),
            DrawableRecipientChip.class);
    final List<DrawableRecipientChip> chipsToRemove = new ArrayList<DrawableRecipientChip>();
    for (final DrawableRecipientChip chip : chips)
        if (chip.getDataId() == entry.getDataId())
            chipsToRemove.add(chip);
    for (final DrawableRecipientChip chip : chipsToRemove)
        removeChip(chip);
}

and as I've written before, for getting the list of contactIds that are currently inside the view, use "getContactIds()" . Another alternative is:

/** returns a collection of all of the chips' items. key is the contact id, and the value is the recipient itself */
public Map<Long, RecipientEntry> getChosenRecipients() {
    final Map<Long, RecipientEntry> result = new HashMap<Long, RecipientEntry>();
    final DrawableRecipientChip[] chips = getSortedRecipients();
    if (chips != null)
        for (final DrawableRecipientChip chip : chips) {
            // if(result.)
            final long contactId = chip.getContactId();
            if (!result.containsKey(contactId))
                result.put(contactId, chip.getEntry());
        }
    return result;
}

Maybe I should post the code on Github.

The only thing I miss now is a good listener to the chips themselves : when a chip is added, removed and replaced. for most of the cases I can detect it, but not when the user presses backspace and removes a chip.


EDIT: also added the listener. now I've found a bug in searching of contacts. it seems to search the normal English letters as if they were phone numbers.


EDIT: I've decided to put a sample and a library on GitHub, here . Hope to update it with more useful features soon.

I would really be happy for any contribution to the code.

Biflagellate answered 6/4, 2014 at 9:46 Comment(0)
L
1

This library seems to allow you to configure what it searches for while also matching the Material Design look. It also seems to be based off of Google's chip library. I happened to find it when investigating a similar problem.

https://github.com/klinker41/android-chips

Leptosome answered 21/8, 2015 at 21:32 Comment(1)
@GauravPolekar I had some other issues with it so I spent some time a while back to update it to the more modern code. That had some bugs in it however so I never got around to finishing it. Google released an open source version of their Messaging app as part of the AOSP 6.0 code. You can find it below. Maybe you can use that to improve what you have now. github.com/CyanogenMod/android_packages_apps_Messaging/tree/…Leptosome

© 2022 - 2024 — McMap. All rights reserved.