Making 2048 Game in Flutter by using Explicit Animations — Part 4

Angjelko
6 min readJun 9, 2022

--

2048 Game in Flutter Logo

The source for the project is at Github:
https://github.com/angjelkom/flutter_2048

Saving State using Hive

In this part of the series we will see how to save the state of the game so that we don’t loose the progress when we exit the app, so that we can continue playing from where we left next time we start the game.

To save the the state locally we will use a known plugin called Hive. Hive is a fast database for storing key/value data.

Before we implement hive we need to add some code which would allow us to convert the data toJson and fromJson as that’s how we will store the data in database, in json format.

to add a toJson and fromJson functions we will use 3 popular plugins: json_annotation, json_serializable and build_runner which we have already installed at the beginning.
Of course for this type of project we could’ve have just written the functions ourself, but because these plugins are mostly used is flutter projects especially in large scale projects, I’ve decided to also use them here for those of you that find it hard how to use generators in Flutter in a simple example like this project.

Let’s start coding. Go ahead and open models/tile.dart and at top above the class definition for Tile add the following code:

JsonSerializable annotation in models/tile.dart

The JsonSerializable() annotation will allow json_serializable to generate to/from json code for the class bellow it, in this case the Tile class and the part ‘tile.g.dart’; means that the models/tile.dart will use the code from models/tile.g.dart. The tile.g.dart doesn’t exist yet and will be generated by json_serializable hence the “g” in the name (short for generated).

Next at the bottom of the class we will add the following code:

fromJson/toJson methods in models/tile.dart

Again we are getting errors because the tile.g.dart file is not yet generated and it is that file that contains the _$TileFromJson and _$TileToJson.

The complete models/tile.dart file looks like this:

models/tile.dart

So save the file and let’s generate the file. In terminal for this project run the following command:

flutter pub run build_runner build

Running build_runner build command.

Again for this type of projects you won’t probably need it and you can write the to/from json functions yourself.

Now if we look in tile.dart again the errors are gone and we have tile.g.dart file under the models folder.

Next let’s do the same for models/board.dart, open the file and again above the class definition add the following code:

JsonSerializable annotation in models/board.dart

So the code is almost same as for the tile.dart except for the explicitToJson parameter being set to true, this means that the class and all of the child classes (in this case the tile class) will be converted to json and from json when the respected functions are called, and we also have the anyMap parameter set to true in order to avoid a nasty error “_InternalLinkedHashMap<dynamic, dynamic> exception” being thrown from the fromJson function, so basically instead of casting the map parameters as Map<String, dynamic> it will use the Map.from method to construct a map from the given value.

And now let’s also add the to/from json functions at the bottom of the class:

fromJson/toJson methods in models/board.dart

The complete models/board.dart looks like this:

models/board.dart

Again rerun the build command we ran earlier and the board.g.dart file will be generated under models folder.

Next we will add TypeAdapter. The TypeAdapter will allow us to directly store and retrieve the data we want and the TypeAdapter will take care of converting it to/from json.

So under models create new file board_adapter.dart and add the following code:

models/board_adapter.dart

So when the data is being “read” we will use the fromJson function to construct a Board model from the json data, and when we “write” to the database we will use the toJson function to generate a json data from the Board model and write that to the database.

After this we need to “register” the adapter with Hive, so open the lib/main.dart file and above the runApp function add the following code:

Initialize Hive and register the custom BoardAdapter in main.dart

This makes sure the Hive is initialised and the BoardAdapter registered.
The whole file with the needed imports looks like this:

main.dart

Now let’s decide when we will write to the db and when we will retrieve it. The best place to load the data from db would be of course when the BoardManager is constructed, but to write to db we will do it when the app becomes inactive, that can happen either when user moves the app to background or tries to exit the app.

First let’s add the function that will write the state to the db.

Open managers/board.dart and after the handleKeyEvent function at the bottom of the BoardManager add the following function:

Save state method in managers/board.dart

And make sure the relevant imports are added:

Imports in managers/board.dart

So Hive has “boxes” which if you have any knowledge of databases Boxes are like the Tables in SQL, the Documents in MongoDB or the Collections in Firestore.

Now here we are storing the current state in a box called boardBox at index 0, that’s because we will always keep only one state in the db and we will always replace the state in db.
Also here we don’t need to call the toJson function of the Board model because thanks to the custom TypeAdapter we added earlier the adapter will take care of that automatically.

Next we will add the code that will call this function when the app becomes inactive.

To listen when the app becomes inactive we can use a WidgetsBindingObserver in order to listen to the Lifecycles of the App.

So open lib/game.dart and next to the TickerProviderStateMixin add the WidgetsBindingObserver separated with comma.

So this:

_GameState class in game.dart

becomes this:

Including WidgetsBindingObserver interface for _GameState class in game.dart

Next above the build method of the Game widget add the following code:

Add the observer when the Game state is initialized in game.dart

This adds an observer to the widget when the Game Widget state is initialised. And we also need to remove that same observer when the widget get’s disposed in order to avoid memory leaks. So on top of the dispose method remove the observer:

Remove the observer when the Game widget is disposed in game.dart

Lastly override the didChangeAppLifecycleState in the Game widget and call the save function of the BoardManager when the AppLifecycleState is inactive:

Override didChangeAppLifecycleState and save the game state when app becomes inactive in game.dart

Now that we can save the state, let’s add the code to load the state when the app is opened. So open managers/board.dart file and right bellow the BoardManager constructor add the following function:

Load state method in game.dart

This will open the boardBox box and retrieve the Board model stored at index 0 in the box.
And again here we don’t need to call the fromJson function of the Board model because thanks to the custom TypeAdapter we added earlier the adapter will take care of that automatically.

Lastly we need to call the load function from the BoardManager constructor so that when the app starts and the manager is initialised it will also load the state from db. So replace this line:

Start new game in BoardManager constructor in managers/board.dart

With this line:

Load game state in BoardManager constructor in managers/board.dart

And now we can build and run the game again.

Now when we try to exit the app by pressing the back button and then we open it again it will continue from where it left, and if we move the app to background and then close it from “recent apps” and then we open it again, it will still continue from where it was left, and also if you noticed the best score doesn’t get lost either.

And we are done! If you reached so far you are amazing! Thanks.

Lastly I highly recommend you read my last post for these tutorial where I show how I managed to find and solve performance issue while working on this project, how to use tools like DevTools Performance to find Junk Animation and how to solve them.

--

--