plugins/shiny-maui/skills/shiny-contactstore/SKILL.md
Generate code using Shiny.Maui.ContactStore for cross-platform device contact access with CRUD, LINQ queries, and MAUI permissions
npx skillsauth add shinyorg/skills shiny-contactstoreInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in Shiny.Maui.ContactStore, a cross-platform .NET MAUI library for accessing device contacts on Android and iOS.
Invoke this skill when the user wants to:
GitHub: https://github.com/shinyorg/contactstore
NuGet: Shiny.Maui.ContactStore
Namespace: Shiny.Maui.ContactStore
Shiny.Maui.ContactStore provides:
dotnet add package Shiny.Maui.ContactStore
using Shiny.Maui.ContactStore;
builder.Services.AddContactStore();
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>
The library provides a MAUI permission class ContactPermission that wraps both read and write contact permissions.
Use the extension methods on IContactStore:
// Request permissions (triggers OS prompt if needed)
var status = await contactStore.RequestPermssionsAsync();
if (status != PermissionStatus.Granted)
{
// Handle denied
return;
}
// Check current status without prompting
var status = await contactStore.CheckPermissionStatusAsync();
PermissionStatus.Granted — both read and write access grantedPermissionStatus.Limited — only read or only write granted (not both)PermissionStatus.Denied — neither read nor write grantedPermissionStatus.Granted — contacts access authorizedPermissionStatus.Denied — contacts access deniedPermissionStatus.Restricted — contacts access restrictedpublic 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);
}
// Permission extensions
Task<PermissionStatus> contactStore.RequestPermssionsAsync();
Task<PermissionStatus> contactStore.CheckPermissionStatusAsync();
// Query extensions
Task<IReadOnlyList<char>> contactStore.GetFamilyNameFirstLetters(CancellationToken ct = default);
The library translates LINQ predicates to native queries where possible, with in-memory fallback.
// Filter by name
var results = contactStore.Query()
.Where(c => c.GivenName.Contains("John"))
.ToList();
// Filter by phone number
var results = contactStore.Query()
.Where(c => c.Phones.Any(p => p.Number.Contains("555")))
.ToList();
// Filter by email
var results = contactStore.Query()
.Where(c => c.Emails.Any(e => e.Address.Contains("@example.com")))
.ToList();
// Combine filters
var results = contactStore.Query()
.Where(c => c.GivenName.StartsWith("J") && c.FamilyName.Contains("Smith"))
.ToList();
// Paging
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)
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("[email protected]", EmailType.Work));
string id = await contactStore.Create(contact);
var contact = await contactStore.GetById(id);
contact.GivenName = "Jane";
await contactStore.Update(contact);
await contactStore.Delete(contactId);
| 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> |
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
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.
contactStore.RequestPermssionsAsync() before any CRUD operationQuery().Where(...) over GetAll() when filtering, as it uses native queriesPermissionStatus.Limited means partial access (read-only or write-only)IContactStore via primary constructordevops
Guide for implementing push notifications in .NET MAUI apps using Shiny.Push (native FCM/APNs) and Shiny.Push.AzureNotificationHubs
tools
Cross-platform local notification management for .NET MAUI apps using Shiny, supporting scheduled, repeating, and geofence-triggered notifications with channels, badges, and interactive actions.
tools
GPS tracking, geofence monitoring, and motion activity recognition for .NET MAUI, iOS, and Android using Shiny.Locations
data-ai
Background job scheduling and execution for .NET MAUI (iOS/Android native OS schedulers) and in-process jobs for plain .NET, Linux, macOS, and Blazor WASM using Shiny.Jobs