I spent a day and half racking my brain trying to figure out how to create a chained selector for my redux store to get IDs from a set of nested objects. In the docs and all the examples I’ve read, I know that Iterable.map()
returns an Iterable<T>
and requires .toList()
if you want to treat it as a list. Eg. as a return type List<int>
.
What I can’t figure out is why my selectors below function while the other does not. The only thing I can come up with is that it’s a nested list that I’m trying to flatten. I’ve seen dozens of examples with the same error where JSON was used in a map and the answer was to use .toList()
.
The only reason I care is because I’ve work with very large very complex redux stores which required multiple levels of chaining – usually split into sub-selectors.
//models
@JsonSerializable()
class Schedule {
/* the data returned from the API is a little confusing. Each scheduled item
* has a primary and secondary task, so each team is usually listed twice for each
* task. Once the JSON is consumed and converted into models, I try to get all
* of the items task IDs so I can get more information about those tasks.
* Having come from a JSX/JavaScript background learning redux, I'm familiar
* with that style of chaining. I've tried to find information about the
* "cascade" ( .. ) operator, but information is hard to come by aside form a
* brief description. And didn't make a difference when I tried to use it.
* The reason each team is listed twice for a schedule under primaryTask and
* secondaryTask is a schedule items may have 2 teams. 99.999% of the time
* it's a single team, however, the odd time a second team will be configured
* as only a subset of the team will be assigned to the secondary task. For
* the purpose of the "get all task IDs" selector, which team that task is
* assigned to doesn't matter whatsoever.
* Example API JSON response:
[
schedule: {
teams:[ {
primaryTask:{
teamName:String,
taskId:Integer
},//primary task
secondaryTask:{
teamName:String,
taskId:Integer
}//secondary task
} //team
,... ]//teams
}//schedule
]
*/
//the map index is either "primaryTask" or "secondaryTask".
//each "Schedule" item is for a single team.
final Map<String, Team> teams;
Schedule({
required this.teams,
});
factory Schedule.fromJson(Map<String, dynamic> json) => _$ScheduleFromJson(json);
Map<String, dynamic> toJson() => _$ScheduleToJson(this);
}
@JsonSerializable()
class Team {
final int id;
final String name;
Team({
required this.id,
required this.name,
});
factory Team.fromJson(Map<String, dynamic> json) => _$TeamFromJson(json);
Map<String, dynamic> toJson() => _$TeamToJson(this);
}
//redux selector that works
List<int> getAllScheduledTeamsTaskIds( store ){
return List<int>.from(
List<List<int>>.from(
store.state.schedule
.map( ( schedule ) => [
schedule.teams['primaryTask'].id as int,
schedule.teams['secondaryTask'].id as int ]
)
)
.expand((i)=>i)
);
}
//The first selector I got to work but I didn't like it.
List<int> getAllScheduledTeamsTaskIdsMergingTwoLists( store ){
// merge two lists composed of primaryTask IDs and secondaryTask IDs
return store.state.schedule
.map( ( schedule ) => schedule.teams['primaryTask'].id as int )
.toList()
+
store.state.schedule
.map( ( schedule ) => schedule.teams['secondaryTask'].id as int )
.toList()
}
//redux selector that doesn't work
List<int> getAllScheduledTeamsTaskIdsError( store ){
return store.state.schedule
.map( ( schedule ) => [
schedule.teams['primaryTask'].id as int,
schedule.teams['secondaryTask'].id as int ]
)
.toList()
.expand((ident)=>ident)
.toList();
//error message: type '(dynamic) => (dynamic)' is not a subtype of (dynamic) => Iterable(dynamic)
}
I’ve been reading docs and examples and source on GitHub and the behaviour I’m experiencing isn’t expected based on what I’ve read. However, I can’t find an example exactly like mine. I’m not sure what I’m missing but I’m sure there’s some nuance as to why toList()
isn’t working while List.from()
is. I’ve tried cast()
, reduce()
, and any method I could find in the docs for Iterable<T>
that might give me a single List<int>
from a list of nested objects which I could get into the form of List<List<int>>
.
2
Answers
As suggested in the comment above, ultimately I ditched the
map()
andexpand()
for a sync generator as follows.Apparently calling the
expand
method on adynamic
value causes the error. The result ofstore.state.schedule.map(...).toList()
is adynamic
(try assigning it to a variable). To prevent the error you can caststore.state.schedule
to anIterable
.Notice that I also removed the first
toList()
call since theexpand
method works onIterable
too.