Skip to content

Add Bloc-like architecture layer for offline sync workflows#6

Merged
Harsh4114 merged 9 commits into
mainfrom
codex/design-bloc-like-architecture-for-sync-engine
Feb 20, 2026
Merged

Add Bloc-like architecture layer for offline sync workflows#6
Harsh4114 merged 9 commits into
mainfrom
codex/design-bloc-like-architecture-for-sync-engine

Conversation

@Harsh4114

Copy link
Copy Markdown
Owner

Motivation

  • Provide a familiar Bloc-like mental model (events → controller → states) for teams building offline-first sync flows to reduce onboarding friction.
  • Surface a generic, testable abstraction for local/cloud adapters and a durable sync-log so developers can implement adapters with minimal boilerplate.
  • Offer a high-level facade that can be swapped alongside the existing SyncManager API without changing existing engine internals.

Description

  • Introduces generic contracts: LocalDataSource<T>, CloudDataSource<T>, and SyncLogStore plus an InMemorySyncLogStore for quick onboarding under lib/src/bloc_like/contracts/.
  • Adds queue/log primitives SyncLog, SyncOperationType, and SyncStatus under lib/src/bloc_like/models/ and event/state types in lib/src/bloc_like/events/ and lib/src/bloc_like/states/.
  • Implements SyncRepository<T> (local-first writes + latest-first replay, status updates) and orchestration layers SyncController<T> and OfflineSyncEngine<T> under lib/src/bloc_like/ to provide event-driven sync flows.
  • Exports the new Bloc-like API from the package entrypoint (lib/offline_sync_engine.dart), updates README.md with a guide and wiring example, and adds test/bloc_like_architecture_test.dart validating an add+sync scenario.

Testing

  • Ran repository checks: git diff --check succeeded.
  • Tried to run dart format and dart test but both could not run in this environment because the dart tool is not available (format/test skipped).
  • Added a focused unit test test/bloc_like_architecture_test.dart which exercises SyncRepositorySyncControllerOfflineSyncEngine (test file committed).

Codex Task

Copilot AI review requested due to automatic review settings February 20, 2026 13:12
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @Harsh4114, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the package by integrating a new Bloc-like architecture designed to streamline the development of offline-first synchronization features. It provides a structured, event-driven approach to managing data consistency between local and cloud sources, offering clear abstractions and a robust framework for handling sync operations. This addition allows for more modular and testable sync flows, complementing the existing SyncManager API without internal modifications.

Highlights

  • New Bloc-like Architecture: Introduced a new Bloc-like architecture layer to provide a familiar event-driven state management model for offline synchronization workflows, aiming to reduce onboarding friction for developers.
  • Generic Contracts and Models: Added generic contracts for LocalDataSource<T>, CloudDataSource<T>, and SyncLogStore, along with core models like SyncLog, SyncOperationType, and SyncStatus to define the interfaces for data handling and sync logging.
  • Orchestration Layer: Implemented SyncRepository<T>, SyncController<T>, and OfflineSyncEngine<T> to manage local-first writes, replay pending logs, handle events, and provide a high-level facade for the new sync mechanism.
  • Documentation and Testing: Updated the README.md with a comprehensive guide and wiring example for the new architecture, and added a dedicated unit test (bloc_like_architecture_test.dart) to validate its core functionality.
