Skip to content

Application Layer

The application layer contains the state management logic of the feature. The application layer is responsible for managing the state of the feature and handling the business logic.

Getting started

Create a new folder named application inside the feature folder.

Feature Cubit

Use the Bloc extension in VSCode to create a new cubit:

  1. Right-click on the application folder.

  2. Select Bloc: New Cubit.

  3. Enter the name of the cubit. For example, scratch_note_cubit.

Writing the cubit

We'll start with the state class first and then write the cubit class. Extend the scratch_note_cubit.dart file with the following code:

dart
part of 'scratch_note_cubit.dart';

@freezed
class ScratchNoteState with _$ScratchNoteState {
  const factory ScratchNoteState({
    required List<ScratchNote> notes, 
    required bool isLoading, 
    required bool isSaving,
    required bool isDeleting,
    required Failure failure,
  }) = _ScratchNoteState;

  factory ScratchNoteState.initial() => _ScratchNoteState(
        notes: [],
        isLoading: false,
        isSaving: false,
        isDeleting: false,
        failure: Failure.none(),
      );
}

The state class contains the following properties:

  • notes: A list of ScratchNote objects.
  • isLoading: A boolean flag to indicate loading state.
  • isSaving: A boolean flag to indicate saving state.
  • isDeleting: A boolean flag to indicate deleting state.
  • failure: A Failure object to handle errors.

It also contains the following factory constructor:

  • initial: A factory constructor to create the initial state. It initializes the state with empty notes, and sets all flags to false. This will be the default state of the cubit when user opens the feature.

Next, extend the scratch_note_cubit.dart file with the following code:

dart
import 'dart:async';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fpdart/fpdart.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:techshoi_app/features/core/domain/entities/failure.dart';
import 'package:techshoi_app/features/scratch_notes/domain/i_scratch_note_repository.dart';
import 'package:techshoi_app/features/scratch_notes/domain/scratch_note.dart';

part 'scratch_note_state.dart';
part 'scratch_note_cubit.freezed.dart';

class ScratchNoteCubit extends Cubit<ScratchNoteState> {
  ScratchNoteCubit(this._repo) : super(ScratchNoteState.initial());

  final IScratchNoteRepository _repo;

  StreamSubscription<Either<Failure, List<ScratchNote>>>? _notesSubscription;

  Future<void> createScratchNote({required String title}) async {
    emit(state.copyWith(isSaving: true));
    final result = await _repo.addScratchNote(title);

    result.fold(
      (failure) => emit(
        state.copyWith(
          isSaving: false,
          failure: failure,
        ),
      ),
      (scratchNote) => emit(
        state.copyWith(
          isSaving: false,
          failure: Failure.none(),
        ),
      ),
    );
  }

  Future<void> updateScratchNote(ScratchNote updatedNote) async {
    emit(state.copyWith(isSaving: true));
    final result = await _repo.updateScratchNote(updatedNote);

    result.fold(
      (failure) => emit(
        state.copyWith(
          isSaving: false,
          failure: failure,
        ),
      ),
      (scratchNote) => emit(
        state.copyWith(
          isSaving: false,
          failure: Failure.none(),
        ),
      ),
    );
  }

  Future<void> deleteScratchNote(String noteId) async {
    emit(state.copyWith(isDeleting: true));
    final result = await _repo.deleteScratchNote(noteId);

    result.fold(
      (failure) => emit(
        state.copyWith(
          isDeleting: false,
          failure: failure,
        ),
      ),
      (scratchNote) => emit(
        state.copyWith(
          isDeleting: false,
          failure: Failure.none(),
        ),
      ),
    );
  }

  Future<void> getScratchNotes() async {
    emit(state.copyWith(isLoading: true));
    final result = await _repo.getScratchNotes();

    result.fold(
      (failure) => emit(
        state.copyWith(
          isLoading: false,
          failure: failure,
        ),
      ),
      (scratchNotes) => emit(
        state.copyWith(
          isLoading: false,
          failure: Failure.none(),
          notes: scratchNotes,
        ),
      ),
    );
  }

  Future<void> listenForChanges() async {
    emit(state.copyWith(isLoading: true));
    _notesSubscription = _repo.listenToScratchNotes().listen(
      (failureOrNotes) {
        failureOrNotes.fold(
          (failure) => emit(
            state.copyWith(
              isLoading: false,
              failure: failure,
            ),
          ),
          (scratchNotes) => emit(
            state.copyWith(
              isLoading: false,
              failure: Failure.none(),
              notes: scratchNotes,
            ),
          ),
        );
      },
    );
  }

  void flush() {
    emit(ScratchNoteState.initial());
  }

  @override
  Future<void> close() {
    _notesSubscription?.cancel();
    return super.close();
  }
}

The ScratchNoteCubit class extends Cubit<ScratchNoteState>. It contains the following methods:

  • createScratchNote: Adds a new scratch note.
  • updateScratchNote: Updates an existing scratch note.
  • deleteScratchNote: Deletes a scratch note.
  • getScratchNotes: Retrieves a list of scratch notes.
  • listenForChanges: Listens to changes in the scratch notes.
  • flush: Resets the state to the initial state.
  • close: Cancels the subscription when the cubit is closed.
  • _notesSubscription: A private subscription to listen for changes in the scratch notes.
  • _repo: An instance of the IScratchNoteRepository interface.

As you can see, the cubit takes an instance of the IScratchNoteRepository interface in the constructor. This interface only contains the abstract methods for interacting with scratch notes. The actual implementation of these methods will be in the infrastructure layer.

Next, we'll move on to the infrastructure layer to implement the repository interface.