A simple pair game made with Flutter, a custom library, and the tunnel pattern.

Francesco Mineo
Flutter Community
Published in
6 min readJan 8, 2019

--

Coding a game, even if it is simple, it is a good exercise to improve our coding skills. In this article, I’m going to show you some strategies that I adopted to build this simple pair game with Flutter.

This is the final result:

Working with streams

I like streams, really. Since I used them from the first time I found them really fun to use so I ended up to use them on every my project. I created a tiny library to make working with streams and Stateless widgets simpler.

At first, I wanted something to reduce all the boilerplates to use the streams in the BLoC pattern. The idea was to embed a stream in a class with setters and getters:

class StreamedValueBase<T> {
final stream = BehaviorSubject<T>();
T _value;
/// timesUpdate shows how many times the got updated
int timesUpdated = 0;
/// Sink for the stream
Function(T) get inStream => stream.sink.add;
/// Stream getter
Stream<T> get outStream => stream.stream;
T get value => _value; set value(T value) => _value; refresh() {
inStream(_value);
}
dispose() {
print(‘ — — — — — Closing Stream — — — type: $T’);
stream.close();
}
}

Extending this class I created the StreamedValue class:

class StreamedValue<T> extends StreamedValueBase<T> {
set value(T value) {
if (_value != value) {
_value = value;
inStream(value);
timesUpdated++;
}
}
}

This essentially does a simple thing: every time a new value is set, this is compared to the oldest one and if it is different assigned to the variable and sent to stream.

Why this? So that when a new value is set, it automatically triggers the StreamerBuilder of the widget and it refreshes without the need to manually add the value to the sink of the stream.

So for example, instead of doing something like this:

counter += 1;
stream.sink.add(counter);

It becomes just:

counter.value += 1;

Passing data between two blocs

There are a lot of ways to pass data between two blocs, I adopted this strategy: in a outer bloc class (in this app it is called GameBloc), the one I use to handle the homepage of the app, I declare the blocs the I need, for example two blocs, blocA for page one and blocB for page two. Then I pass the blocs through DI to the respective pages.

Now the question is, how to pass data from blocA to blocB?

This is the answer:

class StreamedSender<T> {
StreamedValueBase<T> _receiver;
StreamedSender(); StreamedSender.setReceiver(StreamedValue<T> receiver) {
_receiver = receiver;
}
setReceiver(StreamedValue<T> receiver) {
_receiver = receiver;
}
send(T data) {
_receiver.value = data;
if (T is List) _receiver.refresh();
}
}

Tunnel pattern

In the blocA declare an object of type StreamedSender, in the main bloc (GameBloc in this app) set the reference to the stream of the blocB declared to receive the data by using the setReceiver method of the class StreamedSender, finally, send the data from blocA to blocB using the send method of the same class. It works like a tunnel from blocA to blocB without the needs to have the blocB injected in the blocA and viceversa (for this reason “Tunnel pattern”).

class GameBloc extends BlocBase {
final blocA = GamePageOneBloc();
final blocB = GamePageTwoBloc();
final page = StreamedValue<Widget>();
final welcomeMsg = StreamedValue<String>();
GameBloc() {
print(‘ — — — -GAME BLOC — — — — ‘);
blocA.tunnelSenderBox.setReceiver(blocB.tunnelReceiver);
}
dispose() {
print(‘ — — — -GAME BLOC DISPOSE — — — — ‘);
blocA.dispose();
blocB.dispose();
}
}

In the blocA (here box is a class created to handle the boxes of the game, that I send to the second bloc to show all the moves the user made):

// Sender
final tunnelSenderBox = StreamedSender<Box>();

to send data:

tunnelSenderBox.send(box);

In the blocB:

class GamePageTwoBloc extends BlocBase {  final items = StreamedCollection<Box>();  // Receiver
final tunnelReceiver = StreamedValue<Box>();
GamePageTwoBloc() {
print(‘ — — — -GAME PAGE TWO BLOC — — — — ‘);
// Listen for the items from the page one.
// When a box is received, it is added to the
// a collection and showed to the view.
tunnelReceiver.outStream.listen((box) {
if (box != null) {
items.addElement(box);
} else {
items.value.clear();
items.refresh();
}
});
}
dispose() {
print(‘ — — — -GAME PAGE TWO BLOC DISPOSE — — — — ‘);
tunnelReceiver.dispose();
items.dispose();
}
}

So what does it do? It just listens for the incoming boxes from the blocA (every time the user select a box this is sent to the blocB of the second page) and stores in a collection used to populate a gridview.

Streaming widgets instead of push and pop

To change the page you have various options, for example, the navigation stack with Navigator.push/Navigator.pop (PageView is another option), but what about “streaming” the widgets?

This is the strategy I adopted in this app:

Scaffold(
appBar: AppBar(
title: Text(‘Pair game’),
),
body: StreamedWidget<Widget>(
stream: bloc.page.outStream,
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
return snapshot.data;
},
noDataChild: _home()),
),

While “_home()” is just a method that returns the home widget when there are no events on the stream yet (you can find an explanation on the StreamedWidget on my last article: https://medium.com/@frmineoapps/custom-flutter-async-widgets-and-a-counter-using-bloc-pattern-9faa192731ea), bloc.page is a StreamedValue where I send the page I want to show, here is in the drawer:

ListTile(
title: Text(‘Home’),
onTap: () {
Navigator.pop(context);
bloc.page.value = _home();
},
),
ListTile(
title: Text(‘Game’),
onTap: () {
Navigator.pop(context);
bloc.page.value = GamePageOne(
bloc: bloc.blocA,
);
},
),
ListTile(
title: Text(‘Moves’),
onTap: () {
Navigator.pop(context);
bloc.page.value = GamePageTwo(
bloc: bloc.blocB,
);
},
),

So instead of pushing and popping the page I simply stream the widget of the page in the stream, in this way the menu button stays in place and can be accessed even in the new page. As you can see in the PageOne is injected the blocA (and not the blocB) but still, it is able to send data to the blocB.

Stateless widgets

In this app I used only Stateless widgets (apart the one used in the BlocProvider), this is made possible by using the streams and the StreamBuilder (here I use a customized version StreamedWidget but under the hood there is a StreamBuilder). I didn’t have the need to call SetState a single time even if the app is all but static!

Animations

The animations tools of Flutter are awesome and it is impossible to make something better. By the way, I found hard to integrate them in a more interactive way, then for this app I ended to create my own tools to handle the animations. The TimerObject and the AnimatedObject. The first is just a StreamedValue with an embedded timer, while the letter a StreamedValue with a TimerObject inside.

TimerObject :

AnimatedObject:

I created them to manage all the animations of the app. The AnimatedObject just needs the type of the value that needs to be changed over time, the initial value and the refresh time, then in the callback you change the value. Here is how I made the fade out animation of the boxes when the user taps two equals box:

Conclusion

Every time I start a new project, even if it is little like this one, there is something that I learn from Flutter (not only about it!), because it forces you to try to understand how certain things really work, and not only. It is like you have the possibility to make almost everything in a lot of ways, and even if it is can appear confusing at first, it is so much fun exploring new techniques!

In the next article, using my tiny lib, I’m going to show you how to make the widgets change over time using the StagedObject (I created it to show some widgets for a given time). In the meanwhile, you can find the complete source code of the pair game here: https://github.com/frideosapps/pair_game

--

--

Francesco Mineo
Flutter Community

Medical doctor, singer & music producer , software developer.