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.
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