@sync

Easy data synchronization with support for custom data types.

The @sync annotation gives you an easy way to sync state of a StatefulComponent from the server to the client.

For a general guide about data-fetching on the server see Data Fetching.

Usage

Start by annotating a field of your StatefulComponent with @sync:

lib/my_component.dart
class MyComponent extends StatefulComponent { /* ... */ }

class MyComponentState extends State<MyComponent> {

  // Use @sync on any field to automatically sync its value from the server to the client.
  @sync
  int myValue = 0;

}

Then when running jaspr serve or jaspr build this will generate a .sync.dart alongside your component including a <ComponentStateName>SyncMixin that you have to apply to your state class:

lib/my_component.dart
import 'my_component.sync.dart';

class MyComponent extends StatefulComponent { /* ... */ }

class MyComponentState extends State<MyComponent> with MyComponentStateSyncMixin {

  @sync
  int myValue = 0;

}

You can also use the @sync annotation on multiple fields of the State class.

Custom Data Types

When using @sync on a field, that field is

  • serialized on the server during pre-rendering as part of the html output
  • deserialized on the client during initState() when this component is first mounted

Therefore, any annotated field must either have a primitive serializable type: bool, int, double, String or List / Maps of these.

Or you can use custom data types by using the @encoder and @decoder annotations with the class:

model.dart
class Model {
  @decoder
  static Model fromJson(Map<String, dynamic> json) => /* ... */;

  @encoder
  Map<String, dynamic> toJson() => /* ... */;
}

Read the docs on the @encoder / @decoder annotations to learn more about how to set up serialization for custom data types.

With this setup you can use fields of any type with the @sync annotation.

Preloading Data

When using the @sync annotation, it is common that you want to preload the data that you then want to sync to the client.

When the data is synchronous you can simply assign the value of a @sync field in the initState() method:

lib/my_component.dart
import 'my_component.sync.dart';

class MyComponent extends StatefulComponent { /* ... */ }

class MyComponentState extends State<MyComponent> with MyComponentStateSyncMixin {

  @sync
  int myValue = 0;

  @override
  void initState() {
    // On the client, [myValue] and any other synced field will be assigned
    // the synced value during the [super.initState()] call.
    super.initState();

    // On the server, we can assign the field during pre-rendering.
    if (!kIsWeb) {
      myValue = getValueOnServer();
    }
  }

  // You can modify any synced value on the client as usual.
  // @sync is only for setting the initial value of a field on the client to a value that has
  // been set during the pre-rendering on the server.
  void increment() {
    setState(() {
      myValue++;
    });
  }
}

When the data you want to load is asynchronous, you can additionally use the PreloadStateMixin to perform any asynchronous work before initState() is called on the server:

lib/my_component.dart
import 'my_component.sync.dart';

class MyComponent extends StatefulComponent { /* ... */ }

class MyComponentState extends State<MyComponent> with MyComponentStateSyncMixin, PreloadStateMixin {

  @sync
  int myValue = 0;

  // Perform any async work on the server.
  // This will delay `initState()` and the building of this component until the future is completed.
  Future<void> preloadState() async {
    // Load some async data, e.g. from a database.
    myValue = await getValueFromDatabase();
  }
}

Read the docs on PreloadStateMixin for more details.

Alternatives

  • Check out the SyncStateMixin for a more explicit way to sync state of a StatefulComponent between server and client (this is used by @sync under the hood).

  • Check out @client components, that support passing data from the server to the client as part of the component parameters, including support for custom data types.