Bird
Raised Fist0
Fluttermobile~20 mins

Clean Architecture layers in Flutter - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Clean Architecture Layers Demo
This screen shows a simple Flutter app demonstrating the three main layers of Clean Architecture: Presentation, Domain, and Data. It displays a list of tasks fetched from a data source through the domain layer.
Target UI
-------------------------
| Clean Architecture    |
|-----------------------|
| Tasks:                |
| 1. Buy groceries      |
| 2. Walk the dog       |
| 3. Read a book        |
|                       |
| [Refresh]             |
-------------------------
Display a list of tasks on the screen.
Use three layers: Presentation (UI), Domain (business logic), Data (data source).
Presentation layer calls Domain layer to get tasks.
Domain layer defines a UseCase to fetch tasks.
Data layer provides a repository implementation returning a fixed list of tasks.
Add a Refresh button to reload tasks.
Starter Code
Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: TasksScreen(),
    );
  }
}

class TasksScreen extends StatefulWidget {
  const TasksScreen({super.key});

  @override
  State<TasksScreen> createState() => _TasksScreenState();
}

class _TasksScreenState extends State<TasksScreen> {
  // TODO: Add domain layer use case instance
  // TODO: Add list of tasks state

  @override
  void initState() {
    super.initState();
    // TODO: Load tasks initially
  }

  Future<void> _loadTasks() async {
    // TODO: Call domain layer to get tasks and update state
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Clean Architecture')),
      body: Column(
        children: [
          const Padding(
            padding: EdgeInsets.all(8.0),
            child: Text('Tasks:', style: TextStyle(fontSize: 20)),
          ),
          Expanded(
            child: ListView(
              children: [
                // TODO: Show tasks here
              ],
            ),
          ),
          ElevatedButton(
            onPressed: _loadTasks,
            child: const Text('Refresh'),
          ),
        ],
      ),
    );
  }
}

// TODO: Define domain layer use case

// TODO: Define data layer repository
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

// Entity
class Task {
  final String title;
  Task(this.title);
}

// Domain Layer
abstract class TaskRepository {
  Future<List<Task>> fetchTasks();
}

class GetTasksUseCase {
  final TaskRepository repository;
  GetTasksUseCase(this.repository);

  Future<List<Task>> call() async {
    return await repository.fetchTasks();
  }
}

// Data Layer
class DataTaskRepository implements TaskRepository {
  @override
  Future<List<Task>> fetchTasks() async {
    // Simulate network delay
    await Future.delayed(const Duration(milliseconds: 500));
    return [
      Task('Buy groceries'),
      Task('Walk the dog'),
      Task('Read a book'),
    ];
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: TasksScreen(),
    );
  }
}

class TasksScreen extends StatefulWidget {
  const TasksScreen({super.key});

  @override
  State<TasksScreen> createState() => _TasksScreenState();
}

class _TasksScreenState extends State<TasksScreen> {
  late final GetTasksUseCase _getTasksUseCase;
  List<Task> _tasks = [];
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    _getTasksUseCase = GetTasksUseCase(DataTaskRepository());
    _loadTasks();
  }

  Future<void> _loadTasks() async {
    setState(() {
      _loading = true;
    });
    final tasks = await _getTasksUseCase();
    setState(() {
      _tasks = tasks;
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Clean Architecture')),
      body: Column(
        children: [
          const Padding(
            padding: EdgeInsets.all(8.0),
            child: Text('Tasks:', style: TextStyle(fontSize: 20)),
          ),
          Expanded(
            child: _loading
                ? const Center(child: CircularProgressIndicator())
                : ListView.builder(
                    itemCount: _tasks.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(_tasks[index].title),
                      );
                    },
                  ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ElevatedButton(
              onPressed: _loadTasks,
              child: const Text('Refresh'),
            ),
          ),
        ],
      ),
    );
  }
}

This Flutter app demonstrates Clean Architecture by separating the code into three layers:

  • Data Layer: DataTaskRepository implements TaskRepository and returns a fixed list of tasks simulating a data source.
  • Domain Layer: Defines the Task entity, the TaskRepository interface, and the GetTasksUseCase which fetches tasks from the repository.
  • Presentation Layer: The TasksScreen widget uses the use case to load tasks and display them in a list. It shows a loading spinner while fetching and a Refresh button to reload tasks.

This clear separation helps keep business logic independent from UI and data sources, making the app easier to maintain and test.

