Skip to content

Infrastructure Layer

The infrastructure layer contains the implementation of the data sources and external dependencies of the feature. The infrastructure layer is responsible for fetching data from external sources, such as APIs or databases, and providing it to the application layer.

Getting started

Create a new folder named infrastructure inside the feature folder.

Write the repository implementation

We'll be using firestore database to store the scratch notes. Create a new file named scratch_note_repository_impl.dart inside the infrastructure folder. This file will contain the implementation of the IScratchNoteRepository interface.

dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fpdart/fpdart.dart';
import 'package:techshoi_app/features/core/domain/entities/failure.dart';
import 'package:techshoi_app/features/core/infrastructure/firebase_helpers.dart';
import 'package:techshoi_app/features/core/infrastructure/logger/techshoi_logger.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';
import 'package:uuid/uuid.dart';

class FirebaseScratchNoteRepository implements IScratchNoteRepository {
  FirebaseScratchNoteRepository(this._firestore);

  final FirebaseFirestore _firestore;
  final scratchNotesCollectionName = 'scratch_notes';

  @override
  Future<Either<Failure, Unit>> addScratchNote(String title) async {
    try {
      final userDoc = await _firestore.userDocument();
      final notesCollection = userDoc.collection(scratchNotesCollectionName);

      final id = const Uuid().v4();

      final scratchNote = ScratchNote(
        id: id,
        userId: userDoc.id,
        title: title,
        createdAt: DateTime.now(),
      );

      await notesCollection.doc(id).set(scratchNote.toJson());
      return right(unit);
    } catch (e) {
      TechshoiLogger.instance.e('Failed to add scratch note. Error: $e');
      return left(const Failure(message: 'Failed to add scratch note'));
    }
  }

  @override
  Future<Either<Failure, Unit>> deleteScratchNote(String noteId) async {
    try {
      final userDoc = await _firestore.userDocument();
      final notesCollection = userDoc.collection(scratchNotesCollectionName);

      await notesCollection.doc(noteId).delete();
      return right(unit);
    } catch (e) {
      TechshoiLogger.instance.e('Failed to delete scratch note. Error: $e');
      return left(const Failure(message: 'Failed to delete scratch note'));
    }
  }

  @override
  Future<Either<Failure, List<ScratchNote>>> getScratchNotes() async {
    try {
      final userDoc = await _firestore.userDocument();
      final notesCollection = userDoc.collection(scratchNotesCollectionName);

      final notesSnapshot = await notesCollection.get();
      final notes = notesSnapshot.docs
          .map((doc) => ScratchNote.fromJson(doc.data()))
          .toList();

      return right(notes);
    } catch (e) {
      TechshoiLogger.instance.e('Failed to get scratch notes. Error: $e');
      return left(const Failure(message: 'Failed to get scratch notes'));
    }
  }

  @override
  Stream<Either<Failure, List<ScratchNote>>> listenToScratchNotes() async* {
    final userDoc = await _firestore.userDocument();
    final notesCollection = userDoc.collection(scratchNotesCollectionName);

    yield* notesCollection.snapshots().map((snapshot) {
      final notes =
          snapshot.docs.map((doc) => ScratchNote.fromJson(doc.data())).toList();

      return right(notes);
    });
  }

  @override
  Future<Either<Failure, Unit>> updateScratchNote(ScratchNote note) async {
    try {
      final userDoc = await _firestore.userDocument();
      final notesCollection = userDoc.collection(scratchNotesCollectionName);

      await notesCollection.doc(note.id).update(note.toJson());
      return right(unit);
    } catch (e) {
      TechshoiLogger.instance.e('Failed to update scratch note. Error: $e');
      return left(const Failure(message: 'Failed to update scratch note'));
    }
  }
}

In this implementation, we are using the cloud_firestore package to interact with the Firestore database. We define a class FirebaseScratchNoteRepository that implements the IScratchNoteRepository interface. The methods in this class interact with the Firestore database to perform CRUD operations on scratch notes. We also use the fpdart package to return Either types to handle errors.

We're also using the .userDocument() extension method from the firebase_helpers.dart file to get the user document from Firestore. This method is defined in the core module.

Next, create a file named configure_notes_dependencies.dart inside the infrastructure folder. This file will contain the setup for the infrastructure layer.

dart
import 'package:techshoi_app/configure_dependencies.dart';
import 'package:techshoi_app/features/scratch_notes/application/scratch_note_cubit.dart';
import 'package:techshoi_app/features/scratch_notes/domain/i_scratch_note_repository.dart';
import 'package:techshoi_app/features/scratch_notes/infrastructure/firebase_scratch_note_repository.dart';

Future<void> configureNotesDependencies() async {
  serviceLocator
    ..registerLazySingleton<IScratchNoteRepository>(
      () => FirebaseScratchNoteRepository(
        serviceLocator(),
      ),
    )
    ..registerFactory<ScratchNoteCubit>(
      () => ScratchNoteCubit(
        serviceLocator(),
      ),
    );
}

Then add this line to the configure_dependencies.dart file to include the infrastructure layer setup:

dart
import 'package:techshoi_app/features/scratch_notes/infrastructure/configure_notes_dependencies.dart';

Future<void> configureDependencies() async {
  ...

  await configureNotesDependencies(); // Add this line
}

This will ensure that the infrastructure layer is set up when the app starts.