Changelog
  • README.md
    • Added a new section detailing the Bloc-Like Architecture, including concepts, contracts, models, and usage examples.
    • Included an architecture diagram to visually represent the flow of the new components.
  • lib/offline_sync_engine.dart
    • Exported all new Bloc-like architecture components to make them accessible from the package entrypoint.
  • lib/src/bloc_like/contracts/cloud_data_source.dart
    • Added CloudDataSource<T> abstract class, defining the contract for cloud data operations.
  • lib/src/bloc_like/contracts/in_memory_sync_log_store.dart
    • Added InMemorySyncLogStore as a concrete, in-memory implementation of SyncLogStore for quick setup and testing.
  • lib/src/bloc_like/contracts/local_data_source.dart
    • Added LocalDataSource<T> abstract class, defining the contract for local data operations.
  • lib/src/bloc_like/contracts/sync_log_store.dart
    • Added SyncLogStore abstract class, defining the contract for managing sync logs.
  • lib/src/bloc_like/controller/sync_controller.dart
    • Added SyncController<T> to process SyncEvents, interact with SyncRepository, and emit SyncStates, forming the core of the Bloc-like logic.
  • lib/src/bloc_like/engine/offline_sync_engine.dart
    • Added OfflineSyncEngine<T> as a high-level facade, simplifying interaction with the SyncController for common sync actions.
  • lib/src/bloc_like/events/sync_event.dart
    • Added SyncEvent<T> and its derived classes (AddData, UpdateData, DeleteData, StartSync, RetryFailed) to represent various sync actions.
  • lib/src/bloc_like/models/sync_log.dart
    • Added SyncOperationType and SyncStatus enums, and the SyncLog class with a copyWith method, for tracking and managing individual sync operations.
  • lib/src/bloc_like/repository/sync_repository.dart
    • Added SyncRepository<T> to coordinate operations between local data sources, cloud data sources, and the sync log store, including logic for pending and failed sync retries.
  • lib/src/bloc_like/states/sync_state.dart
    • Added SyncState and its derived classes (SyncInitial, SyncInProgress, SyncSuccess, SyncFailure) to represent the different states of the sync process.
  • test/bloc_like_architecture_test.dart
    • Added a new unit test file to verify the functionality of the OfflineSyncEngine using an add and sync scenario with in-memory data sources.
Activity
  • The author, Harsh4114, has implemented a new Bloc-like architecture for offline sync workflows.
  • The author reported running git diff --check successfully.
  • The author attempted to run dart format and dart test but was unable to due to environment limitations.
  • A focused unit test, test/bloc_like_architecture_test.dart, was added and committed to validate the new architecture's core components.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces a new Bloc-like architecture for offline synchronization, providing a familiar mental model and a clear separation of concerns. The new components, including LocalDataSource, CloudDataSource, SyncLogStore, SyncRepository, SyncController, and OfflineSyncEngine, are well-structured and adhere to common Dart practices like immutability for data models. The README.md has been updated with a comprehensive guide and wiring example, which is very helpful for onboarding. A basic unit test is also included, validating the core add and sync functionality.

One area for improvement is the log ID generation, which could lead to potential collisions under high concurrency.

