Search code examples
flutterdartflutter-testrethinkdbdart-null-safety

type '_CompactLinkedHashSet<String>' is not a subtype of type 'Map<dynamic, dynamic>?'


I'm following a tutorial that builds a messaging app using flutter and rethinkdb. It hasn't been updated for null safety, but I'm trying to move forward and learn it with the most updated packages & latest version of flutter as I go, as a beginner.

I'm trying to test typing notifications, and I'm getting an error that I can't decipher.

type '_CompactLinkedHashSet<String>' is not a subtype of type 'Map<dynamic, dynamic>?'

package:rethink_db_ns/src/ast.dart:1223
TypingNotification.send
package:chat/…/typing/typing_notification.dart:22
main.<fn>
test/typing_notification_test.dart:44
main.<fn>
test/typing_notification_test.dart:37

All of the Maps that I have are Map<String, dynamic>, so I'm not sure where the Map<dynamic, dynamic> is coming from. I am also using package:rethink_db_ns instead of what the tutorial author uses, which is the rethink package before ns was added. So I'm not sure if I have to change things further for the new package. I also made sure I didn't see any red squiggles before I tried running the test.

Here are my files:

Here is the testing file where I try to run the test and get the aforementioned error:

import 'package:chat/src/models/typing_event.dart';
import 'package:chat/src/models/user.dart';
import 'package:chat/src/services/typing/typing_notification.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rethink_db_ns/rethink_db_ns.dart';

import 'helpers.dart';

void main() {
  RethinkDb r = RethinkDb();
  late Connection connection;
  late TypingNotification sut;

  setUp(() async {
    connection = await r.connect();
    await createDb(r, connection);
    sut = TypingNotification(r, connection);
  });

  tearDown(() async {
    sut.dispose();
    await cleanDb(r, connection);
  });

  final user = User.fromJson({
    'id': '1234',
    'active': true,
    'lastSeen': DateTime.now(),
  });

  final user2 = User.fromJson({
    'id': '1111',
    'active': true,
    'lastSeen': DateTime.now(),
  });

  test('sent typing notification successfully', () async {
    TypingEvent typingEvent = TypingEvent(
      from: user2.id!,
      to: user.id!,
      event: Typing.start,
    );

    final res = await sut.send(event: typingEvent, to: user);
    expect(res, true);
  });
}

Here is typing_notification.dart:

import 'dart:async';

import 'package:chat/src/models/user.dart';
import 'package:chat/src/models/typing_event.dart';
import 'package:chat/src/services/typing/typing_notification_service_contract.dart';
import 'package:rethink_db_ns/rethink_db_ns.dart';

class TypingNotification implements ITypingNotification {
  final Connection _connection;
  final RethinkDb _r;

  final _controller = StreamController<TypingEvent>.broadcast();
  StreamSubscription? _changefeed; // added ? to this

  TypingNotification(this._r, this._connection); //added this._change

  @override
  Future<bool> send({required TypingEvent event, required User to}) async {
    if (!to.active) return false;
    Map record = await _r
        .table('typing_events')
        .insert(event.toJson(), {'conflict:' 'update'}).run(_connection);
    return record['inserted'] == 1;
  }

  @override
  Stream<TypingEvent> subscribe(User user, List<String> userIds) {
    _startReceivingTypingEvents(user, userIds); 
    return _controller.stream;
  }

  @override
  void dispose() {
    _changefeed?.cancel();
    _controller.close();
  }

  _startReceivingTypingEvents(User user, List<String> userIds) {
    _changefeed = _r
        .table('typing_events')
        .filter((event) {
          return event('to')
              .eq(user.id)
              .and(_r.expr(userIds).contains(event('from')));
        })
        .changes({'include_initial': true})
        .run(_connection)
        .asStream()
        .cast<Feed>()
        .listen((event) {
          event
              .forEach((feedData) {
                if (feedData['new_val'] == null) return;

                final typing = _eventFromFeed(feedData);
                _controller.sink.add(typing);
                _removeEvent(typing);
              })
              .catchError((err) => print(err))
              .onError((error, stackTrace) => print(error));
        });
  }

  TypingEvent _eventFromFeed(feedData) {
    return TypingEvent.fromJson(feedData['new_val']);
  }

  _removeEvent(TypingEvent event) {
    _r
        .table('typing_events')
        .get(event.id)
        .delete({'return_changes': false}).run(_connection);
  }
}

Here is typing_event.dart:

enum Typing { start, stop }

extension TypingParser on Typing {
  String value() {
    return toString().split('.').last;
  }

  static Typing fromString(String event) {
    return Typing.values.firstWhere((element) => element.value() == event);
  }
}

class TypingEvent {
  final String from;
  final String to;
  final Typing event;
  String? _id;
  String? get id => _id;

  TypingEvent({
    required this.from,
    required this.to,
    required this.event,
  });

  Map<String, dynamic> toJson() => { 
        'from': this.from,
        'to': this.to,
        'event': this.event.value(),
      };

  factory TypingEvent.fromJson(Map<String, dynamic> json) { 
    var event = TypingEvent(
      from: json['from'],
      to: json['to'],
      event: TypingParser.fromString(json['event']),
    );
    event._id = json['id'];
    return event;
  }
}

And here is typing_notification_service_contract.dart:

import 'package:chat/src/models/typing_event.dart';
import 'package:chat/src/models/user.dart';

abstract class ITypingNotification {
  Future<bool> send(
      {required TypingEvent event, required User to}); // the tutorial did not include ", required User to" but I added it to prevent an error, since required User to is passed in another file 
  Stream<TypingEvent> subscribe(User user, List<String> userIds);
  void dispose();
}

And finally, typing_event.dart

enum Typing { start, stop }

extension TypingParser on Typing {
  String value() {
    return toString().split('.').last;
  }

  static Typing fromString(String event) {
    return Typing.values.firstWhere((element) => element.value() == event);
  }
}

class TypingEvent {
  final String from;
  final String to;
  final Typing event;
  String? _id;
  String? get id => _id;

  TypingEvent({
    required this.from,
    required this.to,
    required this.event,
  });

  Map<String, dynamic> toJson() => { 
        'from': this.from,
        'to': this.to,
        'event': this.event.value(),
      };

  factory TypingEvent.fromJson(Map<String, dynamic> json) { 
    var event = TypingEvent(
      from: json['from'],
      to: json['to'],
      event: TypingParser.fromString(json['event']),
    );
    event._id = json['id'];
    return event;
  }
}

I'm a total beginner & trying to push myself to learn null safety, so there is a good chance my error is very simple and I'm just not seeing it. Can anyone point me in the right direction or offer any guidance here? I can also add more details upon request if you think it might be helpful.


Solution

  • Try replacing

    .insert(event.toJson(), {'conflict:' 'update'}).run(_connection);
        
    

    with

    .insert(event.toJson(), {'conflict': 'update'}).run(_connection);
                                       ^
                                   add colon
    

    in typing_notification,.dart line 22, it's expecting Map, probably you meant to place ":" outside the string