An independent, pure Dart contacts subsystem. Public common API is busystack_contacts.dart; provider-specific libraries are google.dart, microsoft.dart and carddav.dart. The Google library exports selected generated People types. This package has no Flutter, Riverpod, native credential store or application imports.
Use ContactsDirectory with an explicit application-owned ContactsStore, then bind account-scoped providers. Providers receive authorized callbacks/transports; this package does not implement login. Google uses googleapis 17's generated People client. Microsoft uses busystack_graph. CardDAV uses busystack_dav. Nextcloud is a connection setup for the same CardDAV provider.
ContactIdentity is qualified by account/provider/source/remote ID. ContactProjection is a browsing/recipient projection, never a replacement document. GoogleContactRecord, MicrosoftContactRecord and CardDavContactRecord retain complete native data and revisions. Common ContactPatch fields use FieldEdit.unchanged, .set and .clear; repeated values retain native identities and unknown subproperties. Provider-specific GoogleGroupAction, GoogleFieldsAction, MicrosoftFolderAction, MicrosoftFieldsAction and CardDavAddressBookAction pass through directory.providerAction, the same account serialization, journal and reconciliation path as common edits.
Google contact groups, Microsoft folders/categories and DAV address books remain distinct concepts. Source permissions and cardinality are validated before writing. Non-contact-owned Google fields remain readable and read only. Google uses CONTACT-source ETags and intended update masks. Microsoft PATCH uses intended fields, whole structured postal properties and consistent immutable IDs. A changeKey preflight detects observed changes; there is no claimed atomic If-Match guarantee for the contacts endpoint. CardDAV full replacement requires a complete card, UID and conditional ETag. vCard 3 and advertised vCard 4, Unicode/folding, repeated/structured values, unknown lines/parameters/groups and bounded photos are supported. Common name, organization and notes edits affect the displayed primary values and preserve alternate native values. Clearing a primary note with preserved variants keeps an empty primary slot; a sole note is removed. Cross-provider import previews warn about additional names/notes that the destination cannot represent.
Synchronization commits native records, browse/search projections and the corresponding per-source cursor atomically. Interrupted full scans retain unseen cached records. Expired cursors trigger a safe rebuild. Pending and newly confirmed local writes are protected from delayed Google echoes. Mutations persist as queued/executing/confirmed/failed/unknown/conflict. Restart converts interrupted dispatched work into an unknown outcome; it does not invent remote success. Unknown creates are never blindly replayed: Google/CardDAV use positive marker/resource reconciliation; Microsoft requires an explicit selected remote match. Conflicts retain base/local/remote; rebasing applies only the selected fields. An account disconnect fences stale completions and affects that account only.
The persistence transaction callback and every transaction operation are awaited, atomic and must not escape their scope. Implementations preserve isolation across asynchronous gaps, roll back failures and emit change notifications only after commit. projections may return lightweight list/search data; details/editing must load the full contact record. BusyCards' SQLCipher adapter is a durable production implementation. The test-only transaction store is not used by the application. Apps own database/key protection, scheduling and lifecycle; no shared physical store is required.
Recipient suggestions page through cached matching projections in one read transaction until the requested number of distinct, non-excluded addresses is available or the selected sources are exhausted. Empty addresses do not count. Results retain account/source/page order and group matching identities by case-insensitive email within the pages read. A full page is processed to retain aliases; no further page is requested once the limit is satisfied. The matching count bounds pagination in the same snapshot, including stores that repeat rows.
Read retries are bounded and owned by the directory; transports do not multiply them. Account serialization and a three-account concurrency bound are independent from UI frames. Large JSON/vCard parsing uses isolates. Photos are lazy, bounded to 2 MiB and use public HTTPS downloads without forwarding account tokens to photo hosts.
dart pub get --enforce-lockfile
dart format --output=none --set-exit-if-changed lib test example
dart analyze
dart test
dart run example/main.dart
Requires Dart 3.13.4. main is the authoritative development branch at https://github.com/busystack/busystack_contacts.git. Consumers use sibling path dependencies and pin an exact tested commit from main in tool/shared_dependencies.json; run their tool/checkout_shared_dependencies.py before dependency resolution. Local dependencies are exactly ../busystack_dav and ../busystack_graph; this package's manifest pins their unchanged source trees at their current main commits. The four-target source archive recreates that layout. A publishable release replaces those two paths with exact hosted versions of the same packages after owner authorization; no publication occurred. See BusyCards' provider references and delivery report for supported endpoint limitations, fixtures, durable-store measurements and unavailable live/native gates. Apache-2.0.