Comment on lines +107 to +110
String _logId() {
final micros = DateTime.now().microsecondsSinceEpoch;
return 'sync_log_$micros';
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _logId() method uses DateTime.now().microsecondsSinceEpoch to generate a unique ID. While this often works, it's not guaranteed to be unique if two SyncLog entries are created within the same microsecond, which can happen in highly concurrent scenarios. Consider using a universally unique identifier (UUID) generator for more robust uniqueness guarantees.

  String _logId() {
    // Consider using a UUID generator for more robust uniqueness
    // For example, using the 'uuid' package: Uuid().v4();
    final micros = DateTime.now().microsecondsSinceEpoch;
    return 'sync_log_$micros';
  }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new “Bloc-like” (events → controller → states) API layer for the offline sync engine, aiming to provide a higher-level, testable abstraction over local/cloud adapters and a sync-log queue, while keeping the existing SyncManager intact.

Changes:

  • Added new bloc-like contracts (LocalDataSource, CloudDataSource, SyncLogStore) and in-memory SyncLogStore implementation.
  • Implemented bloc-like orchestration components (SyncRepository, SyncController, OfflineSyncEngine) plus event/state/model types.
  • Exported the new API via the package entrypoint, documented it in README.md, and added a focused unit test.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
test/bloc_like_architecture_test.dart Adds a happy-path test for add + sync using the new bloc-like facade.
lib/src/bloc_like/states/sync_state.dart Introduces state types for the controller/engine state stream.
lib/src/bloc_like/repository/sync_repository.dart Implements local-first writes and sync-log replay to cloud.
lib/src/bloc_like/models/sync_log.dart Adds SyncLog model plus operation/status enums.
lib/src/bloc_like/events/sync_event.dart Defines event types for the bloc-like workflow.
lib/src/bloc_like/engine/offline_sync_engine.dart Adds the facade wrapper that forwards calls to SyncController.
lib/src/bloc_like/controller/sync_controller.dart Adds event handler and state stream management.
lib/src/bloc_like/contracts/sync_log_store.dart Defines persistence contract for sync logs.
lib/src/bloc_like/contracts/local_data_source.dart Defines local persistence contract.
lib/src/bloc_like/contracts/in_memory_sync_log_store.dart Provides an in-memory SyncLogStore for onboarding/tests.
lib/src/bloc_like/contracts/cloud_data_source.dart Defines cloud adapter contract.
lib/offline_sync_engine.dart Exports the new bloc-like API from the package entrypoint.
README.md Documents the new bloc-like architecture and wiring approach.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +107 to +110
String _logId() {
final micros = DateTime.now().microsecondsSinceEpoch;
return 'sync_log_$micros';
}

Copilot AI Feb 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_logId() uses only microsecondsSinceEpoch, which can collide if multiple logs are created within the same microsecond (especially in tight loops/tests). Consider adding a per-instance counter (similar to SyncManager._generateOpId()), or using a UUID, to guarantee uniqueness.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment thread lib/src/bloc_like/repository/sync_repository.dart Outdated
Comment thread lib/src/bloc_like/repository/sync_repository.dart Outdated
Comment thread lib/src/bloc_like/repository/sync_repository.dart Outdated
Comment thread lib/src/bloc_like/repository/sync_repository.dart Outdated
Comment on lines +21 to +23
Future<void> add(T data) async {
final id = idResolver(data);
await local.insert(data);

Copilot AI Feb 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new public API doesn't validate the entity ID returned by idResolver(data). If it returns an empty/whitespace-only string, you'll enqueue logs with an invalid entityId. Existing public APIs (e.g., SyncManager) throw ArgumentError.value for empty identifiers; consider adding similar trim().isEmpty checks here.

Copilot uses AI. Check for mistakes.
_emit(const SyncSuccess());
} catch (e) {
_emit(SyncFailure(e.toString()));
rethrow;

Copilot AI Feb 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The controller emits SyncFailure and then rethrows the exception. The README wiring example uses await engine.sync() without a try/catch and suggests state-driven handling; rethrowing will surface exceptions to callers and can crash apps if unhandled. Consider not rethrowing (state-only error reporting) or explicitly documenting that callers must handle exceptions.

Suggested change
rethrow;

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
final StreamController<SyncState> _states =
StreamController<SyncState>.broadcast();

SyncState _current = const SyncInitial();

SyncController({required this.repository}) {
_states.add(_current);

Copilot AI Feb 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_states is a broadcast stream and the initial SyncInitial is added in the constructor. Any listener that subscribes after construction will not receive that initial state (broadcast streams don’t replay). If consumers are expected to observe the initial state via states, consider emitting the current state in onListen (or using a BehaviorSubject-style controller).

Suggested change
final StreamController<SyncState> _states =
StreamController<SyncState>.broadcast();
SyncState _current = const SyncInitial();
SyncController({required this.repository}) {
_states.add(_current);
late final StreamController<SyncState> _states;
SyncState _current = const SyncInitial();
SyncController({required this.repository}) {
_states = StreamController<SyncState>.broadcast(
onListen: () {
_states.add(_current);
},
);

Copilot uses AI. Check for mistakes.

Future<void> _syncLogs(List<SyncLog> logs) async {
final sorted = [...logs]
..sort((a, b) => b.timestamp.compareTo(a.timestamp));

Copilot AI Feb 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sync is replaying logs in latest-first order. This can cause an update to run before a prior create for the same entity, which will fail for adapters where CloudDataSource.update() is not an upsert. Consider replaying in chronological order, or coalescing pending logs per entityId into a single effective operation (e.g., keep only the newest) before executing.

Suggested change
..sort((a, b) => b.timestamp.compareTo(a.timestamp));
..sort((a, b) => a.timestamp.compareTo(b.timestamp));

Copilot uses AI. Check for mistakes.
Comment thread lib/src/bloc_like/repository/sync_repository.dart Outdated

Copilot AI commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

@Harsh4114 I've opened a new pull request, #7, to work on those changes. Once the pull request is ready, I'll request review from you.

Harsh4114 and others added 5 commits February 20, 2026 18:50
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

@Harsh4114 I've opened a new pull request, #8, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits February 20, 2026 13:23
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

@Harsh4114 I've opened a new pull request, #9, to work on those changes. Once the pull request is ready, I'll request review from you.

@Harsh4114

Copy link
Copy Markdown
Owner Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copilot AI commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

@Harsh4114 I've opened a new pull request, #10, to work on those changes. Once the pull request is ready, I'll request review from you.

[WIP] Update pull request to address feedback on Bloc-like architecture
@Harsh4114 Harsh4114 merged commit adb4d2a into main Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants