Skills Plugins MCP Prompt Model 博客 我的中心

flutter-offline-first

Lead authority for offline-first Flutter architecture. Use when designing local-first apps, sync engines, conflict resolution, optimistic UI, retry queues, background sync, or mission-critical apps requiring reliability without connectivity.

DeepseekModel Curated skill Quality Good · 48 v1.0.0

Get

https://deepseekmodel.com/api/download.php?id=adrigm06-flutter-engineer-skills-flutter-offline-first-skill-md&format=skill
Download .skill Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
name flutter-offline-first description Lead authority for offline-first Flutter architecture. Use when designing local-first apps, sync engines, conflict resolution, optimistic UI, retry queues, background sync, or mission-critical apps requiring reliability without connectivity. Flutter Offline-First Skill Purpose Design and implement local-first Flutter architectures that remain fully functional without network connectivity and synchronize reliably when connectivity is restored. Scope and authority Lead authority for: offline-first architectural patterns (local-first, event sourcing, CQRS-lite) sync engine design (bidirectional sync, delta sync) conflict resolution strategies (LWW, CRDT, manual merge) optimistic UI with rollback retry queue design and persistence background sync (WorkManager, Dart isolates) network connectivity awareness Defers to: flutter-storage for persistence layer implementation (Drift, Isar) flutter-networking for remote sync transport (Dio) flutter-state-management for UI state during sync When to use Designing apps for unreliable connectivity (field workers, developing regions) Mission-critical apps requiring offline reliability (medical, logistics, payments) Building collaborative features requiring conflict resolution Designing sync between local and remote state Optimistic updates with network confirmation Offline-first architecture patterns Pattern 1: Repository sync (recommended for most apps) UI → Riverpod Notifier → Repository (single source of truth) ↓ ↑ Local DB (Drift) Remote API (Dio) ↕ Background Sync Service The Repository exposes Streams backed by local data. Remote sync updates the local DB, which reactively updates the Stream, which updates the UI. // Offline-first read: Stream<List<Order>> watchOrders() async* { // 1. Immediately yield local data yield await _localSource.getOrders(); // 2. Sync with remote in background unawaited(_syncOrders()); // 3. Watch local DB for changes (reactive) yield* _localSource.watchOrders(); } Future<void> _syncOrders() async { try { final remoteOrders = await _remoteSource.fetchOrders(); await _localSource.replaceOrders(remoteOrders); } on NetworkException { // Silent fail — UI has local data already _logger.warning('Sync failed — will retry on next foreground'); } } Pattern 2: Optimistic writes with sync queue // Optimistic write: persist locally first, then sync Future<Result<void>> createOrder(Order order) async { // 1. Persist locally immediately (optimistic) final localOrder = order.copyWith( id: _generateLocalId(), syncStatus: SyncStatus.pending, ); await _localSource.insertOrder(localOrder); // 2. Attempt immediate remote sync try { final remoteOrder = await _remoteSource.createOrder(order); // 3a. Mark as synced with remote ID await _localSource.markSynced(localOrder.id, remoteId: remoteOrder.id); return const Result.success(null); } on NetworkException { // 3b. Queue for background retry await _syncQueue.enqueue(SyncTask.createOrder(localOrder.id)); return const Result.success(null); // Optimistic — show success to user } } Pattern 3: Background sync queue (Drift-backed) // Sync queue persisted in local DB — survives app restart @DataClassName('SyncTaskEntry') class SyncQueue extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get taskType => text()(); TextColumn get payload => text()(); // JSON IntColumn get retryCount => integer().withDefault(const Constant(0))(); DateTimeColumn get nextRetryAt => dateTime().nullable()(); BoolColumn get isProcessing => boolean().withDefault(const Constant(false))(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); } class SyncQueueService { final SyncQueueDao _dao; final RemoteDataSource _remote; Timer? _syncTimer; void startPeriodicSync({Duration interval = const Duration(minutes: 5)}) { _syncTimer = Timer.periodic(interval, (_) => processQueue()); } Future<void> processQueue() async { final pendingTasks = await _dao.getPendingTasks(limit: 10); for (final task in pendingTasks) { if (task.retryCount >= 5) { await _dao.markFailed(task.id); continue; } try { await _executeTask(task); await _dao.delete(task.id); } catch (e) { // Exponential backoff final nextRetry = DateTime.now().add( Duration(minutes: 1 << task.retryCount), ); await _dao.incrementRetry(task.id, nextRetryAt: nextRetry); } } } void dispose() => _syncTimer?.cancel(); } Conflict resolution strategies Last-Write-Wins (LWW) // Simple: latest timestamp wins ResolvedRecord resolveConflict(LocalRecord local, RemoteRecord remote) { return local.updatedAt.isAfter(remote.updatedAt) ? ResolvedRecord.fromLocal(local) : ResolvedRecord.fromRemote(remote); } When to use: non-collaborative data, simple user preferences, last-edit wins acceptably. Field-level merge (collaborative editing) // Merge non-conflicting field changes ResolvedOrder mergeOrder(LocalOrder local, RemoteOrder remote, BaseOrder base) { return ResolvedOrder( // Field changed on local but not remote: keep local title: local.title != base.title ? local.title : remote.title, // Field changed on remote but not local: keep remote status: remote.status != base.status ? remote.status : local.status, // Both changed: conflict — flag for user resolution assigneeId: (local.assigneeId != base.assigneeId && remote.assigneeId != base.assigneeId) ? null // ConflictMarker : local.assigneeId != base.assigneeId ? local.assigneeId : remote.assigneeId, ); } CRDT (conflict-free replicated data types) Use for collaborative documents, shared lists, counters. Consider automerge-dart for complex collaborative scenarios. Connectivity awareness // core/network/connectivity_service.dart @riverpod Stream<bool> isOnline(Ref ref) async* { final connectivity = Connectivity(); yield await _checkConnectivity(connectivity); yield* connectivity.onConnectivityChanged .map((result) => result != ConnectivityResult.none); } // Repository using connectivity awareness: Future<void> sync() async { final online = await ref.read(isOnlineProvider.future); if (!online) { _logger.info('Offline — skipping sync, queuing for later'); return; } await processQueue(); } Optimistic UI with rollback // Notifier with optimistic update and rollback @riverpod class OrderListNotifier extends _$OrderListNotifier { @override Future<List<Order>> build() async { return ref.watch(orderRepositoryProvider).getOrders(); } Future<void> deleteOrder(String orderId) async { // Save previous state for rollback final previousState = state; // Optimistic update: remove from UI immediately state = AsyncData( state.value!.where((o) => o.id != orderId).toList(), ); try { await ref.read(orderRepositoryProvider).deleteOrder(orderId); } catch (e) { // Rollback on failure state = previousState; // Show error to user ref.read(snackbarProvider.notifier).showError('Failed to delete order'); } } } Sync status indicators (UI pattern) sealed class SyncStatus { const SyncStatus(); } class SyncIdle extends SyncStatus {} class SyncInProgress extends SyncStatus {} class SyncSuccess extends SyncStatus { final DateTime lastSynced; const SyncSuccess(this.lastSynced); } class SyncFailed extends SyncStatus { final String error; final int pendingCount; const SyncFailed({required this.error, required this.pendingCount}); } Anti-pattern detection Remote data fetched in widget build() (no local cache first) No optimistic updates (shows loading spinner for every mutation) Sync queue without retry limits (infinite retry loop) No conflict resolution strategy documented (leads to silent data loss) Background sync without network connectivity check Sync queue not persisted (lost on app restart) No sync status shown to user (they don't know data is stale) Cross-skill handoff payload Use the standard payload from ../../AGENTS.md . Set requesting_skill to flutter-offline-first . Output contract Follow global section order from ../../AGENTS.md . Also include: Offline-first pattern selection and justification Sync architecture diagram Conflict resolution strategy Sync queue design Optimistic update plan Connectivity awareness implementation Related resources references/offline-first-patterns.md references/conflict-resolution.md references/sync-queue-design.md templates/offline-repository.dart templates/sync-queue-service.dart templates/optimistic-notifier.dart
Keywords that activate this skill. Click one to copy it.

This skill does not provide trigger words.

The downloaded .skill package contains the following fields.
Field Description
formatFormat tag (skill/v1)
skill_idUnique skill ID
nameSkill name
versionVersion
descriptionDescription
categoryCategories (array)
trigger_wordsTrigger words
tagsTags
sourceSource
source_urlSource URL (this page)
exported_atExported at (set per download)
system_promptSystem prompt body
model_configModel config: provider / model / temperature / max_tokens / top_p
examplesExamples
install_guideImport guide for Coze / Dify / Claude / custom frameworks
The same skill can be exported in different platform formats.
.skill Standard format with system_prompt and model_config, ready for any agent framework Download
.skillpro Enhanced format with scripts, tools, dependencies and hooks Download
.json Plain JSON export with system_prompt and model parameters only Download
Coze Markdown with frontmatter, for Coze platform import Download
Dify Dify DSL, import directly after creating an app Download

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。