Hlavní navigace

Android v příkladech: práce s kontakty

Lukáš Marek

Každý programátor aplikací pro Android narazí dříve nebo později na nutnost pracovat s kontakty uloženými v telefonu. Není to ale příliš složité? K pochopení Android 2.0 Contacts API sice není třeba mít mozek Sheldona Coopera, ale malá pomoc do začátku se určitě bude hodit. Ukážeme si, jak na to.

minulém článku se nám podařilo nechat uživatele vybrat si kontakt z adresáře a tím získat jeho Uri. Dnes budeme s tímto kontaktem dále pracovat.

Základní informace o kontaktu

Android sice používá interně klasickou SQLite databázi, ve které jsou kontakty uloženy v několika tabulkách, ale do databáze není bohužel možný přímý přístup. Místo toho je k dispozici API, které je zřejmě navrženo se záměrem odradit všetečné programátory od svého používání. Práce s ním vyžaduje pevné nervy a velkou trpělivost.

Základem tohoto API je ContentResolver, který slouží k vytváření a volání databázových dotazů. Typický dotaz do interní databáze Androidu bude vypadat asi takto:

    public static String lookupContact(Context ctx, Uri contactUri) {
        String[] projection = new String[]{
                ContactsContract.Contacts._ID,
                ContactsContract.Contacts.DISPLAY_NAME,
        };
        ContactDTO dto = null;
        Cursor c = ctx.getContentResolver().query(contactUri, projection, null, null, null);
        if (c != null && c.moveToFirst()) {
            Long id = c.getLong(0);
            String name = c.getString(1);
        }
        if (c != null) {
            c.close();
        }
        return name;
    }

Nejdůležitějším kusem kódu je metoda query(), tedy spíše její parametry. Takže popořadě: první je Uri, v tomto případě ukazující na konkrétní kontakt, což nám protentokrát ušetřilo práci s tvořením WHERE části dotazu.

Další v řadě je projection, což je vlastně seznam sloupečků tabulky, které má dotaz vrátit. V tomto případě tedy id záznamu a jméno kontaktu. Pokud je zadáno null, pak dotaz vrátí všechny sloupečky dané tabulky. Zjištění, jaké sloupečky vlastně daný dotaz může vrátit, bývá velmi zábavnou částí vývoje Android aplikací a budu se mu věnovat níže.

Následují parametry selection, což je vlastně WHERE část dotazu, selectionArgs  – její parametry a nakonec sortOrder  – jak se mají výsledky třídit. Všechny tři jsou prázdné.

Praktický tip

Pro vážnější práci je potřeba si udělat obrázek o skutečné struktuře kontaktů v databázi Androidu. To není až tak složité – stačí si zkopírovat (z emulátoru nebo rootnutého telefonu) kompletní databázi kontaktů pomocí nástroje adb a prohlédnout si ji v oblíbeném databázovém IDE, případně si připravit SQL dotazy předem:

adb pull /data/data/com.android.providers.contacts/databases/contacts2.db .

Po odladění SQL dotazů „nasucho“ potom nastává zábavná fáze, kdy se nebohý vývojář snaží naroubovat dotaz do ContentResolver u a najít, které konstanty by mohly odpovídat jménu sloupečků v databázi.

Další informace o kontaktu

Pro rozšíření základní sady kontaktů už je potřeba sáhnout do dalších tabulek. Jak vlastně Android s kontakty pracuje?

Každému kontaktu odpovídá jeden záznam v tabulce contacts a jeden nebo více záznamů v tabulce raw_contacts. Raw kontact je vždy navázaný na uživatelův účet (Google, Exchange, …) a obsahuje konkrétní informace o kontaktu. Záznam v tabulce contacts potom slouží sdružuje všechny záznamy z raw_contacts, které mají stejné jméno nebo telefonní číslo, popřípadě jinak přirozeně patří k sobě. To znamená, že pokud má telefon dva zdroje kontaktů (například osobní GMail a firemní Exchange), v telefonním seznamu bude konkrétní člověk uveden pouze jednou, přestože je přítomen v obou zdrojích.

Na Raw Contact jsou potom navázány rozšiřující údaje (poznámky, e-maily, …) uložené v tabulce data. Takže pro telefonní číslo už je třeba sáhnout do této tabulky:

    private static String getPhoneNumber(Context ctx, long contactId) {
        Cursor cursor = ctx.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                new String[]{
                        ContactsContract.CommonDataKinds.Phone.NUMBER},
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "= ?" +
                        " AND " + ContactsContract.CommonDataKinds.Phone.TYPE +
                        "=" + ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
                new String[]{String.valueOf(contactId)},
                null);


        try {
            if (cursor.moveToFirst()) {
                return cursor.getString(0);
            } else {
                return null;
            }
        } finally {
            cursor.close();
        }
    }

Zde už je vidět, že Uri ukazuje obecně na všechna telefonní čísla, takže je potřeba omezit výběr pomocí selection. Jde opravdu o klasickou WHERE klauzuli, bohužel díky použití konstant poněkud nepřehlednou.

