| name | shiny-contactstore |
| description | Generate code using Shiny.Maui.ContactStore for cross-platform device contact access with CRUD, LINQ queries, and MAUI permissions |
| auto_invoke | true |
| triggers | ["contact store","contacts","IContactStore","ContactStore","AddContactStore","ContactPermission","ContactReadPermission","ContactWritePermission","Shiny.Maui.ContactStore","device contacts","read contacts","write contacts","contact query","contact LINQ"] |
Shiny.Maui.ContactStore Skill
You are an expert in Shiny.Maui.ContactStore, a cross-platform .NET MAUI library for accessing device contacts on Android and iOS.
When to Use This Skill
Invoke this skill when the user wants to:
- Access device contacts (read, create, update, delete)
- Query contacts with LINQ
- Set up contact permissions using MAUI permissions
- Register the contact store in DI
- Work with contact models (phones, emails, addresses, etc.)
Library Overview
GitHub: https://github.com/shinyorg/contactstore
NuGet: Shiny.Maui.ContactStore
Namespace: Shiny.Maui.ContactStore
Shiny.Maui.ContactStore provides:
- Full CRUD operations on device contacts
- LINQ query support with native translation (Android content provider queries, iOS CNContact predicates)
- MAUI permission classes for requesting contact access
- Dependency injection integration
- AOT and trimmer compatible
Setup
1. Install NuGet Package
dotnet add package Shiny.Maui.ContactStore
2. Register in MauiProgram.cs
using Shiny.Maui.ContactStore;
builder.Services.AddContactStore();
3. Platform Permissions
Android — Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
iOS — Add to Info.plist:
<key>NSContactsUsageDescription</key>
<string>This app needs access to your contacts.</string>
Permissions
The library provides a MAUI permission class ContactPermission that wraps both read and write contact permissions.
Use the extension methods on IContactStore:
var status = await contactStore.RequestPermssionsAsync();
if (status != PermissionStatus.Granted)
{
return;
}
var status = await contactStore.CheckPermissionStatusAsync();
Android Permission Results
PermissionStatus.Granted — both read and write access granted
PermissionStatus.Limited — only read or only write granted (not both)
PermissionStatus.Denied — neither read nor write granted
iOS Permission Results
PermissionStatus.Granted — contacts access authorized
PermissionStatus.Denied — contacts access denied
PermissionStatus.Restricted — contacts access restricted
API Reference
IContactStore Interface
public interface IContactStore
{
Task<IReadOnlyList<Contact>> GetAll(CancellationToken ct = default);
Task<Contact?> GetById(string contactId, CancellationToken ct = default);
IQueryable<Contact> Query();
Task<string> Create(Contact contact, CancellationToken ct = default);
Task Update(Contact contact, CancellationToken ct = default);
Task Delete(string contactId, CancellationToken ct = default);
}
Extension Methods
Task<PermissionStatus> contactStore.RequestPermssionsAsync();
Task<PermissionStatus> contactStore.CheckPermissionStatusAsync();
Task<IReadOnlyList<char>> contactStore.GetFamilyNameFirstLetters(CancellationToken ct = default);
Query with LINQ
The library translates LINQ predicates to native queries where possible, with in-memory fallback.
var results = contactStore.Query()
.Where(c => c.GivenName.Contains("John"))
.ToList();
var results = contactStore.Query()
.Where(c => c.Phones.Any(p => p.Number.Contains("555")))
.ToList();
var results = contactStore.Query()
.Where(c => c.Emails.Any(e => e.Address.Contains("@example.com")))
.ToList();
var results = contactStore.Query()
.Where(c => c.GivenName.StartsWith("J") && c.FamilyName.Contains("Smith"))
.ToList();
var page = contactStore.Query()
.Where(c => c.FamilyName.StartsWith("A"))
.Skip(10)
.Take(20)
.ToList();
Supported operations: Contains, StartsWith, EndsWith, Equals
Filterable properties: GivenName, FamilyName, MiddleName, NamePrefix, NameSuffix, Nickname, DisplayName, Note
Filterable collections: Phones (by Number), Emails (by Address)
Create a Contact
var contact = new Contact
{
GivenName = "John",
FamilyName = "Doe",
Note = "Met at conference"
};
contact.Phones.Add(new ContactPhone("555-1234", PhoneType.Mobile));
contact.Emails.Add(new ContactEmail("john@example.com", EmailType.Work));
string id = await contactStore.Create(contact);
Update a Contact
var contact = await contactStore.GetById(id);
contact.GivenName = "Jane";
await contactStore.Update(contact);
Delete a Contact
await contactStore.Delete(contactId);
Models
Contact
| Property | Type |
|---|
| Id | string? |
| NamePrefix | string? |
| GivenName | string? |
| MiddleName | string? |
| FamilyName | string? |
| NameSuffix | string? |
| Nickname | string? |
| DisplayName | string |
| Note | string? |
| Organization | ContactOrganization? |
| Photo | byte[]? |
| Thumbnail | byte[]? |
| Phones | List<ContactPhone> |
| Emails | List<ContactEmail> |
| Addresses | List<ContactAddress> |
| Dates | List<ContactDate> |
| Relationships | List<ContactRelationship> |
| Websites | List<ContactWebsite> |
Enums
PhoneType: Home, Mobile, Work, FaxWork, FaxHome, Pager, Other, Custom
EmailType: Home, Work, Other, Custom
AddressType: Home, Work, Other, Custom
ContactDateType: Birthday, Anniversary, Other, Custom
RelationshipType: Father, Mother, Parent, Brother, Sister, Child, Friend, Spouse, Partner, Assistant, Manager, Other, Custom
iOS Notes & Relations Entitlement
Reading Note and Relationships on iOS requires the com.apple.developer.contacts.notes entitlement. The library auto-detects this at runtime. If absent, Note returns null and Relationships is empty.
Best Practices
- Always request permissions first — use
contactStore.RequestPermssionsAsync() before any CRUD operation
- Use LINQ queries for filtering — prefer
Query().Where(...) over GetAll() when filtering, as it uses native queries
- Check for Limited on Android —
PermissionStatus.Limited means partial access (read-only or write-only)
- Handle iOS entitlements gracefully — Notes and Relations silently return empty without the entitlement
- Use primary constructors — inject
IContactStore via primary constructor