Dart is a single threaded language, but it comes with a handy compute function to spawn isolates. In a nutshell, the compute function is useful for doing extra work on a different "thread"--it's actually an isolate--so your flutter app does not experience "jank". Jank occurs when the UI doesn’t render smoothly. For example, every so often, a frame takes 10 times longer to render, so it gets dropped, and the animation visibly jerks.
To use compute, you need a function and data. compute
will return a future of whatever your function returns.
final Future<R> result = compute(someFunction, someData)
In my app, I had to parse about 200 tweets that were scored with sentiment analysis. Each tweet has a message
and created
property, but the sentiment
property had some intense parsing. So when I created a new TweetDto
, I also had to create a new SentimentDto
.
class TweetDto {
final int id;
final String message;
final String created;
final SentimentDto sentiment;
TweetDto({
@required this.id,
@required this.message,
@required this.created,
@required this.sentiment,
});
factory TweetDto.fromJson(Map<String, dynamic> json) {
return TweetDto(
id: json['id'],
message: json['message'],
created: json['created'],
sentiment: SentimentDto.fromJson(json['sentiment']),
);
}
}
class SentimentDto {
final List<_SentimentSentence> sentences;
final _SentimentSummary documentSentiment;
final String language;
SentimentDto({
@required this.sentences,
@required this.documentSentiment,
@required this.language,
});
factory SentimentDto.fromJson(Map<String, dynamic> json) {
final List<dynamic> sentencesRef = json['sentences'];
final List<_SentimentSentence> sentences = List<_SentimentSentence>.from(
sentencesRef.map(
(s) => _SentimentSentence(
text: _SentimentText(
content: s['text']['content'],
beginOffset: (s['text']['beginOffset']).toDouble(),
),
sentiment: _SentimentSummary(
score: (s['sentiment']['score']).toDouble(),
magnitude: (s['sentiment']['magnitude']).toDouble(),
),
),
),
);
final documentSentiment = _SentimentSummary(
score: (json['documentSentiment']['score']).toDouble(),
magnitude: (json['documentSentiment']['magnitude']).toDouble(),
);
final language = json['language'];
return SentimentDto(
sentences: sentences,
documentSentiment: documentSentiment,
language: language,
);
}
}
class _SentimentSentence {
final _SentimentText text;
final _SentimentSummary sentiment;
_SentimentSentence({
@required this.text,
@required this.sentiment,
}) : assert(text != null),
assert(sentiment != null);
}
class _SentimentText {
final String content;
final double beginOffset;
_SentimentText({
@required this.content,
@required this.beginOffset,
});
}
class _SentimentSummary {
final double magnitude;
final double score;
_SentimentSummary({
@required this.magnitude,
@required this.score,
}) : assert(magnitude != null),
assert(score != null);
}
I experienced some performance when I was parsing about 200 tweets each with their own sentiment score, so I looked to compute all of this json parsing with Flutter's compute
. In my TweetBloc, I had a function called loadTweets
which was responsible for fetching 200 scored tweets. You'll see that I switch to new Observable with Observable.fromFuture(...)
in order to keep this operation reactive.
...
void loadTweets() {
_authService
.get(ApiConsts.getTweets(200))
.switchMap((signal) => Observable.fromFuture(
compute(TweetBloc._decodeAndParseTweets, signal.body)))
.take(1)
.listen(
(List<TweetDto> signal) {
_tweets.add(signal);
},
);
}
..
Because compute takes a function that performs the computation, I created a static function decodeAndParseTweets
which parses all of my json.
void loadTweets() {
_authService
.get(ApiConsts.getTweets(200))
.switchMap((signal) => Observable.fromFuture(
compute(TweetBloc._decodeAndParseTweets, signal.body)))
.take(1)
.listen(
(List<TweetDto> signal) {
_tweets.add(signal);
},
);
}
static List<TweetDto> _decodeAndParseTweets(String jsonBody) {
final List<dynamic> decodedJson = jsonDecode(jsonBody);
final List<TweetDto> tweets =
decodedJson.map((t) => TweetDto.fromJson(t)).toList();
return tweets;
}
Flutter's compute
helped prevent jank in my flutter app by offloading intense parsing of data onto a new isolate. Consider using it in the future when you have any intense data processing.
Hi, is there any way to have compute calls not stack? Let's say I have one thing calling compute, and if it is called again, how do I cancel the previous compute operation to only have one spawned process?