PART 2 - Building a Simple Social Feed using DDD Clean Architecture

Circle poster

Source code on Github

The source code for Circle is available on Github. You can find it here

In previous chapters, we discussed about the concept of DDD Clean Architecture. In this article, we will be building a simple social feed using DDD and Clean Architecture and walk through necessary steps to achieve modularity and scalabilty.

The Layers

As we discussed in the previous article, the layers of the DDD Clean Architecture are the Presentation, Application, Domain, and Infrastructure layers. The layers can be applied Flutter applications as shown below.

DDD Clean Architecture in Flutter

The image above shows the layers implemented as four different folder under the lib folder.

We will begin with the Presentation layer. The presentation layer is the entry point of the application. It is the layer that is responsible for displaying the UI to the user.

The Presentation Layer

Presentation Layer

The image above shows that the presentation layer is the topmost layer of the application. It is the layer that is responsible for displaying the UI to the user. It is also the layer that is responsible for receiving user input and passing it to the application layer.

In our social feed application, the presentation folder contains three folders: app, core, and home. The core folder contains the widgets and utils shared across the layer. The app folder contains app.dart which is the entry point of the application. The home folder contains home_screen.dart and other folders which represent the different features of the application, for example, posts folder contains the widgets and utils for the posts feature, activity folder contains the widgets and utils for the activity feature and so on.

Our core folder contains the subfolders constants, colors, widgets,and theme. The constants folder contains the constants used across the application like app sizes. The colors folder contains the colors used across the application. The widgets folder contains the widgets used across the entire application. The theme folder contains the theme data used across the application.

In the home folder, we also have a widgets folder, this folder contains the widgets that are shared across the features of the application. For example, the empty_feed.dart widget is used in the posts_screen.dart and activity_screen.dart widgets.

The app.dart file contains the MaterialApp widget which is the entry point of the application. The home property of the MaterialApp widget is set to the HomeScreen widget. The HomeScreen widget is the main screen of the application. It contains our custom BottomNavigationBar as BottomNav widget which is used to navigate between the different features of the application.

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

  
  Widget build(BuildContext context) {
    FlutterNativeSplash.remove();

    return MaterialApp(
        title: 'Circle',
        debugShowCheckedModeBanner: false,
        theme: themeData,
        home: const Scaffold(
          appBar: AppAppBar(),
          body: HomeScreen(),
        ),
    );
  }
}

Source code on Github

The source code for Circle is available on Github. You can find it here

In summary, the presentation layer in our application contains only the folders and files that are responsible for displaying the UI and its logic to the user.

The Domain Layer

Domain Layer

The image above show our application's domain layer. The domain layer is the core of the application. It is the layer that contains the business rules of the application. It is the layer that contains the models, use cases, and interfaces that are used by the application.

In our application, the domain folder contains the core folder which contains utils used across the layer. The posts folder contains the models, use cases, and interfaces for the posts feature.

The post_response.dart file contains the Posts model which is the model for the posts feature. The Post model contains the id, content, time, handle, and comments properties. The Post model is used to represent a post in the application.

(typeId: 1)
class Post extends HiveObject {
  Post({
    required this.id,
    required this.name,
    required this.handle,
    required this.time,
    required this.content,
    this.image,
  });

  (0)
  final int id;
  (1)
  final String name;
  (2)
  final String handle;
  (3)
  final DateTime time;
  (4)
  final String content;
  (5)
  final String? image;


  factory Post.fromJson(Map<String, dynamic> json) => Post(
        id: json["id"],
        name: json["name"],
        handle: json["handle"],
        time: DateTime.parse(json["time"]),
        content: json["content"],
        image: json["image"],
      );

  Map<String, dynamic> toJson() => {
        "id": id,
        "name": name,
        "handle": handle,
        "time": time.toIso8601String(),
        "content": content,
        "image": image,
      };
}

In the above code block, we have a Post model which is used to represent a post in the application. The Post model contains the id, name, handle, time, content, and image properties (more properties can be added to the model). We can also see that the Post model is annotated with the @HiveType annotation. This annotation is used to tell Hive that the Post model is a Hive object. The @HiveField annotation is used to tell Hive that the property is a Hive field.

Hive is a lightweight and blazing fast key-value database written in pure Dart. It is used to store data locally on the device. We will use Hive to store the posts locally on the device.

The post_interface.dart file contains the PostsInterface interface which is the interface for the posts feature. The PostsInterface interface contains the getPosts method which is used to get the posts from the data source and other methods used by the application.

abstract class PostsInterface {
  Future<Either<Failure, PostsResponse>> getPosts();
  Future<Either<Failure, PostsResponse>> generateInitPosts();
  Future<Either<Failure, Unit>> likePostPressed({required int postId});
  Future<Either<Failure, Unit>> dislikePostPressed({required int postId});
  Future<Either<Failure, Unit>> bookmarkPostPressed({required int postId});
  Future<Either<Failure, Unit>> createPost({
    required String content,
    File? photo,
  });
}

In the above code block, we have an abstract class that acts as an interface for the posts feature. We have 6 methods defined in the interface, each of which is responsible for a different task.

Dart doesn't have the interface keyword like some other languages, but it does have abstract classes that can be used to define a contract that other classes can implement. This is done by defining an abstract class that defines the required methods and properties, and then creating a concrete class that implements those methods and properties, as we have done above.

In the Infrastructure layer, we will create a concrete class that implements the PostsInterface interface and see how it is used in the application.

The getPosts method is used to get the posts from the data source. The generateInitPosts method is used to generate the initial posts when the application is first launched. The likePostPressed method is used to like a post. The dislikePostPressed method is used to dislike a post. The bookmarkPostPressed method is used to bookmark a post. The createPost method is used to create a post.

The methods are async and return a Future of type Either<Failure, PostsResponse> or Either<Failure, Unit>. The Either class is a class that is used to represent a value of two possible types. The Failure class is a class that is used to represent a failure. The PostsResponse class is a class that is used to represent a response from the data source. The Unit class is a class that is used to represent a void response.

The Either and Unit classes are defined in the dartz package, which we utilize for functional programming in dart. The Failure class is defined in the core folder of the domain layer because it can be used across multiple features in the layer.

In summary, the domain layer in our application contains only the folders and files that are responsible for the business rules of the application.

Source code on Github

The source code for Circle is available on Github. You can find it here

Previous read

Next read

Mastodon