Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does not work with embedded objects #12

Open
eelco2k opened this issue Oct 3, 2023 · 4 comments
Open

Does not work with embedded objects #12

eelco2k opened this issue Oct 3, 2023 · 4 comments

Comments

@eelco2k
Copy link

eelco2k commented Oct 3, 2023

Unfortunately it does not work with @Embedded objects. It just sees it as an insert.
(for .toJson() i'm using the json_annotation package)

for example:

@JsonSerializable()
@collection
class Orders extends CrdtBaseObject {
  Id id = Isar.autoIncrement;
  @Index(unique: true, replace: true)
  List<CartModel>? cart;
  int? userId;
  Table? table;
  double? orderTotal;
  double? taxTotal;
}

@JsonSerializable()
@embedded
// even not working with: class CartModel extends CrdtBaseObject {}
class CartModel {
  String productUuid = uuid.v4();
  double? price;
  double? discountAmount;
  double? quantity;
  double? taxAmount;
}

It inserts the whole json ( .toJson() ) in the crdt entries table (value column) as 1 crdt event.

So i guess it only works with the Int, String, and simple Lists..

Furthermore what could be the case is that it needs an id autoIncrement which this embedded object does not have.
in isar_extensions.dart the putAllChanges() relies on schema.getId() for new and changed entries.

specific:

final elementsMatch =
        elements.splitMatch((e) => schema.getId(e) == Isar.autoIncrement);
@richard457
Copy link

@eelco2k could you please share the example of how you are using the package?

@eelco2k
Copy link
Author

eelco2k commented Oct 18, 2023

@richard457 i was now testing it in a simpeler sample project here is my setup:

pubspec dependencies:

dependencies:
  flutter:
    sdk: flutter
    
  isar_crdt:
    git:
      url: https://github.com/davidlondono/isar_crdt.git
      
  cupertino_icons: ^1.0.2
  isar: ^3.1.0+1
  isar_flutter_libs: ^3.1.0+1
  path_provider: ^2.1.1
  crdt: ^5.1.2
  uuid: ^4.1.0
  json_annotation: ^4.8.1
  
  dev_dependencies:
  flutter_test:
    sdk: flutter
  json_serializable: ^6.7.1
  flutter_lints: ^2.0.0
  isar_generator: ^3.1.0+1
  build_runner: ^2.4.6

lib/models/cars.dart

import 'package:isar/isar.dart';
import 'package:isar_crdt/isar_crdt.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:uuid/uuid.dart';

part 'cars.g.dart';

var uuid = const Uuid();

@JsonSerializable(explicitToJson: true)
@Collection()
class Cars extends CrdtBaseObject {
  Id? id = Isar.autoIncrement;
  String? make;
  CarModel? model;
  int? year;

  Cars({this.id, this.make, this.model, this.year});

  factory Cars.fromJson(Map<String, dynamic> json) => _$CarsFromJson(json);
  Map<String, dynamic> toJson() => _$CarsToJson(this);
}

@JsonSerializable(explicitToJson: true)
@Embedded()
class CarModel {
  String? productUuid = uuid.v4();
  String? name;
  List<String>? features;

  CarModel({
    this.productUuid,
    this.name,
    this.features,
  });

  factory CarModel.fromJson(Map<String, dynamic> json) => _$CarModelFromJson(json);
  Map<String, dynamic> toJson() => _$CarModelToJson(this);
}

@Collection()
class CrdtEntry extends CrdtBaseModel {}

lib/main.dart of the flutter counter sample app i reuse the incrementCounter function to start crdt delayed inserts

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  late Isar isar;

  void _initDB() async {
    final dir = await getApplicationDocumentsDirectory();

    isar = await Isar.open(
      [CarsSchema, CrdtEntrySchema],
      directory: dir.path,
    );
  }
  
  void _incrementCounter() {

    startIsarDb();

    setState(() {
      _counter++;
    });
  }
  
   void startIsarDb() async {
    var uuid = const Uuid();
    IsarCrdt crdt;

    String nodeId = 'test-nodeid';
    String remoteNodeId = 'test-nodeid-remote';
    String workspace = 'test-workspace';

    crdt = IsarCrdt.master(
      crdtCollection: isar.crdtEntrys,
      builder: () => Future.value(CrdtEntry()),
      sidGenerator: () => uuid.v4(),
      nodeId: nodeId,
    );
    isar.setCrdt(crdt);
   
     String theSid = uuid.v4();

    int firstCar = 1;
    
     Future.delayed(const Duration(milliseconds: 2000), () async {
      final lateCar = Cars()
        ..make = 'Nissan'
        ..sid = theSid
        ..year = 2014;

      final model = CarModel()
        ..name = "Quashqai"
        ..features = ["1.4 liter", "petrol", "manual gearbox"];

      lateCar.model = model;

      await crdt.writer?.writeTxn(() async {
        final carCollection = isar.collection<Cars>();
        firstCar = await carCollection.putChanges(lateCar);
      });
    });

    Future.delayed(const Duration(milliseconds: 4000), () async {
      final carCollection = isar.cars;
      var thecar = await carCollection.get(firstCar);
      thecar?.make = 'Toyota';
      thecar?.sid = theSid;
      thecar?.year = 2017;
      thecar?.model?.name = "Passat";

      await crdt.writer?.writeTxn(() async {
        // print(thecar);
        await carCollection.putChanges(thecar!);
      });
    });

    Future.delayed(const Duration(milliseconds: 5000), () async {
      final carCollection = isar.cars;
      var thecar = await carCollection.get(firstCar);
      List<String>? currFeatures = thecar?.model?.features.toList();
      currFeatures!.add("another extra Feature");
      thecar?.model?.features = currFeatures;

      await crdt.writer?.writeTxn(() async {
        // print(thecar);
        await carCollection.putChanges(thecar!);
      });
    });

    // merge everything....

    Future.delayed(const Duration(milliseconds: 6000), () async {
      final changes = await crdt.getChanges(onlyModifiedHere: true);

      // final carCollection = isar.collection<Cars>();
      List<MergableChange> syncChanges = [];
      for (var change in changes) {
        // print(change.toJson());
        syncChanges.add(MergableChange(change: change.change, hlc: change.hlc));
      }

      // print(syncChanges);

      crdt.writer?.upgradeChanges(changes);
      });
  }
      
   @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(onPressed: _initDB, child: const Text('Init DB')),
            const SizedBox(height: 10),
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }   