Final Result
Completed Screen
-------------------------
| Clean Architecture    |
|-----------------------|
| Tasks:                |
| 1. Buy groceries      |
| 2. Walk the dog       |
| 3. Read a book        |
|                       |
| [Refresh]             |
-------------------------
When the screen loads, tasks appear after a short loading spinner.
Tapping the Refresh button reloads the tasks and shows the spinner briefly.
Stretch Goal
Add a dark mode toggle button in the app bar that switches the app theme between light and dark.
💡 Hint
Use a StatefulWidget to hold the theme mode state and wrap MaterialApp with a ThemeMode property. Add an IconButton in the AppBar to toggle the mode.

Practice

(1/5)
1. Which layer in Clean Architecture is responsible for handling the user interface in a Flutter app?
easy
A. Domain layer
B. Presentation layer
C. Data layer
D. Network layer

Solution

  1. Step 1: Understand the role of each layer

    The Presentation layer manages UI elements and user interactions.
  2. Step 2: Match the layer to UI responsibility

    Since UI is about what the user sees and interacts with, it belongs to the Presentation layer.
  3. Final Answer:

    Presentation layer -> Option B
  4. Quick Check:

    UI = Presentation layer [OK]
Hint: UI always belongs to Presentation layer [OK]
Common Mistakes:
  • Confusing Domain layer with UI
  • Thinking Data layer handles UI
  • Assuming Network layer is part of UI
2. Which of the following is the correct way to describe the Domain layer in Clean Architecture?
easy
A. It contains business logic and rules.
B. It handles data storage and retrieval.
C. It manages UI components and widgets.
D. It deals with network requests and APIs.

Solution

  1. Step 1: Identify the Domain layer's responsibility

    The Domain layer contains the core business logic and rules of the app.
  2. Step 2: Eliminate incorrect options

    Data storage is Data layer, UI is Presentation, network is part of Data or external services.
  3. Final Answer:

    It contains business logic and rules. -> Option A
  4. Quick Check:

    Domain = Business logic [OK]
Hint: Domain layer = business rules and logic [OK]
Common Mistakes:
  • Mixing Domain with Data layer
  • Thinking Domain manages UI
  • Confusing network tasks with Domain
3. Given the following Flutter app structure, which layer should contain the repository interface?
lib/
  presentation/
  domain/
    repositories/
  data/
    repositories_impl/
medium
A. Presentation layer
B. Data layer
C. Domain layer
D. UI layer

Solution

  1. Step 1: Understand repository interface placement

    Repository interfaces define contracts for data access and belong in the Domain layer.
  2. Step 2: Check the folder structure

    Domain layer has 'repositories' folder for interfaces; Data layer has 'repositories_impl' for implementations.
  3. Final Answer:

    Domain layer -> Option C
  4. Quick Check:

    Repository interface = Domain layer [OK]
Hint: Interfaces go in Domain, implementations in Data [OK]
Common Mistakes:
  • Putting interfaces in Data layer
  • Confusing UI layer with Presentation
  • Ignoring folder naming conventions
4. In a Flutter app using Clean Architecture, a developer places API call code directly inside a widget class. What is the main problem with this approach?
medium
A. It violates the separation of concerns by mixing UI and data logic.
B. It improves app performance by reducing layers.
C. It follows Clean Architecture best practices.
D. It makes the Domain layer responsible for UI.

Solution

  1. Step 1: Identify the problem with API calls in UI

    API calls belong in the Data layer, not in UI widgets.
  2. Step 2: Understand separation of concerns

    Mixing UI and data logic breaks Clean Architecture principles, making code harder to maintain.
  3. Final Answer:

    It violates the separation of concerns by mixing UI and data logic. -> Option A
  4. Quick Check:

    UI should not contain data logic [OK]
Hint: Keep API calls out of UI widgets [OK]
Common Mistakes:
  • Thinking mixing layers improves performance
  • Assuming Domain layer handles UI
  • Believing this is best practice
5. You want to add a new feature to your Flutter app that fetches user profiles from a remote server. According to Clean Architecture, where should you place the code that converts JSON data into Dart objects?
hard
A. In the Presentation layer inside widgets
B. In the Domain layer as part of business logic
C. Directly inside the main.dart file
D. In the Data layer within model or mapper classes

Solution

  1. Step 1: Identify JSON conversion responsibility

    Converting JSON to Dart objects is a data transformation task, part of data handling.
  2. Step 2: Match task to Clean Architecture layer

    Data layer handles data sources and transformations, so JSON parsing belongs here.
  3. Step 3: Exclude other layers

    Presentation is UI only, Domain is business rules, main.dart is app entry point, not for data parsing.
  4. Final Answer:

    In the Data layer within model or mapper classes -> Option D
  5. Quick Check:

    JSON parsing = Data layer [OK]
Hint: Put JSON parsing in Data layer models/mappers [OK]
Common Mistakes:
  • Parsing JSON in UI widgets
  • Putting parsing in Domain layer logic
  • Adding parsing code in main.dart
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard