Skip to content

šŸš€ LiveBloc

LiveBloc is a bloc that syncs its state across devices in real-time. It works by

  1. Fetching the most recent state from the server.
  2. Subscribing to the server to receive events from all other devices.
  3. Writing the updated state to the server (only from the device that made the change).

To use LiveBloc, first, simply extend it instead of Bloc.

import 'package:blocsync/blocsync.dart';
class CounterBloc extends LiveBloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
}

Add toJson and fromJson methods to transform your state to and from JSON:

class CounterBloc extends SyncedBloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
@override
int fromJson(Map<String, dynamic> json) => json['value'] as int;
@override
Map<String, dynamic>? toJson(int state) => {'value': state};
}
abstract class CounterEvent with LiveEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

Add eventFromJson and eventToJson methods to transform your events to and from JSON.

class CounterBloc extends SyncedBloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
@override
Map<String, dynamic>? eventToJson(CounterEvent event) {
switch (event) {
case Increment():
return {'type': 'increment'};
case Decrement():
return {'type': 'decrement'};
}
return null;
}
@override
CounterEvent? eventFromJson(Map<String, dynamic> json) {
final type = json['type'] as String;
switch (type) {
case 'increment':
return Increment();
case 'decrement':
return Decrement();
}
return null;
}
}

That’s it! šŸŽ‰ Your bloc will now sync events across devices in real-time.

import 'package:blocsync/blocsync.dart';
class CounterBloc extends LiveBloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
@override
int fromJson(Map<String, dynamic> json) => json['value'] as int;
@override
Map<String, dynamic>? toJson(int state) => {'value': state};
@override
CounterEvent? eventFromJson(Map<String, dynamic> json) {
final type = json['type'] as String;
switch (type) {
case 'increment':
return Increment();
case 'decrement':
return Decrement();
}
return null;
}
@override
Map<String, dynamic>? eventToJson(CounterEvent event) {
switch (event) {
case Increment():
return {'type': 'increment'};
case Decrement():
return {'type': 'decrement'};
}
return null;
}
}
abstract class CounterEvent with LiveEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

LiveBlocs can be made private and partitioned just like SyncedBlocs. For more information, see Private Blocs and Partitioned Blocs.

A lot of debate went into this decision. In the end, the decision came down to some future plans.

Primarily, we plan to support SharedBlocs soon, which will allow you to sync states across multiple users. However, this will require much more sophisticated security than private blocs. Some questions we asked were:

  • How to we allow certain users to read state and others manipulate it?
  • How do we allow certain users to manipulate state in some ways, but others manipulate it in different ways?

We realized that we could restrict user’s abilities to send events, and that would restrict their ability to manipulate state.

It does have some drawbacks. It means

  • We can’t support a LiveCubit
  • States are not guaranteed to be in sync across devices perfectly. If a user misses an event, the state will be out of sync.

We’re open to feedback on this decision. If you have feedback, please let us know in the GitHub Discussions.