From 4aaea4a3b081d5465d3292d50a4f5d9b7f2d3699 Mon Sep 17 00:00:00 2001 From: ersanKolay Date: Wed, 11 Mar 2026 16:23:02 +0300 Subject: [PATCH 01/13] fix(hydrated_bloc): convert non-string map keys via toString instead of dropping them When using json_serializable with @JsonValue(int) enums as map keys, _traverseComplexJson silently dropped entries because _cast(key) returned null for non-String keys. This converts them via toString() instead, consistent with _traverseRead behavior. Closes #3983 --- .../hydrated_bloc/lib/src/hydrated_bloc.dart | 2 +- .../hydrated_bloc/test/cubits/cubits.dart | 1 + .../test/cubits/int_key_map_cubit.dart | 91 +++++++++++++++++++ packages/hydrated_bloc/test/e2e_test.dart | 16 ++++ .../test/hydrated_cubit_test.dart | 42 +++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart diff --git a/packages/hydrated_bloc/lib/src/hydrated_bloc.dart b/packages/hydrated_bloc/lib/src/hydrated_bloc.dart index 85cc5b2ad48..8e3b2424b0e 100644 --- a/packages/hydrated_bloc/lib/src/hydrated_bloc.dart +++ b/packages/hydrated_bloc/lib/src/hydrated_bloc.dart @@ -324,7 +324,7 @@ mixin HydratedMixin on BlocBase { _checkCycle(object); final map = {}; object.forEach((dynamic key, dynamic value) { - final castKey = _cast(key); + final castKey = key is String ? key : key?.toString(); if (castKey != null) { map[castKey] = _traverseWrite(value).value; } diff --git a/packages/hydrated_bloc/test/cubits/cubits.dart b/packages/hydrated_bloc/test/cubits/cubits.dart index f1905d95c97..8a1979aec2f 100644 --- a/packages/hydrated_bloc/test/cubits/cubits.dart +++ b/packages/hydrated_bloc/test/cubits/cubits.dart @@ -2,6 +2,7 @@ export 'bad_cubit.dart'; export 'cyclic_cubit.dart'; export 'freezed_cubit.dart'; export 'from_json_state_cubit.dart'; +export 'int_key_map_cubit.dart'; export 'json_serializable_cubit.dart'; export 'list_cubit.dart'; export 'manual_cubit.dart'; diff --git a/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart b/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart new file mode 100644 index 00000000000..1cd17d22253 --- /dev/null +++ b/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart @@ -0,0 +1,91 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:meta/meta.dart'; + +/// Simulates the pattern produced by json_serializable +/// with `@JsonValue(int)` enums used as map keys. +/// See: https://github.com/felangel/bloc/issues/3983 +class IntKeyMapCubit extends HydratedCubit { + IntKeyMapCubit() : super(const Palette({})); + + void update(Palette palette) => emit(palette); + + @override + Map toJson(Palette state) => state.toJson(); + + @override + Palette fromJson(Map json) => Palette.fromJson(json); +} + +/// An enum whose serialized form uses int values, +/// similar to `@JsonValue(int)` from json_annotation. +class Season { + const Season._(this.index, this.name); + + static const spring = Season._(0, 'spring'); + static const summer = Season._(1, 'summer'); + static const autumn = Season._(2, 'autumn'); + static const winter = Season._(3, 'winter'); + + static const values = [spring, summer, autumn, winter]; + + final int index; + final String name; + + int toJson() => index; + + static Season fromJson(int value) => + values.firstWhere((e) => e.index == value); + + @override + bool operator ==(Object other) => + identical(this, other) || other is Season && other.index == index; + + @override + int get hashCode => index.hashCode; + + @override + String toString() => 'Season.$name'; +} + +@immutable +class Palette { + const Palette(this.seasonColors); + + factory Palette.fromJson(Map json) { + final raw = json['seasonColors'] as Map? ?? {}; + return Palette( + raw.map( + (key, value) => MapEntry( + Season.fromJson(int.parse(key)), + value as String, + ), + ), + ); + } + + final Map seasonColors; + + /// Produces a map with int keys (Season.index), matching the + /// json_serializable output for `@JsonValue(int)` enums. + Map toJson() { + return { + 'seasonColors': seasonColors.map( + (key, value) => MapEntry(key.toJson(), value), + ), + }; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! Palette) return false; + if (seasonColors.length != other.seasonColors.length) return false; + for (final entry in seasonColors.entries) { + if (other.seasonColors[entry.key] != entry.value) return false; + } + return true; + } + + @override + int get hashCode => seasonColors.hashCode; +} diff --git a/packages/hydrated_bloc/test/e2e_test.dart b/packages/hydrated_bloc/test/e2e_test.dart index a1816297f28..c6bdd6db04d 100644 --- a/packages/hydrated_bloc/test/e2e_test.dart +++ b/packages/hydrated_bloc/test/e2e_test.dart @@ -383,6 +383,22 @@ void main() { }); }); + group('IntKeyMapCubit', () { + test('persists and restores state with non-string map keys', () async { + final palette = Palette({ + Season.spring: 'green', + Season.summer: 'yellow', + Season.autumn: 'orange', + Season.winter: 'white', + }); + final cubit = IntKeyMapCubit(); + expect(cubit.state, const Palette({})); + cubit.update(palette); + await sleep(); + expect(IntKeyMapCubit().state, palette); + }); + }); + group('FromJsonStateCubit', () { test('does not throw StackOverflow ', () async { final fromJsonCalls = []; diff --git a/packages/hydrated_bloc/test/hydrated_cubit_test.dart b/packages/hydrated_bloc/test/hydrated_cubit_test.dart index 421f12f5f34..09ee917d4d1 100644 --- a/packages/hydrated_bloc/test/hydrated_cubit_test.dart +++ b/packages/hydrated_bloc/test/hydrated_cubit_test.dart @@ -91,6 +91,23 @@ class MyMultiHydratedCubit extends HydratedCubit { int? fromJson(dynamic json) => json['value'] as int?; } +class MyIntKeyMapCubit extends HydratedCubit> { + MyIntKeyMapCubit() : super(const {}); + + void update(Map value) => emit(value); + + @override + Map toJson(Map state) { + return {'data': state}; + } + + @override + Map fromJson(Map json) { + final raw = json['data'] as Map? ?? {}; + return raw.map((key, value) => MapEntry(int.parse(key), value as String)); + } +} + class MyHydratedCubitWithCustomStorage extends HydratedCubit { MyHydratedCubitWithCustomStorage(Storage storage) : super(0, storage: storage); @@ -396,6 +413,31 @@ void main() { }); }); + group('MyIntKeyMapCubit', () { + test('converts non-string (int) map keys to strings when persisting', () { + final cubit = MyIntKeyMapCubit(); + const data = {0: 'spring', 1: 'summer', 2: 'autumn', 3: 'winter'}; + const change = Change( + currentState: {}, + nextState: data, + ); + cubit.onChange(change); + verify( + () => storage.write('MyIntKeyMapCubit', { + 'data': {'0': 'spring', '1': 'summer', '2': 'autumn', '3': 'winter'}, + }), + ).called(1); + }); + + test('restores state when cache has stringified int keys', () { + when(() => storage.read(any())).thenReturn({ + 'data': {'0': 'a', '1': 'b'}, + }); + final cubit = MyIntKeyMapCubit(); + expect(cubit.state, {0: 'a', 1: 'b'}); + }); + }); + group('MyHydratedCubitWithCustomStorage', () { setUp(() { HydratedBloc.storage = null; From b4202d47f923e32d475b6c3eec82ca2a852f4c3c Mon Sep 17 00:00:00 2001 From: ersanKolay Date: Wed, 11 Mar 2026 19:33:32 +0300 Subject: [PATCH 02/13] style: format hydrated_cubit_test.dart --- packages/hydrated_bloc/test/hydrated_cubit_test.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/hydrated_bloc/test/hydrated_cubit_test.dart b/packages/hydrated_bloc/test/hydrated_cubit_test.dart index 09ee917d4d1..8f7e8a7aebf 100644 --- a/packages/hydrated_bloc/test/hydrated_cubit_test.dart +++ b/packages/hydrated_bloc/test/hydrated_cubit_test.dart @@ -424,7 +424,12 @@ void main() { cubit.onChange(change); verify( () => storage.write('MyIntKeyMapCubit', { - 'data': {'0': 'spring', '1': 'summer', '2': 'autumn', '3': 'winter'}, + 'data': { + '0': 'spring', + '1': 'summer', + '2': 'autumn', + '3': 'winter' + }, }), ).called(1); }); From 3824f799e0719d323b8df219da6ec8eeba848212 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:13:24 -0500 Subject: [PATCH 03/13] chore: various cleanup --- packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart | 1 + packages/hydrated_bloc/test/hydrated_cubit_test.dart | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart b/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart index 1cd17d22253..4357489808b 100644 --- a/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart @@ -18,6 +18,7 @@ class IntKeyMapCubit extends HydratedCubit { /// An enum whose serialized form uses int values, /// similar to `@JsonValue(int)` from json_annotation. +@immutable class Season { const Season._(this.index, this.name); diff --git a/packages/hydrated_bloc/test/hydrated_cubit_test.dart b/packages/hydrated_bloc/test/hydrated_cubit_test.dart index 8f7e8a7aebf..f58671b7cb1 100644 --- a/packages/hydrated_bloc/test/hydrated_cubit_test.dart +++ b/packages/hydrated_bloc/test/hydrated_cubit_test.dart @@ -417,18 +417,14 @@ void main() { test('converts non-string (int) map keys to strings when persisting', () { final cubit = MyIntKeyMapCubit(); const data = {0: 'spring', 1: 'summer', 2: 'autumn', 3: 'winter'}; - const change = Change( - currentState: {}, - nextState: data, - ); - cubit.onChange(change); + cubit.update(data); verify( () => storage.write('MyIntKeyMapCubit', { 'data': { '0': 'spring', '1': 'summer', '2': 'autumn', - '3': 'winter' + '3': 'winter', }, }), ).called(1); From bbb8472aa5026e966fe96c3064244cef31d00c8e Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:14:01 -0500 Subject: [PATCH 04/13] chore: additional cleanup --- packages/hydrated_bloc/test/hydrated_cubit_test.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/hydrated_bloc/test/hydrated_cubit_test.dart b/packages/hydrated_bloc/test/hydrated_cubit_test.dart index f58671b7cb1..45798100275 100644 --- a/packages/hydrated_bloc/test/hydrated_cubit_test.dart +++ b/packages/hydrated_bloc/test/hydrated_cubit_test.dart @@ -94,8 +94,6 @@ class MyMultiHydratedCubit extends HydratedCubit { class MyIntKeyMapCubit extends HydratedCubit> { MyIntKeyMapCubit() : super(const {}); - void update(Map value) => emit(value); - @override Map toJson(Map state) { return {'data': state}; @@ -417,7 +415,11 @@ void main() { test('converts non-string (int) map keys to strings when persisting', () { final cubit = MyIntKeyMapCubit(); const data = {0: 'spring', 1: 'summer', 2: 'autumn', 3: 'winter'}; - cubit.update(data); + const change = Change( + currentState: {}, + nextState: data, + ); + cubit.onChange(change); verify( () => storage.write('MyIntKeyMapCubit', { 'data': { From 2382237ebfe767f0290e9ee9c4e475d9473bb217 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:17:13 -0500 Subject: [PATCH 05/13] chore: additional simplification --- packages/hydrated_bloc/lib/src/hydrated_bloc.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/hydrated_bloc/lib/src/hydrated_bloc.dart b/packages/hydrated_bloc/lib/src/hydrated_bloc.dart index 8e3b2424b0e..0ad74868fca 100644 --- a/packages/hydrated_bloc/lib/src/hydrated_bloc.dart +++ b/packages/hydrated_bloc/lib/src/hydrated_bloc.dart @@ -324,10 +324,8 @@ mixin HydratedMixin on BlocBase { _checkCycle(object); final map = {}; object.forEach((dynamic key, dynamic value) { - final castKey = key is String ? key : key?.toString(); - if (castKey != null) { - map[castKey] = _traverseWrite(value).value; - } + final castKey = key?.toString(); + if (castKey != null) map[castKey] = _traverseWrite(value).value; }); _removeSeen(object); return map; From 35b16ef203fc1ff5c77cc86b86e53eb9001dbca0 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:23:49 -0500 Subject: [PATCH 06/13] chore: minor updates --- .../hydrated_bloc/test/cubits/cubits.dart | 2 +- ...p_cubit.dart => season_palette_cubit.dart} | 27 +++++++++---------- packages/hydrated_bloc/test/e2e_test.dart | 6 ++--- .../test/hydrated_cubit_test.dart | 9 ++----- 4 files changed, 18 insertions(+), 26 deletions(-) rename packages/hydrated_bloc/test/cubits/{int_key_map_cubit.dart => season_palette_cubit.dart} (67%) diff --git a/packages/hydrated_bloc/test/cubits/cubits.dart b/packages/hydrated_bloc/test/cubits/cubits.dart index 8a1979aec2f..cf0d258054e 100644 --- a/packages/hydrated_bloc/test/cubits/cubits.dart +++ b/packages/hydrated_bloc/test/cubits/cubits.dart @@ -2,8 +2,8 @@ export 'bad_cubit.dart'; export 'cyclic_cubit.dart'; export 'freezed_cubit.dart'; export 'from_json_state_cubit.dart'; -export 'int_key_map_cubit.dart'; export 'json_serializable_cubit.dart'; export 'list_cubit.dart'; export 'manual_cubit.dart'; +export 'season_palette_cubit.dart'; export 'simple_cubit.dart'; diff --git a/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart similarity index 67% rename from packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart rename to packages/hydrated_bloc/test/cubits/season_palette_cubit.dart index 4357489808b..dfba075f15c 100644 --- a/packages/hydrated_bloc/test/cubits/int_key_map_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart @@ -1,11 +1,10 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:meta/meta.dart'; -/// Simulates the pattern produced by json_serializable -/// with `@JsonValue(int)` enums used as map keys. -/// See: https://github.com/felangel/bloc/issues/3983 -class IntKeyMapCubit extends HydratedCubit { - IntKeyMapCubit() : super(const Palette({})); +/// A cubit that has a state which uses int key values. +/// https://github.com/felangel/bloc/issues/3983 +class SeasonPaletteCubit extends HydratedCubit { + SeasonPaletteCubit() : super(const Palette({})); void update(Palette palette) => emit(palette); @@ -16,8 +15,6 @@ class IntKeyMapCubit extends HydratedCubit { Palette fromJson(Map json) => Palette.fromJson(json); } -/// An enum whose serialized form uses int values, -/// similar to `@JsonValue(int)` from json_annotation. @immutable class Season { const Season._(this.index, this.name); @@ -34,12 +31,14 @@ class Season { int toJson() => index; - static Season fromJson(int value) => - values.firstWhere((e) => e.index == value); + static Season fromJson(int value) { + return values.firstWhere((e) => e.index == value); + } @override - bool operator ==(Object other) => - identical(this, other) || other is Season && other.index == index; + bool operator ==(Object other) { + return identical(this, other) || other is Season && other.index == index; + } @override int get hashCode => index.hashCode; @@ -53,7 +52,7 @@ class Palette { const Palette(this.seasonColors); factory Palette.fromJson(Map json) { - final raw = json['seasonColors'] as Map? ?? {}; + final raw = json['season_colors'] as Map? ?? {}; return Palette( raw.map( (key, value) => MapEntry( @@ -66,11 +65,9 @@ class Palette { final Map seasonColors; - /// Produces a map with int keys (Season.index), matching the - /// json_serializable output for `@JsonValue(int)` enums. Map toJson() { return { - 'seasonColors': seasonColors.map( + 'season_colors': seasonColors.map( (key, value) => MapEntry(key.toJson(), value), ), }; diff --git a/packages/hydrated_bloc/test/e2e_test.dart b/packages/hydrated_bloc/test/e2e_test.dart index c6bdd6db04d..294cf7267b5 100644 --- a/packages/hydrated_bloc/test/e2e_test.dart +++ b/packages/hydrated_bloc/test/e2e_test.dart @@ -383,7 +383,7 @@ void main() { }); }); - group('IntKeyMapCubit', () { + group('SeasonPaletteCubit', () { test('persists and restores state with non-string map keys', () async { final palette = Palette({ Season.spring: 'green', @@ -391,11 +391,11 @@ void main() { Season.autumn: 'orange', Season.winter: 'white', }); - final cubit = IntKeyMapCubit(); + final cubit = SeasonPaletteCubit(); expect(cubit.state, const Palette({})); cubit.update(palette); await sleep(); - expect(IntKeyMapCubit().state, palette); + expect(SeasonPaletteCubit().state, palette); }); }); diff --git a/packages/hydrated_bloc/test/hydrated_cubit_test.dart b/packages/hydrated_bloc/test/hydrated_cubit_test.dart index 45798100275..fb7479159b9 100644 --- a/packages/hydrated_bloc/test/hydrated_cubit_test.dart +++ b/packages/hydrated_bloc/test/hydrated_cubit_test.dart @@ -414,7 +414,7 @@ void main() { group('MyIntKeyMapCubit', () { test('converts non-string (int) map keys to strings when persisting', () { final cubit = MyIntKeyMapCubit(); - const data = {0: 'spring', 1: 'summer', 2: 'autumn', 3: 'winter'}; + const data = {0: 'a', 1: 'b', 2: 'c'}; const change = Change( currentState: {}, nextState: data, @@ -422,12 +422,7 @@ void main() { cubit.onChange(change); verify( () => storage.write('MyIntKeyMapCubit', { - 'data': { - '0': 'spring', - '1': 'summer', - '2': 'autumn', - '3': 'winter', - }, + 'data': {'0': 'a', '1': 'b', '2': 'c'}, }), ).called(1); }); From fcf4acf47cd10cc52db8539b89f17acc6a8f7a4a Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:24:41 -0500 Subject: [PATCH 07/13] formatting --- .../test/cubits/season_palette_cubit.dart | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart index dfba075f15c..75c0344b040 100644 --- a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart @@ -15,38 +15,6 @@ class SeasonPaletteCubit extends HydratedCubit { Palette fromJson(Map json) => Palette.fromJson(json); } -@immutable -class Season { - const Season._(this.index, this.name); - - static const spring = Season._(0, 'spring'); - static const summer = Season._(1, 'summer'); - static const autumn = Season._(2, 'autumn'); - static const winter = Season._(3, 'winter'); - - static const values = [spring, summer, autumn, winter]; - - final int index; - final String name; - - int toJson() => index; - - static Season fromJson(int value) { - return values.firstWhere((e) => e.index == value); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || other is Season && other.index == index; - } - - @override - int get hashCode => index.hashCode; - - @override - String toString() => 'Season.$name'; -} - @immutable class Palette { const Palette(this.seasonColors); @@ -87,3 +55,35 @@ class Palette { @override int get hashCode => seasonColors.hashCode; } + +@immutable +class Season { + const Season._(this.index, this.name); + + static const spring = Season._(0, 'spring'); + static const summer = Season._(1, 'summer'); + static const autumn = Season._(2, 'autumn'); + static const winter = Season._(3, 'winter'); + + static const values = [spring, summer, autumn, winter]; + + final int index; + final String name; + + int toJson() => index; + + static Season fromJson(int value) { + return values.firstWhere((e) => e.index == value); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || other is Season && other.index == index; + } + + @override + int get hashCode => index.hashCode; + + @override + String toString() => 'Season.$name'; +} From 9fcec39c41b18c2e518eefc29e4078fdb5f341b6 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:26:05 -0500 Subject: [PATCH 08/13] rename --- .../test/cubits/season_palette_cubit.dart | 35 ++++++++++--------- packages/hydrated_bloc/test/e2e_test.dart | 4 +-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart index 75c0344b040..fb452519c94 100644 --- a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart @@ -3,25 +3,26 @@ import 'package:meta/meta.dart'; /// A cubit that has a state which uses int key values. /// https://github.com/felangel/bloc/issues/3983 -class SeasonPaletteCubit extends HydratedCubit { - SeasonPaletteCubit() : super(const Palette({})); +class SeasonPaletteCubit extends HydratedCubit { + SeasonPaletteCubit() : super(const SeasonPalette({})); - void update(Palette palette) => emit(palette); + void update(SeasonPalette palette) => emit(palette); @override - Map toJson(Palette state) => state.toJson(); + Map toJson(SeasonPalette state) => state.toJson(); @override - Palette fromJson(Map json) => Palette.fromJson(json); + SeasonPalette fromJson(Map json) => + SeasonPalette.fromJson(json); } @immutable -class Palette { - const Palette(this.seasonColors); +class SeasonPalette { + const SeasonPalette(this.colors); - factory Palette.fromJson(Map json) { - final raw = json['season_colors'] as Map? ?? {}; - return Palette( + factory SeasonPalette.fromJson(Map json) { + final raw = json['colors'] as Map? ?? {}; + return SeasonPalette( raw.map( (key, value) => MapEntry( Season.fromJson(int.parse(key)), @@ -31,11 +32,11 @@ class Palette { ); } - final Map seasonColors; + final Map colors; Map toJson() { return { - 'season_colors': seasonColors.map( + 'colors': colors.map( (key, value) => MapEntry(key.toJson(), value), ), }; @@ -44,16 +45,16 @@ class Palette { @override bool operator ==(Object other) { if (identical(this, other)) return true; - if (other is! Palette) return false; - if (seasonColors.length != other.seasonColors.length) return false; - for (final entry in seasonColors.entries) { - if (other.seasonColors[entry.key] != entry.value) return false; + if (other is! SeasonPalette) return false; + if (colors.length != other.colors.length) return false; + for (final entry in colors.entries) { + if (other.colors[entry.key] != entry.value) return false; } return true; } @override - int get hashCode => seasonColors.hashCode; + int get hashCode => colors.hashCode; } @immutable diff --git a/packages/hydrated_bloc/test/e2e_test.dart b/packages/hydrated_bloc/test/e2e_test.dart index 294cf7267b5..b87d68e4663 100644 --- a/packages/hydrated_bloc/test/e2e_test.dart +++ b/packages/hydrated_bloc/test/e2e_test.dart @@ -385,14 +385,14 @@ void main() { group('SeasonPaletteCubit', () { test('persists and restores state with non-string map keys', () async { - final palette = Palette({ + final palette = SeasonPalette({ Season.spring: 'green', Season.summer: 'yellow', Season.autumn: 'orange', Season.winter: 'white', }); final cubit = SeasonPaletteCubit(); - expect(cubit.state, const Palette({})); + expect(cubit.state, const SeasonPalette({})); cubit.update(palette); await sleep(); expect(SeasonPaletteCubit().state, palette); From 67462e92063411939249807481849141d7f82415 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:27:11 -0500 Subject: [PATCH 09/13] docs --- packages/hydrated_bloc/test/cubits/season_palette_cubit.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart index fb452519c94..c5eacbd08ed 100644 --- a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart @@ -1,7 +1,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:meta/meta.dart'; -/// A cubit that has a state which uses int key values. +/// A cubit that has a state which uses int key values in its serialized form. /// https://github.com/felangel/bloc/issues/3983 class SeasonPaletteCubit extends HydratedCubit { SeasonPaletteCubit() : super(const SeasonPalette({})); From aeaf956a98c57826f8c911bb59ccd9bebe951752 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:27:19 -0500 Subject: [PATCH 10/13] format --- packages/hydrated_bloc/test/cubits/season_palette_cubit.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart index c5eacbd08ed..a77d44bd8f0 100644 --- a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart @@ -12,8 +12,9 @@ class SeasonPaletteCubit extends HydratedCubit { Map toJson(SeasonPalette state) => state.toJson(); @override - SeasonPalette fromJson(Map json) => - SeasonPalette.fromJson(json); + SeasonPalette fromJson(Map json) { + return SeasonPalette.fromJson(json); + } } @immutable From 275f5a615570b816dbff62171679fdc4803890a7 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:28:25 -0500 Subject: [PATCH 11/13] naming --- packages/hydrated_bloc/test/cubits/season_palette_cubit.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart index a77d44bd8f0..971d198008e 100644 --- a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart @@ -22,9 +22,9 @@ class SeasonPalette { const SeasonPalette(this.colors); factory SeasonPalette.fromJson(Map json) { - final raw = json['colors'] as Map? ?? {}; + final deserialized = json['colors'] as Map? ?? {}; return SeasonPalette( - raw.map( + deserialized.map( (key, value) => MapEntry( Season.fromJson(int.parse(key)), value as String, From 213e2afeed1d86d7c9738b4711f5f50526a71d6d Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:31:26 -0500 Subject: [PATCH 12/13] chore: adjust test descriptions --- packages/hydrated_bloc/test/e2e_test.dart | 4 +++- packages/hydrated_bloc/test/hydrated_cubit_test.dart | 7 ++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/hydrated_bloc/test/e2e_test.dart b/packages/hydrated_bloc/test/e2e_test.dart index b87d68e4663..43a3917e7d4 100644 --- a/packages/hydrated_bloc/test/e2e_test.dart +++ b/packages/hydrated_bloc/test/e2e_test.dart @@ -384,7 +384,9 @@ void main() { }); group('SeasonPaletteCubit', () { - test('persists and restores state with non-string map keys', () async { + test( + 'persists and restores state ' + 'when serialized state uses non-string keys', () async { final palette = SeasonPalette({ Season.spring: 'green', Season.summer: 'yellow', diff --git a/packages/hydrated_bloc/test/hydrated_cubit_test.dart b/packages/hydrated_bloc/test/hydrated_cubit_test.dart index fb7479159b9..d9f770105e9 100644 --- a/packages/hydrated_bloc/test/hydrated_cubit_test.dart +++ b/packages/hydrated_bloc/test/hydrated_cubit_test.dart @@ -412,13 +412,10 @@ void main() { }); group('MyIntKeyMapCubit', () { - test('converts non-string (int) map keys to strings when persisting', () { + test('serializes non-string keys', () { final cubit = MyIntKeyMapCubit(); const data = {0: 'a', 1: 'b', 2: 'c'}; - const change = Change( - currentState: {}, - nextState: data, - ); + const change = Change(currentState: {}, nextState: data); cubit.onChange(change); verify( () => storage.write('MyIntKeyMapCubit', { From e15d4b8c38b6d880fd87816f3fb1577cfd7ea997 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sat, 14 Mar 2026 15:36:43 -0500 Subject: [PATCH 13/13] revert naming change --- packages/hydrated_bloc/test/cubits/season_palette_cubit.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart index 971d198008e..a77d44bd8f0 100644 --- a/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart +++ b/packages/hydrated_bloc/test/cubits/season_palette_cubit.dart @@ -22,9 +22,9 @@ class SeasonPalette { const SeasonPalette(this.colors); factory SeasonPalette.fromJson(Map json) { - final deserialized = json['colors'] as Map? ?? {}; + final raw = json['colors'] as Map? ?? {}; return SeasonPalette( - deserialized.map( + raw.map( (key, value) => MapEntry( Season.fromJson(int.parse(key)), value as String,