fasq_bloc

Pub Enterprise Ready Null Safety

The Native Bloc Adapter for FASQ.

Bring the power of caching, optimistic updates, and offline support to your Bloc application with zero friction. fasq_bloc bridges the gap between fasq's powerful query engine and the flutter_bloc ecosystem, allowing you to build complex data-driven apps without fighting your architecture.

Current Version: 0.3.0

πŸ“š Documentation

For full documentation and API reference, visit:
https://fasq.shafi.dev/adapters/bloc

✨ Features

  • 🧟 Zombie-proof Caching: Data is cached, deduplicated, and garbage collected automatically. No more stale data bugs.
  • πŸ›‘οΈ Resilience Built-in: Circuit Breakers protect your app from crashing backends.
  • πŸ”Œ Offline-First: Queue mutations when offline and sync automatically when connectivity returns.
  • πŸš€ Native Feel: Zero friction. It's just a Cubit where QueryClient is auto-injected.
  • 🧩 Composition: Solve "Bloc Hell" by composing multiple queries in a single Bloc with FasqSubscriptionMixin.

πŸ“¦ Installation

dependencies:
  fasq_bloc: ^0.3.0
  flutter_bloc: ^8.0.0

πŸš€ Quick Start

1. Setup the Provider

Wrap your app (or feature) with FasqBlocProvider. It automatically handles the QueryClient lifecycle.

void main() {
  runApp(
    FasqBlocProvider(
      child: MyApp(),
    ),
  );
}

2. Create a QueryCubit

Extend QueryCubit for a 1-to-1 mapping between a Bloc and a Query.

class UserCubit extends QueryCubit<User> {
  final int userId;

  UserCubit(this.userId);

  @override
  QueryKey get queryKey => QueryKey('user', args: userId);

  @override
  Future<User> Function() get queryFn => () => api.fetchUser(userId);
}

3. Use in UI

Consume it like any other Bloc.

BlocBuilder<UserCubit, QueryState<User>>(
  builder: (context, state) {
    if (state.isLoading) return CircularProgressIndicator();
    if (state.hasData) return Text('Hello ${state.data!.name}');
    return Text('Error: ${state.error}');
  },
)

πŸ› οΈ Advanced Usage

Composition (Multiple Queries)

Need to fetch a User and their Posts in the same Bloc? Use the FasqSubscriptionMixin.

class DashboardBloc extends Cubit<DashboardState> with FasqSubscriptionMixin {
  DashboardBloc() : super(DashboardState.loading()) {
    // 1. Fetch User
    final userQuery = client.getQuery<User>('user'.toQueryKey(), ...);

    subscribeToQuery(userQuery, (state) {
       // Update internal state based on User query
       emit(state.copyWith(user: state.data));
    });

    // 2. Fetch Posts
    final postsQuery = client.getQuery<List<Post>>('posts'.toQueryKey(), ...);

    subscribeToQuery(postsQuery, (state) {
       emit(state.copyWith(posts: state.data));
    });
  }
}

Optimistic Updates

Update your UI before the server responds for a snappy experience.

class TodosCubit extends MutationCubit<Todo, String> {
  // ... configuration ...

  void addTodo(String text) {
    mutate(
      text,
      onMutate: () async {
        // 1. Cancel outgoing refetches
        await queryClient.cancelQueries('todos');

        // 2. Snapshot previous value
        final previous = queryClient.getQueryData<List<Todo>>('todos');

        // 3. Optimistically update cache
        queryClient.setQueryData<List<Todo>>(
          'todos',
          [...previous ?? [], Todo(id: 'temp', text: text)]
        );

        return { 'previous': previous };
      },
      onError: (error, context) {
        // 4. Rollback on error
        queryClient.setQueryData('todos', context['previous']);
      },
      onSettled: () {
        // 5. Always refetch to ensure sync
        queryClient.invalidateQueries('todos');
      }
    );
  }
}

Libraries

fasq_bloc
Bloc/Cubit adapters for the FASQ async data layer.