Search code examples
androidflutterasync-awaitheavy-computation

Flutter: How can I carry out resource intense background calculations without locking up the phone?


I have a puzzle game app. (https://play.google.com/store/apps/details?id=com.karolinadart.blackbox)

When the player thinks they have solved the puzzle, they press the "This is my final answer" button, at which point the score is calculated. A new screen is pushed, displaying the score.

But the score calculation is a rather heavy computation which can take several seconds, depending on the complexity of the particular setup. Therefore, I've made the score counting function async and try to show a spinner while waiting for the computation to finish... but this doesn't work! I've tried starting the spinner a bit before the computation just to see if it starts... and it does, but the moment the computation starts, the spinner freezes.

How should I program it so that the heavy computation can go on in the background, while leaving the phone free to do other things, such as showing a spinner, or even continue with other tasks in the game?


These are the main features of the result displaying code:

import 'package:intl/intl.dart';
import 'package:blackbox/my_firebase_labels.dart';
import 'package:blackbox/units/small_widgets.dart';
import 'package:blackbox/firestore_lables.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:blackbox/board_grid.dart';
import 'package:blackbox/play.dart';
import 'package:blackbox/constants.dart';
import 'package:blackbox/atom_n_beam.dart';
import 'package:collection/collection.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:blackbox/game_hub_updates.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'package:provider/provider.dart';

class SentResultsScreen extends StatefulWidget {
  SentResultsScreen({@required this.setupData, @required this.resultPlayerId});
  final Map<String, dynamic> setupData;
  final String resultPlayerId;

  @override
  _SentResultsScreenState createState() => _SentResultsScreenState(setupData, resultPlayerId);
}

class _SentResultsScreenState extends State<SentResultsScreen> {
  _SentResultsScreenState(this.setupData, this.resultPlayerId);
  final Map<String, dynamic> setupData;
  final String resultPlayerId;
  FirebaseFirestore firestoreObject = FirebaseFirestore.instance;
  Play thisGame;  // My own class, containing all the logic of the game
  bool resultsReady = false;
  String errorMsg = '';
  int beamScore;
  int atomScore;
  int totalScore;
  bool awaitingData = false;
  List<dynamic> alternativeSolutions;
  bool showSpinner = true;

  @override
  void initState() {
    super.initState();
    getGameData();
  }

  void getGameData() async {
    await Future.delayed(Duration(milliseconds: 500));  // The spinner spins for this duration, then freezes
    Map<String, dynamic> setupData = widget.setupData;

    if (setupData == null) {
      setState(() {
        errorMsg = 'Document contains no data';
        showSpinner = false;
      });
    } else {
      thisGame = Play(setupData);
      alternativeSolutions = await thisGame.getScore();  // This is the heavy calculation!
    }
       setState(() {
         totalScore = thisGame.atomScore + thisGame.beamScore;
         resultsReady = true;
         showSpinner = false;
       });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('blackbox')),
      body: ModalProgressHUD(
        inAsyncCall: showSpinner,
        child: Column(
          children: <Widget>[
            Expanded(
              child: FittedBox(
                fit: BoxFit.scaleDown,
                child: Column(
                  children: [
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text('My score'),
                        Text(
                          'beam score:  ${resultsReady ? '${thisGame.beamScore}' : '...'}',
                        ),
                        Text(
                          'atom penalty:  ${resultsReady ? '${thisGame.atomScore} ' : '...'}',
                        ),
                        Text(
                          'total: $totalScore'  // Will be null if results are not ready
                        ),
                      ],
                    ),
                  ),
                ),
                alternativeSolutions != null
                   ? Text(
                      'Multiple solutions exist!',
                    )
                   : SizedBox(),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}


Solution

  • When you start an asynchronous method, it will be computed in the same thread as the UI. That's the reason for your problem. It can be solved using Isolates. They correspond to e.g. threads in Java. For more information you can watch the video from Flutter for Isolates: https://youtu.be/vl_AaCgudcY