Poznámka: Tento kód rozhodně není vhodný pro reálné využití, neboť vybírá pouze první telefonní číslo ze seznamu. V reálné aplikaci by musel vracet všechna telefonní čísla a nechat uživatele vybrat.

Ukládání změn

Dejme tomu, že se nám podařilo načíst z databáze všechny podstatné údaje a zobrazit je uživateli. Například takto:

K tomu, dát uživateli možnost uložit změnu poznámky stačí na událost onClick() příslušného tlačítka zavolat takovouto metodu:

    public static void saveNote(Context ctx, long contactId, String note, NoteDTO prevNote) {
        ArrayList batch = new ArrayList(1);
        if (prevNote == null) {
            //INSERT
            Long rawContactId = getRawContactIds(ctx, contactId)[0]; //tady by si mel uzivatel spravne vybrat
            batch.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).
                withValue(ContactsContract.CommonDataKinds.Note.RAW_CONTACT_ID, String.valueOf(rawContactId)).
                withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE).
                withValue(ContactsContract.CommonDataKinds.Note.NOTE, note).
                build()
            );
        } else {
            //UPDATE
            batch.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).
                withSelection(ContactsContract.CommonDataKinds.Note._ID + "= ?",
                        new String[]{String.valueOf(prevNote.id)}).
                withValue(ContactsContract.CommonDataKinds.Note.NOTE, note).
                build()
            );
        }
        ContentProviderResult[] result = ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, batch);
   }

V kódu je záměrně použit objekt ContentProviderOperation, který sice má sloužit k dávkovému zpracování změn v databázi, ale práce s ním je (na poměry Androida) relativně jednoduchá. Jediná zrada je, že musíme rozlišit, zda se jedná o INSERT nebo UPDATE. Tedy zda jde o vytvoření nové poznámky nebo úpravu stávající. V aplikaci připravené pro tento článek je to vyřešeno předáním původního objektu s poznámkou. Pokud je prevNote null, pak se jedná o INSERT.

A ještě jedna drobněnka: Poznámka se nedá přidat ke kontaktu, ale k raw kontaktu. Takže místo contactId musíme získat rawContactId. Pravděpodobně nějak takhle:

    private static Long[] getRawContactIds(Context ctx, Long contactId) {
        Cursor cursor =  ctx.getContentResolver().query(
                ContactsContract.RawContacts.CONTENT_URI,
                new String[] {ContactsContract.RawContacts._ID,
                ContactsContract.RawContacts.CONTACT_ID + " = ?",
                new String[]{String.valueOf(contactId)}, null);

        List result = new ArrayList();
        while (cursor.moveToNext()) {
            result.add(cursor.getLong(0));
        }
        return result.toArray(new Long[result.size()]);
    }

Bonus: Získání fotky

Ve světle předešlých informací je docela překvapivé, že získání fotky kontaktu je operace na jeden řádek:

ContactsContract.Contacts.openContactPhotoInputStream(ctx.getContentResolver(), contactUri);

Ale takhle to s Androidem prostě je. Některé složité věci jdou překvapivě jednoduše, některé jednoduché věci překvapivě složitě.

Závěr

V článku určitě nebylo popsáno všechno, nicméně zvídavému čtenáři je k dispozici kompletní zdrojový kód aplikace.

Našli jste v článku chybu?

31. 7. 2014 17:56

Pterodactyll (neregistrovaný)

Tak Android již dost dlouho studuji,aby mě z něho bylo nevolno.
Základní jednoduché věci složité až běda.To co uměly systémy pro nokii neumí a to že jde stáhnout miliony aplikací,většinou k ničemu, to nevyváží.Zlatý WinPhone. Již bych nikomu neradil jít do androida, už jen úprava kontaktů zabere půl dne a musí se překopat aby něco fungovalo-HRŮZA


Podnikatel.cz: K EET. Štamgast už peníze na stole nenechá

K EET. Štamgast už peníze na stole nenechá

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

Lupa.cz: Teletext je „internetem hipsterů“

Teletext je „internetem hipsterů“

Podnikatel.cz: Změny v cestovních náhradách 2017

Změny v cestovních náhradách 2017

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

Vitalia.cz: Potvrzeno: Pobyt v lese je skvělý na imunitu

Potvrzeno: Pobyt v lese je skvělý na imunitu

Vitalia.cz: Spor o mortadelu: podle Lidlu falšovaná nebyla

Spor o mortadelu: podle Lidlu falšovaná nebyla

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Podnikatel.cz: Snížení DPH na 15 % se netýká všech

Snížení DPH na 15 % se netýká všech

Vitalia.cz: Když přijdete o oko, přijdete na rok o řidičák

Když přijdete o oko, přijdete na rok o řidičák

120na80.cz: Co všechno ovlivňuje ženskou plodnost?

Co všechno ovlivňuje ženskou plodnost?

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?