Home · All Classes · All Functions ·

Contacts

The Contacts API enables a client to request contact data from local or remote backends. This is part of the Qt Mobility Project.

Introduction

The Contacts API provides clients with the ability to access contact data in a platform-independent and datastore-agnostic manner. This is achieved by defining generic personal information data abstractions which can sufficiently describe contact data stored on any platform. Due to the cross-platform nature of the API, and the ability for developers to write platform-independent implementations of a QContactManager which may unify one or more platform-specific contact backends, it is intended that the semantics and quirks of the underlying datastores on any platform may be entirely opaque from the perspective of Qt-based, cross-platform client applications.

Overview

A contact is the digital representation of a person, group or entity, which is stored in a platform-specific manner. Information pertaining to a single contact may be located across several different datastores, and each datum (or detail) may or may not pertain to a particular context in which that information is valid. A contact may include semantically identical pieces of information that are relevant in different contexts. For example, a contact may have a phone number that is relevant to their "home" context, and another phone number that is relevant to their "work" context. It can be seen that the context of information defines its validity to the user, depending on the context of usage; and as such, the sum of information in a given context can be considered equivalent to a "contextual identity". This allows great flexibility when consolidating data from various sources into a single, cohesive contact.

Each datum (or detail) stored in a contact has defined semantics of usage and storage. The Qt Contacts API allows per-datastore contact detail definitions, allowing a manager to provide clients with this information on demand, and allowing third-party developers to register detail definitions for use by clients. A detail definition includes the fields (and value-types of those fields) which make up the detail, per-contact uniqueness constraints on details of the definition, and access constraints (such as read-only, create-only, etc). Additionally, the fields of a detail definition may also be constrained to be read-only or not.

A detail is a single, cohesive unit of information that is stored in a contact. As explained previously, it is valid for a particular context or set of contexts, and conforms to a particular definition. A detail may have specific metadata associated with it, such as its sub-type, context, and arbitrary, user-defined metadata.

Contacts may participate in relationships with other contacts. The details of any such relationship is stored by the manager which contains the contact. There are several standard relationship types supported by the default schema, and arbitrary relationship types are also allowed. In particular, membership of a contact in a group can be modeled as that group contact participating in a HasMember relationship with the contact.

Access to the contacts is provided by implementations of the Qt Contacts manager API. A manager provides access to zero or more platform-specific datastores. Each datastore may support different capabilities (for example, the ability to store certain datatypes, the ability to natively filter on different details or details of different definitions, the provision of locking mechanisms, the provision of changelog information, etc) which are reported by the manager on request. The manager therefore provides access to detail definitions, contacts, and relationships stored in different datastores, in a platform and datastore independent manner. The engine of a manager may be implemented as a plugin to allow dynamic loading of different engines at run-time.

Using the API

This section provides some examples of common usage of the API.

Synchronous API Usage

The synchronous API provides the simplest way to access or modify the contact information managed by a particular backend. It has the disadvantage that calls block until completion and is therefore most suitable only for applications which interact with local, high-speed datastores.

Saving a new contact to the default manager

The client creates a new contact, adds a name and a phone number, and saves it to the default store of the default manager.

We assume the existence of a specialised leaf-class that allows simple access to details of the definition identified by the "PhoneNumber" identifier, and another that allows simple access to details of the definition identified by the "Name" identifier. These specialised leaf classes may be written by anyone, and simply wrap the functionality provided by QContactDetail in order to allow simpler access to fields supported by a particular definition.

    void addContact(QContactManager* cm)
    {
        QContact alice;

        /* Set the contact's name */
        QContactName aliceName;
        aliceName.setFirstName("Alice");
        aliceName.setLastName("Jones");
        aliceName.setCustomLabel("Ally Jones");
        alice.saveDetail(&aliceName);

        /* Add a phone number */
        QContactPhoneNumber number;
        number.setContexts(QContactDetail::ContextHome);
        number.setSubTypes(QContactPhoneNumber::SubTypeMobile);
        number.setNumber("12345678");
        alice.saveDetail(&number);
        alice.setPreferredDetail("DialAction", number);

        /* Add a second phone number */
        QContactPhoneNumber number2;
        number2.setContexts(QContactDetail::ContextWork);
        number2.setSubTypes(QContactPhoneNumber::SubTypeLandline);
        number2.setNumber("555-4444");
        alice.saveDetail(&number2);

        /* Save the contact */
        cm->saveContact(&alice) ? qDebug() << "Successfully saved" << aliceName.customLabel()
                                : qDebug() << "Failed to save" << aliceName.customLabel();
        qDebug() << "The backend has synthesized a display label for the contact:" << alice.displayLabel();
    }
Filtering by detail definition and value

The client utilises a default manager and asks for any contacts with a particular phone number. The example assumes that the default manager supports the provided QContactPhoneNumber detail leaf class (which implements the default definition for phone number details).

    void matchCall(QContactManager* cm, const QString& incomingCallNbr)
    {
        QContactDetailFilter phoneFilter;
        phoneFilter.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber);
        phoneFilter.setValue(incomingCallNbr);
        phoneFilter.setMatchFlags(QContactFilter::MatchExactly);

        QList<QContactLocalId> matchingContacts = cm->contactIds(phoneFilter);
        if (matchingContacts.size() == 0) {
            qDebug() << "Incoming call from unknown contact (" << incomingCallNbr << ")";
        } else {
            QContact match = cm->contact(matchingContacts.at(0));
            qDebug() << "Incoming call from"
                     << match.displayLabel()
                     << "(" << incomingCallNbr << ")";
        }
    }
Viewing a specific detail of a contact

The client retrieves the phone numbers of a contact, and displays the first one

    void viewSpecificDetail(QContactManager* cm)
    {
        QList<QContactLocalId> contactIds = cm->contactIds();
        QContact a = cm->contact(contactIds.first());
        qDebug() << "The first phone number of" << a.displayLabel()
                 << "is" << a.detail(QContactPhoneNumber::DefinitionName).value(QContactPhoneNumber::FieldNumber);
    }
Viewing all of the details of a contact

The client retrieves all of the details of a contact, and displays them

    void viewDetails(QContactManager* cm)
    {
        QList<QContactLocalId> contactIds = cm->contactIds();
        QContact a = cm->contact(contactIds.first());
        qDebug() << "Viewing the details of" << a.displayLabel();

        QList<QContactDetail> allDetails = a.details();
        for (int i = 0; i < allDetails.size(); i++) {
            QContactDetail detail = allDetails.at(i);
            QContactDetailDefinition currentDefinition = cm->detailDefinition(detail.definitionName());
            QMap<QString, QContactDetailDefinitionField> fields = currentDefinition.fields();

            qDebug("\tDetail #%d (%s):", i, detail.definitionName().toAscii().constData());
            foreach (const QString& fieldKey, fields.keys()) {
                qDebug() << "\t\t" << fieldKey << "(" << fields.value(fieldKey).dataType() << ") =" << detail.value(fieldKey);
            }
            qDebug();
        }
    }

It is important to note that details are implicitly shared objects with particular semantics surrounding saving, removal and modification. The following example demonstrates these semantics

    void detailSharing(QContactManager* cm)
    {
        QList<QContactLocalId> contactIds = cm->contactIds();
        QContact a = cm->contact(contactIds.first());
        qDebug() << "Demonstrating detail sharing semantics with" << a.displayLabel();

        /* Create a new phone number detail. */
        QContactPhoneNumber newNumber;
        newNumber.setNumber("123123123");
        qDebug() << "\tThe new phone number is" << newNumber.number();

        /*
         * Create a copy of that detail.  These will be implicitly shared;
         * changes to nnCopy will not affect newNumber, and vice versa.
         * However, attempting to save them will cause overwrite to occur.
         * Removal is done purely via key() checking, also.
         */
        QContactPhoneNumber nnCopy(newNumber);
        nnCopy.setNumber("456456456");
        qDebug() << "\tThat number is still" << newNumber.number() << ", the copy is" << nnCopy.number();

        /* Save the detail in the contact, then remove via the copy, then resave. */
        a.saveDetail(&newNumber);
        a.removeDetail(&nnCopy);  // identical to a.removeDetail(&newNumber);
        a.saveDetail(&newNumber); // since newNumber.key() == nnCopy.key();

        /* Saving will cause overwrite */
        qDebug() << "\tPrior to saving nnCopy," << a.displayLabel() << "has" << a.details().count() << "details.";
        a.saveDetail(&nnCopy);
        qDebug() << "\tAfter saving nnCopy," << a.displayLabel() << "still has" << a.details().count() << "details.";

        /* In order to save nnCopy as a new detail, we must reset its key */
        nnCopy.resetKey();
        qDebug() << "\tThe copy key is now" << nnCopy.key() << ", whereas the original key is" << newNumber.key();
        qDebug() << "\tPrior to saving (key reset) nnCopy," << a.displayLabel() << "has" << a.details().count() << "details.";
        a.saveDetail(&nnCopy);
        qDebug() << "\tAfter saving (key reset) nnCopy," << a.displayLabel() << "still has" << a.details().count() << "details.";
        a.removeDetail(&nnCopy);

        /*
         * Note that changes made to details are not
         * propagated automatically to the contact.
         * To persist changes to a detail, you must call saveDetail().
         */
        QList<QContactPhoneNumber> allNumbers = a.details<QContactPhoneNumber>();
        foreach (const QContactPhoneNumber& savedPhn, allNumbers) {
            if (savedPhn.key() != newNumber.key()) {
                continue;
            }

            /*
             * This phone number is the saved copy of the newNumber detail.
             * It is detached from the newNumber detail, so changes to newNumber
             * shouldn't affect savedPhn until saveDetail() is called again.
             */
            qDebug() << "\tCurrently, the (stack) newNumber is" << newNumber.number()
                     << ", and the saved newNumber is" << savedPhn.number();
            newNumber.setNumber("678678678");
            qDebug() << "\tNow, the (stack) newNumber is" << newNumber.number()
                     << ", but the saved newNumber is" << savedPhn.number();
        }

        /*
         * Removal of the detail depends only on the key of the detail; the fact
         * that the values differ is not taken into account by the remove operation.
         */
        a.removeDetail(&newNumber) ? qDebug() << "\tSucceeded in removing the temporary detail."
                                   : qDebug() << "\tFailed to remove the temporary detail.\n";
    }
