4.7. Notion of Animation, triggered by the code.

Most of the cases, Animation means use of an AnimationController, which when the latter is running, is listening to tick events and triggers a build of a Widget.

In our case, this would mean having to rebuild the GamePage (or part of it, if we optimize), which would not be a big issue but… how to define the animations to be played, which Tile(s) to animate and how?

This would become a nightmare.

The solution I came with is based on the use of the Overlay Widget (please refer to my previous articles on the Overlay for further details on this topic).

4.7.1. Advantage of using the Overlay

The Overlay is a Stack which already contains both HomePage and GamePage, as explained before. Nothing prevents us from adding any new Widget to it, in a temporarily manner.

This Widget will be:

By default, put on top of any other content of the Overlay;

Be built as soon as added to the Overlay.

This is it, we found a way of playing an animation, triggered by the code, by simply adding a Widget, only responsible for the animation, to the Overlay.

4.7.2. How to do it?

The following code shows how this can be achieved:

4.7.3. How to know when an animation completes?

In our game, we need to know when an animation completes.

To be notified, let’s simply add a callback method which will be invoked by the animation-responsible Widget, once the animation is complete.

The generic code skeleton of such animation-responsible Widget would then become the following:

One additional advantages of being notified that an animation is complete is that we may remove the OverlayEntry from the Overlay at the right moment, as follows:

4.7.4. Notion of Future

Something also very interesting for our game is the notion of Synchronization.

Most of the time, the game will have to await the completion of one (or several) animation(s) before going on with subsequent processing.

Should we only rely on the callback method to be invoked, this would made our code quite cumbersome and very difficult to handle, in case of multiple animations.

Therefore, we need to find out a way of awaiting the completion of all animations.

Here comes the notion of Future into play.

4.7.4.1. How to make a Future out of a synchronous source code?

Fortunately for us, Dart comes with a solution, called Completer, which produces a Future object and completes it later.

The following code shows how to implement this Completer in a method which will trigger an animation:

4.7.4.2. How to use this Future?

The following code sample shows how to wait for one or multiple animations to complete:

4.8. How to use the Streams?

We now have almost all the pieces to build the game but I would like to come back on the notion of Streams and the way of using them to display the:

Number of moves left before the end of the game;

Counter related to each objective;

Banner when the game is over.

4.8.1. Number of moves left

This is done by the ‘StreamMovesLeftCounter’ Widget. Its implementation is very basic:

We use a simple StreamBuilder which listens to the “movesLeftCount” stream, exposed by the GameBloc.

Nothing difficult. When the number of moves left is added to the “GameBloc.movesLeftCount” Sink, the value is emitted by the stream and intercepted by the StreamBuilder, which rebuilds the Text.

4.8.2. Objective Counters

To display and refresh the objective counters, it is a bit more tricky, since I only wanted to rebuild the counter of ONE PARTICULAR objective, and not all.

Since the display of an objective is done by one dedicated Widget and a game might have several objectives, I needed to find a way to let the Widget know that it needs to refresh itself or not…

As you will see in the code, each time we are removing a Tile or making a bomb explode, we emit an ObjectiveEvent which contains the:

type of Tile being involved;

the counter related to that particular Objective (if any)

This ObjectiveEvent is emitted by the GameBloc, via the sendObjectiveEvent Sink / outObjectiveEvents Stream.

4.8.2.1. The Objective BLoC

The solution I implemented resides on the use of a second BLoC, dedicated to the Objectives.

This BLoC is written as follows:

Explanation:

The BLoC consists in the interaction of 2 Streams:

The _objectivesController will be fed with all the ObjectiveEvent

* each time an ObjectiveEvent is emitted, it is compared to the type of Tile of interest;

* if the ObjectiveEvent corresponds to the type of Tile, the counter is input into the “_objectiveCounterController”.

* each time an ObjectiveEvent is emitted, it is compared to the type of Tile of interest; * if the ObjectiveEvent corresponds to the type of Tile, the counter is input into the “_objectiveCounterController”. The _objectiveCounterController is responsible for conveying the counter that will be used by the StreamObjectiveItem Widget to update the counter on the screen.

In order to have this working, we need the StreamObjectiveItem to tell the type of Tile it is interested in being notified about any changes to the counter…

4.8.2.2. The StreamObjectiveItem Widget

This Widget is responsible for displaying the counter related to a specific Objective, identified by its type of Tile.

Here are the interesting parts of that widget, worth an explanation:

Explanation:

When we are instantiating the Widget, we need to pass the type of Objective it will have to handle;

As soon as the context is available, we:

* retrieve the instance of the gameBloc and,

* create a new instance of the ObjectiveBloc, initializing it with the objective tile type;

* retrieve the instance of the gameBloc and, * create a new instance of the ObjectiveBloc, initializing it with the objective tile type; Then we create a pipe :

* we are listening to any emission of any ObjectiveEvent and,

* simply relaying it into our dedicated instance of the ObjectiveBloc.

: * we are listening to any emission of any ObjectiveEvent and, * simply relaying it into our dedicated instance of the ObjectiveBloc. As explained earlier, the dedicated instance of the ObjectiveBloc will only feed its _objectivesController when the type of the objective will match the type handled by this specific Widget.

A StreamBuilder is listening to any emitted value to rebuild the counter Text.

4.8.3. The Game Over banner

This is much simpler to implement…

In the GamePage, as soon as the context is available, we instantiate a StreamSubscription which will listen to the GameBloC.gameIsOver Stream.

As soon as a value is emitted by that Stream, the GamePage will call the _onGameOver(bool) method which will launch the animation to display the banner, as show in the following code extract: