GetDutchie / brick

An intuitive way to work with persistent data in Dart
https://getdutchie.github.io/brick/#/
371 stars 30 forks source link

Subscribe is not listening to remote changes even when policy is set to OfflineFirstGetPolicy.awaitRemote #364

Closed rutaba1 closed 1 year ago

rutaba1 commented 1 year ago

Hi, I have a configured graphql repository for and working fine for all the operations except subscribe. I had set the policy to OfflineFirstGetPolicy.awaitRemote and when I change anything from the backend the listener is not getting triggered. It triggers only If I do any CRUD operation from the frontend.

Here is the code for my Repository set up

class Repository extends OfflineFirstWithGraphqlRepository {
  Repository._(String endpoint)
      : super(
          migrations: migrations,
          graphqlProvider: GraphqlProvider(
            link: HttpLink(endpoint),
            modelDictionary: graphqlModelDictionary,
          ),
          sqliteProvider: SqliteProvider(
            "BrickGraphql.sqlite",
            databaseFactory: databaseFactory,
            modelDictionary: sqliteModelDictionary,
          ),
          offlineRequestManager: GraphqlRequestSqliteCacheManager(
            'brick_graphql_offline_queue.sqlite',
            databaseFactory: databaseFactory,
          ),
          autoHydrate: true,
        );

  factory Repository() => _singleton!;

  static Repository? _singleton;

  static void configure(String endpoint) {
    _singleton = Repository._(endpoint);
  }
}

And here is the UI where I'm trying to listen to the stream

StreamBuilder<List<Todo>>(
              stream: repository?.subscribe<Todo>(
                  policy: OfflineFirstGetPolicy.awaitRemote),
              builder: (context, ss) {
                return ss.data?.isNotEmpty == true
                    ? Center(
                        child: ListView.builder(
                          itemCount: ss.data!.length,
                          itemBuilder: (ctx, i) {
                            return Padding(
                              padding: const EdgeInsets.all(8.0),
                              child: ListTile(
                                leading: Text(ss.data![i].id),
                                title: Text(ss.data![i].title),
                                tileColor: Colors.pink.shade50,
                              ),
                            );
                          },
                        ),
                      )
                    : const Center(
                        child: Text("No data available"),
                      );
              },
            )

I have also noticed that in initState function I have to call get function to hydrate the local database the stream subscription is not doing it on it's own.

tshedor commented 1 year ago

Hey @rutaba1 is your backend GraphQL operation a subscribe operation or a get operation?

If you're calling a get operation, then subscribe will only add a new event to the stream when local data satisfying the subscribe query is changed in the SQLite db.

rutaba1 commented 1 year ago

@tshedor It is a subscribe operation and I'm even overriding the subscribe function in my GraphqlQueryOperationTransformer but still no luck.

 @override
  GraphqlOperation? get subscribe => const GraphqlOperation(
        document: r'''
      subscription Subscription{
        todo_mutated {
         key
     event 
         data {
            title
            id
          }
        }
      }''',
      );

I'm getting an error in console now with no realtime behaviour

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'Null' is not a subtype of type 'String' in type cast
E/flutter (19394): #0      _$TodoFromGraphql (package:brick_graphql_test/brick/adapters/todo_adapter.g.dart:8:30)
E/flutter (19394): #1      TodoAdapter.fromGraphql (package:brick_graphql_test/brick/adapters/todo_adapter.g.dart:95:13)
E/flutter (19394): #2      GraphqlProvider.subscribe (package:brick_graphql/src/graphql_provider.dart:131:27) 

I'm printing the values from _$TodoFromGraphql function in generated adapter and I'm getting this: MAP{id: 86, title: Test} MAP{id: 87, title: Fielder} MAP___{todo_mutated: null}

Here 1st and 2nd are valid but 3rd is not

tshedor commented 1 year ago

@rutaba1 can you please provide your Todo model? When you say "printing the values" does that mean you're doing print(data)?

rutaba1 commented 1 year ago

@tshedor Yeah,here is my model

@ConnectOfflineFirstWithGraphql(
    graphqlConfig: GraphqlSerializable(
  queryOperationTransformer: TodoQueryOperationTransformer.new,
))
class Todo extends OfflineFirstWithGraphqlModel {
  @Sqlite(unique: true)
  final String id;

  final String title;

  Todo({
    required this.id,
    required this.title,
  });
}

and in todo_adapter.g.dart I am printing the value here.

Future<Todo> _$TodoFromGraphql(Map<String, dynamic> data,
    {required GraphqlProvider provider,
    OfflineFirstWithGraphqlRepository? repository}) async {
  print('MAP_______${data}');
  return Todo(id: data['id'] as String, title: data['title'] as String);
}
tshedor commented 1 year ago

@rutaba1 ah, I see. Brick expects the fields you list within the operation to map as top-level properties for the Dart instance fields. For example, the expected Dart model would be

@ConnectOfflineFirstWithGraphql(
    graphqlConfig: GraphqlSerializable(
  queryOperationTransformer: TodoQueryOperationTransformer.new,
))
class Todo extends OfflineFirstWithGraphqlModel {
  final String key;

  final String event;

  final Map<String, dynamic> data;

  Todo({
    required this.key,
    required this.event,
    required this.data,
  });
}

If you want to use the nested properties of data as instance fields in Dart, you'll need to do some custom remapping:

@ConnectOfflineFirstWithGraphql(
    graphqlConfig: GraphqlSerializable(
  queryOperationTransformer: TodoQueryOperationTransformer.new,
))
class Todo extends OfflineFirstWithGraphqlModel {
  @Sqlite(unique: true)
  @Graphql(fromGenerator: "(data['data'] as Map)['id'] as String")
  final String id;

  @Graphql(fromGenerator: "(data['data'] as Map)['title'] as String")
  final String title;

  Todo({
    required this.id,
    required this.title,
  });
}
tshedor commented 1 year ago

@rutaba1 going to close this due to inactivity