Bloc: Hydrated: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>'

Created on 13 Jul 2020  路  11Comments  路  Source: felangel/bloc

From our Discord
I am trying to migrate all of my code to 5.0.0, but I am currently running into the following error for HydratedBloc type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>' , this is happening in the fromJson method...

To Reproduce
Author of issue (me) thinks you just need two nested Map<String, whatever> to reproduce this issue.
But some sample code will be awesome to have, and I will write it a bit :wink:

Expected behavior
Everything goes butter smooth

Logs
Stacktrace says it happened here:

@override
State get state {
  if (_state != null) return _state;
  try {
    final stateJson = storage.read(storageToken);
    if (stateJson == null) return super.state;
    return fromJson(Map<String, dynamic>.from(stateJson));
  } on dynamic catch (error, stackTrace) {
    onError(error, stackTrace);
    return super.state;
  }
}
bug hydrated_bloc

Most helpful comment

So help wanted馃槢! @felangel

All 11 comments

As you can see, we already encountered same issue before,
therefore Map<String, dynamic>.from(stateJson) is used.


I've approached this issue today and came up changing hive's internal readMap method in hive/lib/src/binary/binary_reader_impl.dart
Here is before:

@override
Map readMap([int length]) {
  length ??= readUint32();
  var map = <dynamic, dynamic>{};
  for (var i = 0; i < length; i++) {
    map[read()] = read();
  }
  return map;
}

And here is after

@override
Map readMap([int length]) {
  var map = <dynamic, dynamic>{};

  length ??= readUint32();
  if (length < 1) return map;

  final keyType = readByte();
  if (keyType == FrameValueType.intT) {
    map = <int, dynamic>{};
  }
  if (keyType == FrameValueType.stringT) {
    map = <String, dynamic>{};
  }
  map[read(keyType)] = read();

  for (var i = 1; i < length; i++) {
    map[read()] = read();
  }

  return map;
}

pro tip:
_hive writes data as sequence of bytes, prepending every value with it's typeId, so you can sequentially read type before reading actual data_

As you can see, read of first entry is a bit different from the rest. On first read I determine the type of Map key and create my map accordingly.

final keyType = readByte();
keyType == FrameValueType.intT // <int, dynamic>{}
keyType == FrameValueType.stringT // <String, dynamic>{}

And then finish read of first entry like so

map[read(keyType)] = read(); // passing keyType to `read` to avoid reading type byte again

Worked for me! :smile_cat: :smile_cat:
Now what should we actually do with it??

So help wanted馃槢! @felangel

@orsenkucher thanks for putting that together! Will take a look later today 馃憤

@orsenkucher I just opened a pull request with a fix. I believe the issue is actually in the way the current application serializes and deserializes the model and not an issue in hive. Thoughts?

@felangel that is what we suggest as quick fix for this problem, but it's very tedious work especially knowing that previously it worked fine

And default json.encode was kind enough to avoid such error

Maybe we can run this Map<>.from somewhere in library code, to relieve developers from it

Check out this pull request showing how to use json_serializable to handle toJson and fromJson for us. This is my recommendation 馃憤

Thanks for the correction of my sample repo.

Fixed in #1460 and published in v6.0.0-dev.2 of hydrated_bloc 馃帀

Was this page helpful?
0 / 5 - 0 ratings