Installing a plugin that modifies the definition of one type of detail

The client installs a plugin, which requires a new field to be added to details of the "EmailAddress" definition. It loads the definition from the default manager, modifies it (by adding the new field - a label field), and saves it back.

    void addPlugin(QContactManager* cm)
    {
        /* Find the definition that we are modifying */
        QMap<QString, QContactDetailDefinition> definitions = cm->detailDefinitions();
        QContactDetailDefinition modified = definitions.value(QContactEmailAddress::DefinitionName);

        /* Make our modifications: we add a "Label" field to email addresses */
        QContactDetailDefinitionField newField;
        newField.setDataType(QVariant::String);
        QMap<QString, QContactDetailDefinitionField> fields = modified.fields();
        fields.insert("Label", newField);

        /* Update the definition with the new field included */
        modified.setFields(fields);

        /* Save the definition back to the manager */
        if (cm->saveDetailDefinition(modified))
            qDebug() << "Successfully modified the detail definition!";
        else
            qDebug() << "This backend could not support our modifications!";
    }
Modifying an existing contact and saving the modifications

The client retrieves a contact, modifies one of its details, adds a new detail, and then saves the contact back to the manager. Note that it uses the newly added field of the email address definition!

    void editView(QContactManager* cm)
    {
        QList<QContactLocalId> contactIds = cm->contactIds();
        QContact a = cm->contact(contactIds.first());
        qDebug() << "Modifying the details of" << a.displayLabel();

        /* Change the first phone number */
        QList<QContactDetail> numbers = a.details(QContactPhoneNumber::DefinitionName);
        QContactPhoneNumber phone = numbers.value(0);
        phone.setNumber("123-4445");

        /* Add an email address */
        QContactEmailAddress email;
        email.setEmailAddress("alice.jones@example");
        email.setContexts(QContactDetail::ContextHome);
        email.setValue("Label", "Alice's Work Email Address");

        /* Save the updated details to the contact. */
        a.saveDetail(&phone);
        a.saveDetail(&email);

        /* Now we must save the updated contact back to the database. */
        cm->saveContact(&a);
        viewDetails(cm);
    }

Asynchronous API Usage

The asynchronous API provides a flexible and powerful method of accessing and modifying the contact information managed by a particular backend in an asynchronous manner. Use of the asynchronous API is slightly more complex than use of the synchronous API, but offers the programmer greater flexibility when requesting information from remote or slow, local datastores.

Requesting Contacts

The client sets up a request for contacts matching a specific criteria from a particular manager.

