0
0
Fluttermobile~20 mins

Why clean architecture scales codebases in Flutter - Build It to Prove It

Choose your learning style9 modes available
Build: Clean Architecture Demo
This screen demonstrates a simple counter app built using clean architecture principles. It separates UI, business logic, and data layers to show how code can stay organized and scalable.
Target UI
┌─────────────────────────────┐
│       Clean Architecture     │
├─────────────────────────────┤
│                             │
│        Counter: 0            │
│                             │
│   [ Increment ] [ Reset ]   │
│                             │
└─────────────────────────────┘
Display a counter value starting at 0
Increment button increases the counter by 1
Reset button sets the counter back to 0
Separate UI, business logic, and data layers using clean architecture
Use a simple repository class to manage counter state
UI listens to changes and updates accordingly
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: CounterScreen(),
    );
  }
}

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

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  // TODO: Add repository and business logic here

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Clean Architecture Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Counter:'),
            // TODO: Display counter value
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    // TODO: Increment counter
                  },
                  child: const Text('Increment'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    // TODO: Reset counter
                  },
                  child: const Text('Reset'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
Task 1
Task 2
Task 3
Task 4
Solution
Flutter
import 'package:flutter/material.dart';

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

// Data Layer
class CounterRepository {
  int _counter = 0;

  int getCounter() => _counter;

  void increment() {
    _counter++;
  }

  void reset() {
    _counter = 0;
  }
}

// Domain Layer
class CounterUseCase {
  final CounterRepository repository;

  CounterUseCase(this.repository);

  int getCounter() => repository.getCounter();

  void incrementCounter() {
    repository.increment();
  }

  void resetCounter() {
    repository.reset();
  }
}

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

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

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

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  late final CounterRepository _repository;
  late final CounterUseCase _useCase;

  @override
  void initState() {
    super.initState();
    _repository = CounterRepository();
    _useCase = CounterUseCase(_repository);
  }

  void _increment() {
    setState(() {
      _useCase.incrementCounter();
    });
  }

  void _reset() {
    setState(() {
      _useCase.resetCounter();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Clean Architecture Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Counter:'),
            Text(
              '${_useCase.getCounter()}',
              style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: _increment,
                  child: const Text('Increment'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: _reset,
                  child: const Text('Reset'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

This example shows clean architecture by splitting the app into three layers:

  • Data Layer: CounterRepository holds the counter state and methods to change it.
  • Domain Layer: CounterUseCase contains business logic and uses the repository to get or update data.
  • Presentation Layer: The Flutter UI listens to user taps and calls use case methods. It updates the screen when the counter changes.

This separation helps keep code organized and easy to maintain as the app grows. Each layer has a clear job, so changes in one place don’t break others.

Final Result
Completed Screen
┌─────────────────────────────┐
│       Clean Architecture     │
├─────────────────────────────┤
│                             │
│        Counter: 0            │
│                             │
│   [ Increment ] [ Reset ]   │
│                             │
└─────────────────────────────┘
Tapping 'Increment' increases the counter number by 1 and updates the display.
Tapping 'Reset' sets the counter back to 0 and updates the display.
Stretch Goal
Add a feature to save the counter value persistently so it remains after app restart.
💡 Hint
Use Flutter's shared_preferences package to store and load the counter value in the repository.