{"id":32516463,"url":"https://github.com/dmfs/contentpal","last_synced_at":"2025-10-28T01:53:54.295Z","repository":{"id":55102462,"uuid":"92286413","full_name":"dmfs/ContentPal","owner":"dmfs","description":"A friend to help with Android ContentProvider operations.","archived":false,"fork":false,"pushed_at":"2023-04-12T02:39:18.000Z","size":752,"stargazers_count":4,"open_issues_count":32,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-07-06T14:41:52.260Z","etag":null,"topics":["android","calendar","contacts","contentprovider"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmfs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-24T11:47:26.000Z","updated_at":"2023-04-12T02:33:57.000Z","dependencies_parsed_at":"2022-08-14T12:00:59.437Z","dependency_job_id":null,"html_url":"https://github.com/dmfs/ContentPal","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/dmfs/ContentPal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmfs%2FContentPal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmfs%2FContentPal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmfs%2FContentPal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmfs%2FContentPal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmfs","download_url":"https://codeload.github.com/dmfs/ContentPal/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmfs%2FContentPal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281371741,"owners_count":26489526,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","calendar","contacts","contentprovider"],"created_at":"2025-10-28T01:53:48.960Z","updated_at":"2025-10-28T01:53:54.286Z","avatar_url":"https://github.com/dmfs.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/dmfs/ContentPal.svg?branch=master)](https://travis-ci.org/dmfs/ContentPal)\n[![codecov](https://codecov.io/gh/dmfs/ContentPal/branch/master/graph/badge.svg)](https://codecov.io/gh/dmfs/ContentPal)\n\n# ContentPal\n\nA friend to help with Android ContentProvider operations.\n\n## Rationale\n\nAndroid uses the concept of \"ContentProviders\" to share data among apps quite successfully. While the concept behind these ContentProviders is quite powerful it\ncan be a real hassle to work with. Reading and writing data can be a a challenging task. The resulting code is easy to get wrong and often quite verbose and\nhard to read. Using the efficient batch operations is non trivial as well, especially when working with \"related\" rows.\n\nThis library aims to add an abstraction layer to ContentProviders to handle operations in an object oriented manner. With ContentPal, ContentProvider operations\nare written in a declarative way before being enqueued for aggregated execution.   \nThe resulting code is easier to write and read.\n\nNote that this library is not meant to be an ORM and uses database terminology all over the place.\n\n## Note well\n\nThe interfaces of this library are not considered 100% stable at this time. Design and names are still subject to change.\n\n## Goals\n\nThis library has been created with specific goals in mind.\n\n* reduce boilerplate when working with content providers\n* provide a certain level of type safety, i.e. reduce the risk of writing data to the wrong table\n* declarative way to describe operations\n* utilize IPC limit as good as possible in order to make big transactions efficient\n* automatically resolve references when inserting related rows in a single transaction\n\n## ContactsPal\n\nContactsPal provides ContentProvider specific classes including some of the most important `Table` and `RowData` implementations like `RawContacts`\nand `PhoneData`.\n\n## CalendarPal\n\nCalendarPal provides CalendarProvider specific classes including some of the most important `Table` and `RowData` implementations like `Events`\nand `ReminderData`.\n\n## Example\n\n### Insert a Contact\n\n#### Without ContactsPal\n\nThe following example is taken from\nAndroid's [ContactsProvider Documentation](https://developer.android.com/guide/topics/providers/contacts-provider.html#Transactions). It shows the basic steps\nto create a new contact with display name, phone and email.\n\n```java\nArrayList\u003cContentProviderOperation\u003e ops =\n    new ArrayList\u003cContentProviderOperation\u003e();\n\nContentProviderOperation.Builder op =\n    ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)\n    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())\n    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());\nops.add(op.build());\n\nop = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)\n    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)\n    .withValue(ContactsContract.Data.MIMETYPE,\n        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)\n    .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);\nops.add(op.build());\n\nop = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)\n    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)\n    .withValue(ContactsContract.Data.MIMETYPE,\n        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)\n    .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)\n    .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);\nops.add(op.build());\n\nop = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)\n    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)\n    .withValue(ContactsContract.Data.MIMETYPE,\n        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)\n    .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)\n    .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);\nop.withYieldAllowed(true);\nops.add(op.build());\n\ngetContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);\n```\n\n#### With ContactsPal\n\nThe following code snippets show the same operation with ContentPal/ContactsPal:\n\n##### Step 1 - setup\n\nBefore you can perform any operations you need to create a few required objects like the tables to work with. Usually this has to be done only once.\n\n```java\nContentProviderClient client =\n  getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY_URI);\nOperationsQueue operationsQueue = new BasicOperationsQueue(client);\n```\n\n##### Step 2 - execute\n\nCreate an `OperationsBatch` which contains the operations to insert the RawContact and the data rows and enqueue them for execution.\n\n```java\noperationsQueue.enqueue(\n  new Yieldable( // optional, not required when inserting only one contact\n    new InsertRawContactBatch(\n      account,\n      // list the data to be inserted\n      new DisplayNameData(name),\n      new Typed(phoneType, new PhoneData(phone)),\n      new Typed(emailType, new EmailData(email))))));\n```\n\nAt this point you can enqueue more operations. All operations will be executed automatically when the transaction size grows too large or when `flush()` is\ncalled.\n\nReferences between the inserted rows will be resolved automatically.\n\nAlso note that all relevant types are generic having the contract they implement as the generic type. That makes it much harder to get confused. E.g. you can\nnot accidentally try to insert an email address into RawContacts.\n\n##### Step 3 - shut down\n\nOnce everything is done, just make sure all pending operations get committed and close the `ContentProviderClient`\n\n```java\noperationsQueue.flush();\nclient.release();\n```\n\n### Reading Contacts\n\nTo read contacts you build a view onto the `Table` to read and iterate over a `RowSet` that contains the rows you need.\n\n```java\n// a sync-adapter view onto RawContacts, scoped to a specific account\nView\u003cContactsContract.RawContacts\u003e rawContacts =\n  new Synced\u003c\u003e(new AccountScoped\u003c\u003e(new RawContacts(client), account));\n    \n// iterate over all \"dirty\" contacts \nfor (RowSnapshot\u003cContactsContract.RawContacts\u003e rowSnapshot:new Dirty\u003c\u003e(rawContacts))\n{\n  // work with the row snapshot\n   \n  // for instance, set the dirty flag to 0 like so\n  operationsQueue.enqueue(\n    new SingletonOperationsBatch(\n      new new Put\u003c\u003e(rowSnapshot, new CleanData())));\n}\n```\n\nThe same can also be achieved in a declarative way like this:\n\n```java\noperationsQueue.enqueue(\n  new MappedRowSetBatch\u003c\u003e(\n    // map all dirty rows of a specific account\n    new Dirty\u003c\u003e(new Synced\u003c\u003e(new AccountScoped\u003c\u003e(new RawContacts(client), account))),\n    new Function\u003cContactsContract.RawContacts, Operation\u003c\u003e\u003e()\n    {\n      public Operation\u003c\u003e apply(RowSnapshot\u003cContactsContract.RawContacts\u003e rowSnapwhot)\n      {\n        // work with the row and return an Operation, can be a NoOp or this:\n        return new Put\u003c\u003e(rowSnapshot, new CleanData());\n      }\n    }\n  )\n);\n```\n\n## License\n\nCopyright dmfs GmbH 2017, licensed under Apache2.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmfs%2Fcontentpal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmfs%2Fcontentpal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmfs%2Fcontentpal/lists"}