you would expect that:
currFeatures!.add("another extra Feature");

gives crdtEntry only on the added "another extra Feature" but it says it added the whole features which is not correct.

So the @Embedded wil work but not as I was expecting. Because when you use multiple parent -> child -> child -> child. as inner embeddings it gets worse.
For example if you make a change in the lowest child object. the CRDT entry sees it like a new entry for the whole parent-> child -> child -> child entry. but it only should say path of lowest child has a key changed with this value.

@eelco2k
Copy link
Author

eelco2k commented Oct 18, 2023

This is the resulting CrdtEntry Table
CrdtEntry.json

as you can see a whole update on the "another extra Feature" item:

collection: "Cars",
field: "model",
operation: "update",
value: "{"name":"Passat","features":["1.4 liter","petrol","manual gearbox","another extra Feature"]}"

which is actually incorrect and should be:

collection:"CarModel",
field: "features",
operation: "add",
value: "add extra Feature"

So single level depth(s) for each Collection is no problem for this package isar_crdt, but when you use objects in objects in one and the same collection (table) this will not work correctly.

@davidlondono
Copy link
Owner

I'm not maintaining this project as Isar was not the best option to manage this crdt changes
If anyone wants to grow this, can create a fork branch and work with it
but as for my self, I'm creating another solution with Sqlite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants