Find The Latest Tech Insights, News and Updates to Read

Flutter Clean Architecture With Code Snippets

Written by Kshitiz Sharma | Jan 4, 2024 8:05:38 AM

Introduction

Flutter is a popular framework for building cross-platform mobile applications. When developing Flutter apps, adopting a clean architecture can greatly enhance code maintainability, scalability, and testability. In this article, we will explore the principles of Clean Architecture and demonstrate how to implement it in a Flutter project with code snippets.

What is Clean Architecture?

Clean Architecture is a software design philosophy that emphasizes the separation of concerns and independence of frameworks. It was introduced by Robert C. Martin, also known as Uncle Bob. The key idea is to divide the codebase into layers, each with a specific responsibility, and arrange these layers in a way that dependencies flow inward. The common layers in Clean Architecture include:

  1. Entities: Represent the core business logic and data structures.
  2. Use Cases (Interactors): Contain application-specific business rules and orchestrate the flow of data between entities.
  3. Interface Adapters: Convert data between the use cases and the external systems, such as UI, databases, or APIs.
  4. Frameworks and Drivers: Include external frameworks, libraries, and tools. These are the outermost layer and should be kept as thin as possible.

Setting Up a Flutter Project

Let's start by creating a new Flutter project using the following commands:

flutter create flutter_clean_architecture

cd flutter_clean_architecture

 

Now, let's create the basic directory structure for our Clean Architecture:

lib/

|-- core/

|   |-- entities/

|   |-- usecases/

|-- features/

|   |-- feature1/

|       |-- data/

|       |-- domain/

|       |-- presentation/

|-- main.dart

 

Implementing Clean Architecture in Flutter

  1. Entities: Entities represent the core business logic and are independent of any framework. Create a file `user_entity.dart` in the `core/entities` directory:

// core/entities/user_entity.dart


class UserEntity {

  final String id;

  final String name;

  final String email;


  UserEntity({required this.id, required this.name, required this.email});

}

 

  1. Use Cases: Use cases contain application-specific business rules. Create a file `get_user_usecase.dart` in the `core/usecases` directory:

// core/usecases/get_user_usecase.dart

import '../entities/user_entity.dart';


class GetUserUseCase {

  // Inject any dependencies here


  Future<UserEntity> execute(String userId) async {

    // Business logic to retrieve a user by ID

    // Example: return UserRepository.getUserById(userId);

    throw UnimplementedError();

  }

}

 

  1. Interface Adapters: 

 3.1 Data Layer: Create a file `user_repository.dart` in the `features/feature1/data` directory:

// features/feature1/data/user_repository.dart

import '../../../core/entities/user_entity.dart';


abstract class UserRepository {

  Future<UserEntity> getUserById(String userId);

}


class UserRepositoryImpl implements UserRepository {

  @override

  Future<UserEntity> getUserById(String userId) async {

    // Implementation to fetch user data from API or database

    throw UnimplementedError();

  }

}

 

3.2 Domain Layer: Create a file user_interactor.dart in the features/feature1/domain directory:

// features/feature1/domain/user_interactor.dart

import '../../../core/entities/user_entity.dart';

import '../../../core/usecases/get_user_usecase.dart';


class UserInteractor {

  final GetUserUseCase _getUserUseCase;


  UserInteractor(this._getUserUseCase);


  Future<UserEntity> getUserById(String userId) async {

    return _getUserUseCase.execute(userId);

  }

}

 

3.3 Presentation Layer: Create a file user_presenter.dart in the features/feature1/presentation directory:

// features/feature1/presentation/user_presenter.dart

import 'package:flutter_clean_architecture/core/entities/user_entity.dart';

import 'package:flutter_clean_architecture/features/feature1/domain/user_interactor.dart';


class UserPresenter {

  final UserInteractor _userInteractor;


  UserPresenter(this._userInteractor);


  Future<UserEntity> getUserById(String userId) async {

    return _userInteractor.getUserById(userId);

  }

}

 

  1. Frameworks and Drivers: In this example, we'll create a simple Flutter UI to display user information. Open the `main.dart` file:

// main.dart

import 'package:flutter/material.dart';

import 'package:flutter_clean_architecture/features/feature1/presentation/user_presenter.dart';


void main() {

  runApp(MyApp());

}


class MyApp extends StatelessWidget {

  final UserPresenter _userPresenter = UserPresenter(UserInteractor(GetUserUseCase()));


  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      home: Scaffold(

        appBar: AppBar(

          title: Text('Clean Architecture Example'),

        ),

        body: Center(

          child: FutureBuilder(

            future: _userPresenter.getUserById('1'),

            builder: (context, snapshot) {

              if (snapshot.connectionState == ConnectionState.waiting) {

                return CircularProgressIndicator();

              } else if (snapshot.hasError) {

                return Text('Error: ${snapshot.error}');

              } else {

                final user = snapshot.data as UserEntity;

                return Column(

                  mainAxisAlignment: MainAxisAlignment.center,

                  children: [

                    Text('User ID: ${user.id}'),

                    Text('Name: ${user.name}'),

                    Text('Email: ${user.email}'),

                  ],

                );

              }

            },

          ),

        ),

      ),

    );

  }

}

Conclusion

In this article, we've explored the principles of Clean Architecture and implemented it in a Flutter project. This separation of concerns allows for better code organization, maintainability, and testability. As your project grows, the Clean Architecture pattern can help you adapt to changes without major refactoring. Feel free to customize the example to fit the specific needs of your project. Happy coding!

Ready to elevate your Flutter app design? Unlock the full potential of Flutter layouts with our professional Flutter developers.