Results from the request will be displayed to the user as they are received.

    void RequestExample::performRequest()
    {
        // retrieve any contact whose first name is "Alice"
        QContactDetailFilter dfil;
        dfil.setDetailDefinitionName(QContactName::DefinitionName, QContactName::FieldFirstName);
        dfil.setValue("Alice");
        dfil.setMatchFlags(QContactFilter::MatchExactly);

        // m_fetchRequest was created with m_fetchRequest = new QContactFetchRequest() in the ctor.
        m_fetchRequest->setManager(this->m_manager); // m_manager is a QContactManager*.
        m_fetchRequest->setFilter(dfil);
        connect(m_fetchRequest, SIGNAL(resultsAvailable()), this, SLOT(printContacts()));
        connect(m_fetchRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)),
                this, SLOT(stateChanged(QContactAbstractRequest::State)));
        if (!m_fetchRequest->start()) {
            qDebug() << "Unable to request contacts!";
            QCoreApplication::exit(0);
        } else {
            qDebug() << "Requested contacts; awaiting results...";
        }
    }

    void RequestExample::printContacts()
    {
        QList<QContact> results = m_fetchRequest->contacts();
        for (m_previousLastIndex = 0; m_previousLastIndex < results.size(); m_previousLastIndex++) {
            qDebug() << "Found an Alice:" << results.at(m_previousLastIndex).displayLabel();
        }
    }

    void RequestExample::stateChanged(QContactAbstractRequest::State state)
    {
        // once we've finished retrieving results, stop processing events.
        if (state == QContactAbstractRequest::FinishedState
            || state == QContactAbstractRequest::CanceledState) {
            qDebug() << "Finished displaying asynchronously retrieved contacts!";
            QCoreApplication::exit(0);
        }
    }
Other Asynchronous Operations

All other asynchronous operations are performed in a similar manner to the previous example. A request of the desired type (which is derived from QContactAbstractRequest) is created, certain criteria are set which determine the result of the request, and the progress signal of the request is connected to a slot which deals with the result. The request can then be started.

Any operation that may be performed using the synchronous API may also be performed using the asynchronous API. It is recommended for most applications that the asynchronous API be used where possible.

Manager Settings And Configuration

Users of the contacts API can define which backend they wish to access if a manager for that backend is available. The list of available managers can be queried programmatically at run-time, and the capabilities of different managers can be ascertained by inspecting a QContactManager instance. Furthermore, some managers can be constructed with parameters which affect the operation of the backend.

Loading the manager for a specific backend

In this example, the client loads a manager for a specific backend. While this could be found and retrieved using a more advanced plugin framework (such as the Qt Service Framework), this code assumes that the client has prior knowledge of the backend in question.

    void loadManager()
    {
        QContactManager* cm = new QContactManager("KABC");
        QList<QContactLocalId> contactIds = cm->contactIds();
        if (!contactIds.isEmpty()) {
            QContact a = cm->contact(contactIds.first());
            qDebug() << "This manager contains" << a.displayLabel();
        } else {
            qDebug() << "This manager contains no contacts";
        }

        delete cm;
    }

Loading a manager with specific parameters

The client loads a manager with specific parameters defined. The parameters which are available are backend specific, and so the client had to know that the "Settings" parameter was valid for the particular backend, and what argument it took. In this example, the client tells the backend to load detail definitions saved in a particular settings file.

    void loadManagerWithParameters()
    {
        QMap<QString, QString> parameters;
        parameters.insert("Settings", "~/.qcontactmanager-kabc-settings.ini");
        QContactManager* cm = new QContactManager("KABC", parameters);
        QMap<QString, QContactDetailDefinition> definitions = cm->detailDefinitions();

        qDebug() << "This backend currently supports the following detail definitions:";
        QList<QContactDetailDefinition> allDefinitions = definitions.values();
        foreach (const QContactDetailDefinition& defn, allDefinitions) {
            QMap<QString, QContactDetailDefinitionField> fields = defn.fields();
            foreach (const QString& fieldKey, fields.keys()) {
                QList<QVariant> allowableValues = fields.value(fieldKey).allowableValues();
                qDebug() << "\t" << fieldKey << "(" << fields.value(fieldKey).dataType() << "):";
                if (allowableValues.isEmpty()) {
                    qDebug() << "\t\tAny Value Permitted";
                } else {
                    qDebug() << allowableValues;
                }
            }
        }

        delete cm;
    }

Building and compiling

This library requires Qt 4.5 to be installed.

To build the library, run qmake and make.

Reference documentation

Main classes

QContactAddressbook contact
QContactAbstractRequestMechanism for asynchronous requests to be made of a manager if it supports them
QContactActionInterface for performing an action on a QContact or QContactDetail
QContactDetailAccess to a single, complete detail about a contact
QContactFilterUsed to select contacts made available through a QContactManager
QContactManagerClients with access to contact information stored in a particular backend
QContactRelationshipDescribes a one-to-one relationship between a locally-stored contact and another (possibly remote) contact

"Contact Details" Leaf Classes

Several subclasses of QContactDetail are provided as part of the Qt Mobility Project Contacts API. They are general in design but are intended to fulfil specific use-cases. Please note that certain backends may choose not to support one or more of these subclasses as they appear here; they may offer their own which provide similar functionality.

QContactAddressContains an address of a contact
QContactAnniversaryContains the anniversary of a contact
QContactAvatarContains the avatar of a contact
QContactBirthdayContains the birthday of a contact
QContactDisplayLabelThe (possibly synthesized) display label of a contact
QContactEmailAddressContains the email address of a contact
QContactFamilyContains the names of the family members of a contact
QContactGenderContains the gender of a contact
QContactGeoLocationContains the global location coordinate associated with a contact
QContactGuidContains the globally unique Id of a contact
QContactNameContains the name of a contact
QContactNicknameContains a nickname of a contact
QContactNoteContains a note associated with a contact
QContactOnlineAccountThe online account, which the contact uses to communicate with friends and family
QContactOrganizationDetails about an organization that the contact is either a part of, or stands for
QContactPhoneNumberPhone number of a contact
QContactSyncTargetSync target for a contact
QContactTimestampContains the creation and last-modified timestamp associated with the contact
QContactTypeDescribes the type of the contact
QContactUrlContains a url associated with a contact

Each of these subclasses provide access to information stored in fields which may have certain constraints, as listed in the schema.

Asynchronous Requests

Clients may use either the synchronous or asynchronous API to access functionality provided by a manager backend. The asynchronous API is offered through subclasses of the QContactAbstractRequest class:

QContactDetailDefinitionFetchRequestAllows a client to asynchronously request detail definitions from a contacts store manager
QContactDetailDefinitionRemoveRequestAllows a client to asynchronously request that certain detail definitions be removed from a contacts store
QContactDetailDefinitionSaveRequestAllows a client to asynchronously request that certain detail definitions be saved in a contacts store
QContactFetchRequestAllows a client to asynchronously request contacts from a contacts store manager
QContactLocalIdFetchRequestAllows a client to asynchronously request a list of contact ids from a contacts store manager
QContactRelationshipFetchRequestAllows a client to asynchronously request relationships from a contacts store manager
QContactRelationshipRemoveRequestAllows a client to asynchronously request that certain relationships be removed from a contacts store
QContactRelationshipSaveRequestAllows a client to asynchronously request that certain groups be saved to a contacts store
QContactRemoveRequestAllows a client to asynchronously request that certain contacts be removed from a contacts store
QContactSaveRequestAllows a client to asynchronously request that certain contacts be saved to a contacts store

Contact Selection

Clients may select a contact by specifying a unique contact id, or by supplying a QContactFilter which matches the contact or contacts they wish to select. The various derivatives of QContactFilter allow for fine-grained and flexible selection of contacts according to various criteria:

QContactActionFilterFilter based around an action availability criterion
QContactChangeLogFilterFilter based around a contact timestamp criterion
QContactDetailFilterFilter based around a detail value criterion
QContactDetailRangeFilterFilter based around a detail value range criterion
QContactIntersectionFilterFilter which intersects the results of other filters
QContactInvalidFilterMatches no contacts
QContactLocalIdFilterFilter based around a list of contact ids
QContactRelationshipFilterFilter based around relationship criteria
QContactUnionFilterFilter which unions the results of other filters

A client can also request that the results of such a selection be sorted, by passing a QContactSortOrder (or list of sort orders) to the manager.

Actions

Clients can perform actions on contacts which support them. Actions are things like "Send Email" or "Dial", and can be provided from various sources including Qt Plugins or the Qt Mobility Service Framework. Every action implementation is uniquely identified by a combination of its name, the name of the vendor which provided the implementation, and the version of the implementation according to the vendor. These pieces of data may be encapsulated in a QContactActionDescriptor which can be used to retrieve an instance of the implementation from a QContactActionFactory.

When an instance of a QContactAction is created, the caller takes ownership of the instance, and must delete it after use.

Implementing Backends

A manager backend may be implemented by subclassing QContactManagerEngine, and providing a QContactManagerEngineFactory which can instantiate it when required.

Manager information and functionality reporting

Different backends have different capabilities and offer different functionality. In order to allow clients to query the provided functionality at runtime, every backend must be capable of reporting their functionality and implementation version. They are reported to clients through various functions provided by the QContactManager class.

Synchronization and Serialization

The contacts API is used by another Qt Mobility module: the Versit ® module. It allows serialization of a QContact into a vCard document, and vice versa.

[*] Versit ® is a trademark of the Internet Mail Consortium.

Examples


Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Mobility Project 1.0.0