Cat and Mice – Part 7: Releasing the App

In this blog series about Cat and Mice, I will discuss the whole process, from design to implementation, from publishing to artificial intelligence. Cat and Mice is a game that is played on a checkers board. One player plays the cat and has only one piece. The piece of this player starts at one side of the checker’s board. On the other side are four pieces of the mice. The goal for the cat is to reach the other side of the checker’s board. The goal for the mice is to prevent this. This article will describe how to perform the final steps!

About this blog series

This blog series will follow me along in the making of a simple game with Flutter. Not just the technical parts but also everything that happens around the implementation. I started off with a weekly series but after another Flutter project, I am back to finish the series.

  1. The Goal
  2. CI/CD
  3. The Architecture
  4. Implementing the Game
  5. Implementing the AI
  6. Really implementing the AI
  7. Adding sound

Adding a Splash Screen

With the following package, it is really easy to add a splash screen to the app. The package generates a native splash screen for both Android and IOs. It is easy to set up and easy to use. You can just add the dependency to the dev dependencies. Furthermore, you can configure the image and colors, and more. For this application, I just add the image and the color.

dev_dependencies:
  flutter_native_splash: ^1.3.3

flutter_native_splash:
  color: "#4CAF50"
  branding: assets/images/splash.png

Now, we can run the following command to generate the splash screens.

flutter pub run flutter_native_splash:create

Automated Screenshots

One of the steps I hate the most about creating mobile applications is the need for screenshots. For example, in the Play Store, you currently need four screenshots for the mobile version and two screenshots for two different tablet sizes. Luckily it is possible to generate them almost automatically. Most of these steps are based on this blog post. I start by adding a new integration test. This integration test start the mobile application

void main() {
  final IntegrationTestWidgetsFlutterBinding binding = IntegrationTestWidgetsFlutterBinding();

  testWidgets('Main menu', (WidgetTester tester) async {
    await tester.pumpWidget(ProviderScope(child: CatVsMice()));
    await tester.pumpAndSettle();
    await binding.takeScreenshot('menu');
  });
}

Since the application uses Riverpod, this also means we can override the state. This is especially useful in the generation of the screenshot of the game state. The starting state looks pretty boring, and moving all pieces on each device to the same state is a lot of work. Luckily, the override scopes allows us to override the positions of the checkers.

void main() {
  final IntegrationTestWidgetsFlutterBinding binding = IntegrationTestWidgetsFlutterBinding();

  testWidgets('Main menu', (WidgetTester tester) async {
    await tester.pumpWidget(ProviderScope(child: CatVsMice()));
    await tester.pumpAndSettle();
    await binding.takeScreenshot('menu');
  });

  testWidgets('Game', (WidgetTester tester) async {
    List<Checker> checkersForScreenshot = [
      Checker(1, PlayerType.MICE, Coordinate(0, 2)),
      Checker(2, PlayerType.MICE, Coordinate(3, 3)),
      Checker(3, PlayerType.MICE, Coordinate(5, 3)),
      Checker(4, PlayerType.MICE, Coordinate(6, 2)),
      Checker(5, PlayerType.CAT, Coordinate(4, 4))
    ];
    await tester.pumpWidget(MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: ProviderScope(
        overrides: [checkers.overrideWithValue(checkersForScreenshot)],
        child: BoardPage(
          BoardPageInput(null),
        ),
      ),
    ));

    await tester.pumpAndSettle();
    await binding.takeScreenshot('game');
  });
}

As stated in this blog post, you can override the location of the screenshots by adding a driver file.

Future<void> main() async {
  try {
    await integrationDriver(
      onScreenshot: (String screenshotName, List<int> screenshotBytes) async {
        final File image = await File('screenshots/tmp/$screenshotName.png').create(recursive: true);
        image.writeAsBytesSync(screenshotBytes);
        return true;
      },
    );
  } catch (e) {
    print('Error occured: $e');
  }
}

For now, I start the emulators manually. With the following command, I can pick the device ids. These device ids are used to run the integration tests.

flutter devices

With the following command, the integration tests are started. After each different device, the screenshots are moved to the corresponding folders.

flutter drive --driver=test_driver/integration_test.dart --target=integration_test/screenshot_test.dart -d "emulator-5554"
mv screenshots/tmp screenshots/tablet_7_inch
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/screenshot_test.dart -d "emulator-5556"
mv screenshots/tmp screenshots/phone
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/screenshot_test.dart -d "emulator-5558"
mv screenshots/tmp screenshots/tablet_10_inch

Now with these screenshots, I can create an initial release on the play store. The CI/CD blog post describes the steps to generate the app bundle. When the application is approved, I will share the link here. As always the code is available on Github

Leave a Reply