Skip to content
Development
Skill

/flutter-app-architecture

Use when scaffolding a project, refactoring into layers, creating view models/repositories, configuring dependency injection, or implementing unidirectional data flow (MVVM).

From plugin
flutter-ai-skills
63737 skills
Install
$ npx -y skills add evanca/flutter-ai-rules --skill flutter-app-architecture --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/flutter-app-architecture

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when scaffolding a project, refactoring into layers, creating view models/repositories, configuring dependency injection, or implementing unidirectional data flow (MVVM).

SKILL.md

flutter-app-architecture.SKILL.md
name: flutter-app-architecture
description: "Use when scaffolding a project, refactoring into layers, creating view models/repositories, configuring dependency injection, or implementing unidirectional data flow (MVVM)."
license: MIT

Flutter App Architecture Skill

This skill defines how to structure Flutter applications using layered architecture, proper data flow, and MVVM patterns for maintainability and testability.

When to Use

Use this skill when:

  • Scaffolding a new Flutter project with layered architecture.
  • Creating or refactoring View Models, Repositories, or Services.
  • Wiring dependency injection between architectural components.
  • Implementing unidirectional data flow across layers.
  • Adding a Domain (Logic) Layer for complex business logic or shared use cases.

---

1. Layer Structure

Separate every app into a **UI Layer** and a **Data Layer**. Add a **Logic (Domain) Layer** only for complex apps.

┌──────────────────────────────────────────────────────────────┐
│   UI Layer    │  Views + ViewModels                           │
├──────────────────────────────────────────────────────────────┤
│  Logic Layer  │  Use Cases / Interactors  (optional)         │
├──────────────────────────────────────────────────────────────┤
│   Data Layer  │  Repositories + Services                     │
└──────────────────────────────────────────────────────────────┘

**Rules:**

  • Only adjacent layers may communicate. The UI layer must never access a Service directly.
  • Data changes always happen in the Data layer (SSOT = Repository). No mutation in UI or Logic layers.
  • Follow unidirectional data flow: state flows **down** (Data → UI), events flow **up** (UI → Data).

---

2. Component Responsibilities

View

  • Describes how to present data; keep logic minimal and UI-related only.
  • Passes events to the ViewModel in response to user interactions.

ViewModel

  • Converts app data into UI state and maintains the current state needed by the View.
  • Exposes callbacks (commands) to the View and retrieves/transforms data from Repositories.
class BookingViewModel extends ChangeNotifier {
  final BookingRepository _repo;

  BookingViewModel(this._repo);

  List<Booking> _bookings = [];
  List<Booking> get bookings => List.unmodifiable(_bookings);

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  Future<void> loadBookings() async {
    _isLoading = true;
    notifyListeners();

    _bookings = await _repo.getBookings();
    _isLoading = false;
    notifyListeners();
  }

  Future<void> cancelBooking(String id) async {
    await _repo.cancelBooking(id);
    _bookings = await _repo.getBookings();
    notifyListeners();
  }
}

Repository (Single Source of Truth)

  • The only class that may mutate its data; all other classes read from it.
  • Handles caching, error handling, and data refresh logic.
  • Transforms raw data from Services into domain models.
class BookingRepository {
  final BookingApiService _apiService;
  final BookingLocalService _localService;

  BookingRepository(this._apiService, this._localService);

  Future<List<Booking>> getBookings() async {
    try {
      final remote = await _apiService.fetchBookings();
      await _localService.cacheBookings(remote);
      return remote;
    } catch (_) {
      return _localService.getCachedBookings();
    }
  }

  Future<void> cancelBooking(String id) async {
    await _apiService.cancelBooking(id);
    await _localService.removeCachedBooking(id);
  }
}

Service

  • Wraps API endpoints and exposes asynchronous response objects.
  • Isolates data-loading and holds no state.
class BookingApiService {
  final http.Client _client;
  BookingApiService(this._client);

  Future<List<Booking>> fetchBookings() async {
    final response = await _client.get(Uri.parse('/api/bookings'));
    if (response.statusCode != 200) {
      throw HttpException('Failed to load bookings');
    }
    final data = jsonDecode(response.body) as List;
    return data.map((json) => Booking.fromJson(json)).toList();
  }
}

---

3. Dependency Injection

Supply dependencies via constructors. Define abstract interfaces so implementations can be swapped for testing.

// Abstract interface for the repository
abstract class BookingRepository {
  Future<List<Booking>> getBookings();
  Future<void> cancelBooking(String id);
}

// Concrete implementation
class BookingRepositoryImpl implements BookingRepository {
  final BookingApiService _api;
  BookingRepositoryImpl(this._api);

  @override
  Future<List<Booking>> getBookings() => _api.fetchBookings();

  @override
  Future<void> cancelBooking(String id) => _api.cancelBooking(id);
}

---

4. Use Cases (Domain Layer)

Introduce use cases only when:

  • Logic is complex or does not fit cleanly in the UI or Data layers.
  • Logic is reused across multiple ViewModels or merges data from multiple Repositories.
class GetUpcomingBookingsUseCase {
  final BookingRepository _bookingRepo;
  final UserRepository _userRepo;

  GetUpcomingBookingsUseCase(this._bookingRepo, this._userRepo);

  Future<List<Booking>> call() async {
    final user = await _userRepo.getCurrentUser();
    final bookings = await _bookingRepo.getBookings();
    return bookings
        .where((b) => b.userId == user.id && b.date.isAfter(DateTime.now()))
        .toList();
  }
}

---

5. Workflow: Scaffold a New Feature

1. **Create the Service** — implement the API wrapper with typed response parsing. 2. **Create the Repository** — inject the Service, implement caching and error-handling logic. 3. **Create the ViewModel** — inject the Repository, expose UI state and commands. 4. **Create the View** — bind to the ViewModel, render state, dispatch events. 5. **Wire DI** — register all components in the dependency injection container. 6. **Verify** — confirm the View never accesses the Service directly and data flows unidirectionally.

---

6. Data Sto

Read more
Ships withflutter-ai-skills

36 Flutter and Dart skills your coding agent loads by itself, sourced only from official documentation. A skill is a folder with a SKILL.md file.

Get the whole plugin
Stats
637
Stars
66
Forks
Active
Maintenance
Shell
Language
MIT
License
1d ago
Last commit
1y ago
Created

Repo: evanca/flutter-ai-rules

Other skills on flutter-ai-skills.