Back to Rules
Flutter90% popularity

Flutter Clean Architecture Layers

Structure Flutter applications with domain, data, and presentation layers for maintainability.

Reso CoderUpdated Jan 20, 2024

Overview

Clean Architecture in Flutter establishes clear boundaries between business logic, data handling, and UI code. This separation enables independent development, easier testing, and better maintainability as applications grow in complexity. The three-layer model—domain, data, and presentation—each serves a distinct purpose. The domain layer contains pure Dart code with no Flutter or external dependencies. Entities represent core business objects, use cases encapsulate single business operations, and repository interfaces define data access contracts. This layer is entirely testable without mocking Flutter components. Entities should be simple data classes, while use cases follow the single responsibility principle—one use case per business operation. The data layer implements repository interfaces defined in the domain layer. Data sources handle actual data retrieval from APIs, databases, or local storage. Repository implementations coordinate between multiple data sources if needed, transform data between entity and model representations, and handle caching strategies. Models extend entities with serialization capabilities for network or local storage. The presentation layer contains widgets, state management, and view models. It depends only on the domain layer, never directly accessing data sources. This dependency flow ensures that business rules remain consistent regardless of UI changes. State management solutions like Riverpod or Bloc sit here, connecting domain use cases to the UI while managing application state reactively.

Code Example

.flutterrules
// Domain Layer - Entities
class Article {
  final String id;
  final String title;
  final String content;
  final String authorId;
  final DateTime publishedAt;
  final List<String> tags;

  const Article({
    required this.id,
    required this.title,
    required this.content,
    required this.authorId,
    required this.publishedAt,
    required this.tags,
  });
}

// Domain Layer - Repository Interface
abstract class ArticleRepository {
  Future<List<Article>> getArticles();
  Future<Article?> getArticleById(String id);
  Future<void> createArticle(Article article);
  Future<void> deleteArticle(String id);
}

// Domain Layer - Use Case
class GetArticlesUseCase {
  final ArticleRepository repository;

  GetArticlesUseCase(this.repository);

  Future<List<Article>> call() async {
    return repository.getArticles();
  }
}

// Data Layer - Model
class ArticleModel extends Article {
  const ArticleModel({
    required super.id,
    required super.title,
    required super.content,
    required super.authorId,
    required super.publishedAt,
    required super.tags,
  });

  factory ArticleModel.fromJson(Map<String, dynamic> json) {
    return ArticleModel(
      id: json['id'],
      title: json['title'],
      content: json['content'],
      authorId: json['author_id'],
      publishedAt: DateTime.parse(json['published_at']),
      tags: List<String>.from(json['tags']),
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'content': content,
      'author_id': authorId,
      'published_at': publishedAt.toIso8601String(),
      'tags': tags,
    };
  }
}

// Data Layer - Repository Implementation
class ArticleRepositoryImpl implements ArticleRepository {
  final ArticleRemoteDataSource remoteDataSource;

  ArticleRepositoryImpl({required this.remoteDataSource});

  @override
  Future<List<Article>> getArticles() async {
    final models = await remoteDataSource.fetchArticles();
    return models;
  }
}

// Presentation Layer - Riverpod Provider
final getArticlesUseCaseProvider = Provider<GetArticlesUseCase>((ref) {
  return GetArticlesUseCase(ref.watch(articleRepositoryProvider));
});

final articlesProvider = FutureProvider<List<Article>>((ref) async {
  final useCase = ref.watch(getArticlesUseCaseProvider);
  return useCase();
});

More Flutter Rules

FLUTTER
92%

Flutter State Management with Riverpod

Use Riverpod for reactive state management with compile-time safety and testable provider architecture.

state-managementriverpodarchitecture
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../models/todo.dart';
im...
Mar 10, 2024by Remi Rousselet
View Rule
FLUTTER
88%

Flutter BLoC Pattern Implementation

Implement BLoC for predictable state management with clear event-state separation.

flutterblocstate-management
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:equatable/equatable.dart...
Feb 18, 2024by Felix Angelov
View Rule
FLUTTER
87%

Flutter Navigation with GoRouter

Implement declarative routing with GoRouter for deep linking and nested navigation.

fluttergorouternavigation
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '....
Feb 22, 2024by Chris Cox
View Rule