Learning TDD with Clean Architecture for Flutter Part IV
Tags: flutter, tdd, cleanarchitecture, dart
Published: 2023-01-04 15:51:00



Part **I [here](https://dev.to/codingmountain/learning-tdd-with-clean-architecture-for-flutter-part-i-46nk)**
Part **II [here](https://dev.to/codingmountain/learning-tdd-with-clean-architecture-for-flutter-domain-layer-part-ii-1pi5)**
Part **III [here](https://dev.to/codingmountain/learning-tdd-with-clean-architecture-for-flutter-data-layer-part-iii-1fff)**

This is the final part of the series. Let’s get write into it.

![Presentation Layer](https://cdn-images-1.medium.com/max/2000/1*z2bjC_Y9hBMzFeeCgHzSPg.png)

## Presentation Layer

It is the UI part. Code is written to give life to the app. It is the part where users interact. It consists of pages, widgets, and state management (bloc, provider, etc).

![](https://cdn-images-1.medium.com/max/2000/1*XcRHsGm2nD9i6mI_kEVB7g.png)

### Pages

It consists of the screens to be displayed.

### Widgets

It consists of the widgets that are being used.

### Bloc

Let’s create a bloc for the movie list state management.

Eg. Movie Bloc *lib/features/movie/presentation/bloc/movie_bloc.dart*

    import 'package:bloc/bloc.dart';
    import 'package:equatable/equatable.dart';
    import 'package:movie_show/core/usecases/usecase.dart';
    import 'package:movie_show/features/movie/domain/entities/movie_entity.dart';
    import 'package:movie_show/features/movie/domain/usecases/get_movie_list_usecase.dart';
    
    part 'movie_event.dart';
    part 'movie_state.dart';
    
    class MovieBloc extends Bloc<MovieEvent, MovieState> {
      final GetMovieListUsecase getMovieListUsecase;
    
      MovieBloc({required this.getMovieListUsecase}) : super(MovieInitial()) {
        on<MovieEvent>((event, emit) async => await _onGetMovies(emit));
      }
    
      Future<void> _onGetMovies(Emitter<MovieState> emit) async {
        emit(MovieLoading());
        final failureOrMovieList = await getMovieListUsecase.call(NoParams());
        emit(failureOrMovieList.fold(
            (failure) => const Error(message: "Server Failure"),
            (movieList) => MovieLoaded(movieList: movieList)));
      }
    }

Eg. Movie Bloc State *lib/features/movie/presentation/bloc/movie_state.dart*

    part of 'movie_bloc.dart';
    
    abstract class MovieState extends Equatable {
      const MovieState();
    
      @override
      List<Object> get props => [];
    }
    
    class MovieInitial extends MovieState {}
    
    class MovieLoading extends MovieState {}
    
    class MovieLoaded extends MovieState {
      final List<MovieEntity> movieList;
      const MovieLoaded({
        required this.movieList,
      });
      @override
      List<Object> get props => [movieList];
    }
    
    class Error extends MovieState {
      final String message;
      const Error({
        required this.message,
      });
      @override
      List<Object> get props => [message];
    }

Eg. Movie Bloc Event *lib/features/movie/presentation/bloc/movie_event.dart*

    part of 'movie_bloc.dart';
    
    abstract class MovieEvent extends Equatable {
      const MovieEvent();
    
      @override
      List<Object> get props => [];
    }
    
    class GetMovies extends MovieEvent {}

After creating the bloc we will test it.

Eg. Movie Bloc test: *test/features/movie/presentation/bloc/movie_bloc_test.dart*

    import 'package:dartz/dartz.dart';
    import 'package:flutter_test/flutter_test.dart';
    import 'package:mocktail/mocktail.dart';
    import 'package:movie_show/core/error/failure.dart';
    import 'package:movie_show/core/usecases/usecase.dart';
    import 'package:movie_show/features/movie/domain/entities/movie_entity.dart';
    import 'package:movie_show/features/movie/domain/usecases/get_movie_list_usecase.dart';
    import 'package:movie_show/features/movie/presentation/bloc/movie_bloc.dart';
    
    class MockGetMovieListUsecase extends Mock implements GetMovieListUsecase {}
    
    void main() {
      late MovieBloc movieBloc;
      late MockGetMovieListUsecase mockGetMovieListUsecase;
      final tMovieList = [
        const MovieEntity(
          movieId: 'movieId',
          title: 'title',
          thumbnail: 'thumbnail',
          movieUrl: 'movieUrl',
          unlocked: true,
        ),
        const MovieEntity(
          movieId: 'moviesIds',
          title: 'titles',
          thumbnail: 'thumbnails',
          movieUrl: 'movieUrls',
          unlocked: false,
        )
      ];
      setUp(() {
        mockGetMovieListUsecase = MockGetMovieListUsecase();
        movieBloc = MovieBloc(getMovieListUsecase: mockGetMovieListUsecase);
      });
      group('Bloc Test', () {
        test('initial state should be MovieInitial()', () async {
          //Arrange
          //Act
          //Assert
          expect(movieBloc.state, equals(MovieInitial()));
        });
        test('should get data from usecase', () async {
          //Arrange
          when(
            () => mockGetMovieListUsecase.call(NoParams()),
          ).thenAnswer((_) async => Right(tMovieList));
          //Act
          movieBloc.add(GetMovies());
          await untilCalled(() => mockGetMovieListUsecase.call(NoParams()));
          //Assert
          verify(() => mockGetMovieListUsecase.call(NoParams()));
        });
        test('should emit MovieLoaded when data is obtained succesfully', () async {
          //Arrange
          when(
            () => mockGetMovieListUsecase.call(NoParams()),
          ).thenAnswer((_) async => Right(tMovieList));
    //Assert Later
          final expected = [
            MovieLoading(),
            MovieLoaded(movieList: tMovieList),
          ];
          expectLater(movieBloc.stream, emitsInOrder(expected));
    //Act
          movieBloc.add(GetMovies());
          // print(movieBloc.stream.runtimeType);
        });
        test('should emit Error when getting data fails', () async {
          //Arrange
          when(
            () => mockGetMovieListUsecase.call(NoParams()),
          ).thenAnswer((_) async => Left(ServerFailure()));
    //Assert Later
          final expected = [
            MovieLoading(),
            const Error(message: 'Server Failure'),
          ];
          expectLater(movieBloc.stream, emitsInOrder(expected));
    //Act
          movieBloc.add(GetMovies());
          // print(movieBloc.stream.runtimeType);
        });
      });
    }

First, we test the initial state of the bloc. During the GetMovies() event the bloc should emit two states MovieInitial() and MovieLoaded() or MovieLoading() or Error(). The state of the bloc is obtained as a stream. Here we are using the expectLater() as the bloc is executing later. So we should keep on listening for the stream of state. What about the page and widget? Shouldn’t we test it? Here we are doing the unit test, in order to test those we need to do a widget test. That’s a topic for another time. Now let’s create the UI i.e. pages and widgets. Wait a minute there arises a problem. The presentation layer only knows about the domain layer. It doesn’t know about the data layer. There seems to be no connection between those. In the presentation, we are only calling the use-case and the use-case only contains the abstract function. The UI has no way to access implementation. With all the work we have done and we can’t connect those dots.

We can solve these issues by using dependency injection. It connects all the things we have been doing so far. So let’s connect the dots using [get_it](https://pub.dev/packages/get_it).

Let’s create a file *injection_container.dart*

Eg . *lib*/*injection_container.dart*

    final sl = GetIt.instance;
    Future<void> init() async {
    //bloc
      // movie bloc
      sl.registerFactory<MovieBloc>(
        () => MovieBloc(
          getMovieListUsecase: sl(),
        ),
      );
    //usecase

    //movie use case
      sl.registerLazySingleton<GetMovieListUsecase>(
          () => GetMovieListUsecase(movieRepository: sl()));

    //repository

    // movie repository
      sl.registerLazySingleton<MovieRepository>(
        () => MovieRepositoryImpl(
          networkInfo: sl(),
          movieListRemoteDataSource: sl(),
          awsDataSource: sl(),
        ),
      );

    //data sources

    sl.registerLazySingleton<MovieListRemoteDataSource>(
        () => MovieListRemoteDataSourceImpl(provideFakeData: sl()),
      )

    //core

    //network info --> internet connected or not
      sl.registerLazySingleton<NetworkInfo>(
        () => NetworkInfoImpl(sl()),
      );
    //fake Data
      sl.registerLazySingleton<ProvideFakeData>(() => ProvideFakeData());

    //external
      final sharedPreferences = await SharedPreferences.getInstance();
      sl.registerLazySingleton<SharedPreferences>(
        () => sharedPreferences,
      );

    sl.registerLazySingleton<Dio>(
        () => Dio(),
      );

    sl.registerLazySingleton<Connectivity>(
        () => Connectivity(),
      );

    }

The **get_it** package supports creating **singletons** and instance **factories**. Here, we’re going to register everything as required. We will register most of them as a singleton so that our app gets only one instance of the class per lifecycle except bloc which might need multiple instances.

To register, instantiate the class as usual and pass in sl() into every constructor parameter and define the constructor parameter in the same way and repeat until no constructor parameter is left.

Here we provide the implementation of the abstract classes should they appear in our code. Dependency injection was the missing link between the production and test codes, where we sort of injected the dependencies manually with mocked classes. Now that we’ve implemented the **service locator**, nothing can stop us from **writing Flutter widgets** which will utilize every bit of code we’ve written until now by showing a fully functional UI to the user.

Now you can initialize it in the *main.dart* file and start building your UI
>  Make sure to use the service locator that we created when accessing the bloc. To access movie bloc instance, you can use sl<MovieBloc>.

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'injection_container.dart' as di;

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await di.init();
      runApp(
        const MyApp(),
      );
    }
>  I won’t be doing the UI part. For reference go [here](https://resocoder.com/2019/10/29/flutter-tdd-clean-architecture-course-14-user-interface/) and [here](https://bloclibrary.dev/#/)(bloc)

Happy Coding!!