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

In the previous chapter, we looked at how to apply the DDD Clean Architecture to our simple social feed flutter application. We implemented the Presentation layer and the Domain layer. In this chapter, we will be implementing the Infrastructure layer and the Application layer.

Source code on Github

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

The Infrastructure Layer

After implementing the Domain layer, the next step is to implement the Infrastructure layer. The Infrastructure layer is responsible for providing the implementation of the interfaces defined in the Domain layer.

In our case, we have six methods in the PostsInterface located in the Domain layer. We will be implementing these interfaces here, in the Infrastructure layer.

Infrastructure Layer

In the image above we have the infrastructure folder which contains the core folder which contains init_posts.dart used to generate initial posts for the app. It also contains the posts folder. The posts folder contains the posts_repository.dart file which contains the implementations of the interfaces defined in the Domain layer.

Here is one example of one of the methods defined in the PostsInterface in the Domain layer.

(as: PostsInterface)
class PostsRepository implements PostsInterface {
  
  Future<Either<Failure, Unit>> likePostPressed({required int postId}) async {
    try {
      var dbBox = await Hive.openBox<PostsResponse>('postResponse');

      if (dbBox.isEmpty) {
        return left(const Failure.serverError());
      }

      // Get the posts from dbBox and like/unlike the post where post.id is equal to the postId passed in
      var posts = dbBox.get('posts')!.posts;
      var post = posts.firstWhere((post) => post.id == postId);

      // if the post is already disliked, then remove the dislike
      if (post.disliked) {
        post.disliked = false;
        post.dislikes -= 1;
      }

      // then like/unlike the post
      post.liked = !post.liked;
      post.likes = post.liked ? post.likes + 1 : post.likes - 1;

      // Update the posts in the dbBox
      dbBox.put('posts', PostsResponse(posts: posts));

      return right(unit);
    } on SocketException catch (_) {
      return left(const Failure.noInternet());
    }
  }
}

In the above code block, we are implementing the likePostPressed method defined in the PostsInterface in the Domain layer. We are using the Hive package to store the posts in the local database. We are also using the Dartz package to handle errors and exceptions, and the Injectable package to inject the PostsRepository class as a singleton (more on dependency injection coming up later).

The PostsRepository class would be required to implement all other methods defined in the PostsInterface in the Domain layer.

The PostsRepository class would be injected into the PostsBloc class in the Application layer.

In summary, the Infrastructure layer is responsible for providing the implementation of the interfaces defined in the Domain layer.

The Application Layer

The Application layer is responsible for handling the business logic of the application. It is also responsible for handling the state of the application. In our case, we will be using the flutter_bloc package to handle the state of the application.

Application Layer

In the image above, we have the application folder which contains the posts folder which represents the posts feature of the application. The posts folder contains the posts_bloc.dart, posts_event.dart and posts_state.dart files. The posts_bloc.dart file contains the PostsBloc class which is responsible for handling the state of the application. The posts_event.dart file contains the PostsEvent class which represents the events that can occur in the application. The posts_state.dart file contains the PostsState class which represents the state posts feature within the application. It also contains a posts_bloc.freezed.dart file which is generated by the freezed package.

Let's start with the PostsEvent class.

part of 'posts_bloc.dart';


abstract class PostsEvent {}

class GetPosts extends PostsEvent {}

class GenerateInitPosts extends PostsEvent {}

class LikePostPressed extends PostsEvent {
  final int postId;

  LikePostPressed({required this.postId});
}

class DislikePostPressed extends PostsEvent {
  final int postId;

  DislikePostPressed({required this.postId});
}

class BookmarkPostPressed extends PostsEvent {
  final int postId;

  BookmarkPostPressed({required this.postId});
}

class CreatePost extends PostsEvent {
  final String content;
  final File? photo;

  CreatePost({required this.content, this.photo});
}

In the above code block, we have the PostsEvent class which represents the events that can occur in the application. We have the GetPosts event which is used to get the posts from the local database. We have the GenerateInitPosts event which is used to generate initial posts for the application and the class is immutable, which means that it cannot be changed after it has been created. The subsequent events mimick the methods defined in the PostsInterface in the Domain layer as these are actions that can be performed on the posts.

Next, we have the PostsState class.

part of 'posts_bloc.dart';


class PostsState with _$PostsState {
  const factory PostsState({
    required bool isLoading,
    required Option<Either<Failure, PostsResponse>> failureOrResponseOption,
    required Option<Either<Failure, Unit>> failureOrUnitOption,
  }) = _PostsState;

  factory PostsState.initial() => PostsState(
        isLoading: false,
        failureOrResponseOption: none(),
        failureOrUnitOption: none(),
      );
}

In the above code block, we have the PostsState class which represents the state of the application. We are using the freezed package to generate the PostsState class. The PostsState class has three properties. The isLoading property is used to indicate whether the application is loading or not. The failureOrResponseOption property is used to indicate whether there is an error or a response option. The failureOrUnitOption property is used to indicate whether there is an error or a void (when the result is a success, but does not return a response) option.

We also have a PostsState.initial() method which is used to initialize the state of the application and all the properties are set to their default values.

Next, we have the PostsBloc class with just one example of the methods defined in the class.

part 'posts_bloc.freezed.dart';
part 'posts_event.dart';
part 'posts_state.dart';


class PostsBloc extends Bloc<PostsEvent, PostsState> {
  final PostsInterface postsInterface;

  PostsBloc(this.postsInterface) : super(PostsState.initial()) {
    on<GetPosts>((event, emit) async {
      emit(
        state.copyWith(
          isLoading: true,
          failureOrResponseOption: none(),
        ),
      );

      final result = await postsInterface.getPosts();

      result.fold(
        (failure) => emit(
          state.copyWith(
            isLoading: false,
            failureOrResponseOption: some(left(failure)),
          ),
        ),
        (postsResponse) => emit(
          state.copyWith(
            isLoading: false,
            failureOrResponseOption: some(right(postsResponse)),
          ),
        ),
      );
    });
  }
}

In the above code block, we have the PostsBloc class which is responsible for handling the state of the posts feature. We are using the flutter_bloc package to handle the state of the application. We are also using the freezed package to generate the PostsBloc class. We are using the injectable package to inject the PostsInterface class into the PostsBloc class as a singleton. We are also using the Dartz package to handle errors and exceptions.

The PostsBloc class would be injected into the PostsScreen class in the Presentation layer using flutter_bloc and get_it.

The on method is used to listen for events and perform actions based on the events. The emit method is used to emit a new state to the application. The state.copyWith method is used to copy the current state of the application and update the properties of the state. The some method is used to indicate that the value is present. The none method is used to indicate that the value is absent. The left method is used to indicate that the value is an error. The right method is used to indicate that the value is a success.

The PostsBloc class would be required to implement all other methods defined in the PostsEvent.

In summary, the PostsBloc class is responsible for handling the state of the application. It listens for events and performs actions based on the events. It also emits a new state to 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