Building a Flutter Slide Puzzle With Minimal Code

2 years ago   •   14 min read

By Souvik Biswas
Table of contents

Recently the Flutter team at Google has announced a challenge called the Flutter Puzzle Hack for a chance to win one of over $50,000 worth of prizes. To be eligible for a prize, you have to either build a solvable slide puzzle from scratch or use their sample project as the starting point and use your creativity to design an innovative version of it.

You need coding skills to participate in this challenge, right? — Not really, and this is why you are here.

In this article, we’ll demonstrate how to build a working version of the Flutter Puzzle from scratch with very minimal coding using FlutterFlow. The only code that you need to write is for the logic of the puzzle (that code is included in this post 🙂).

The final puzzle, that you'll be building throughout this article, will look like this:‌

You can try out the puzzle by going to this link.

What’s FlutterFlow?

FlutterFlow is a low-code builder for native mobile applications

FlutterFlow is a low-code builder for native mobile applications (Android & iOS). You can the intuitive drag and drop visual builder to create your apps’ user interface but it offers a lot more:

  • Easy integration with Firebase, Stripe, Algolia, GitHub, etc.
  • Over 50 templates to start with
  • Define Custom Functions, Custom Actions, and Custom Widgets
  • AI generated code powered by OpenAI
  • One click deployment using Codemagic

Behind the scenes, code is generated using the open source framework called Flutter powered by Google, so the app you build is totally eligible for the Flutter Puzzle Hack challenge.

Getting started

To start building the puzzle using FlutterFlow, you need to create a free account by going here. If you already have an account, just login.

Once you successfully login to your account, you will be navigated to the FlutterFlow Dashboard page.

Follow the steps below to create a new project:

  1. Click “+ Create New”.
  2. Enter a Project Name, for example — “SlidePuzzle”.
  3. Under Blank App, click “+ Create New”. You would also have an option to choose among the variety of templates offered by FlutterFlow, these cover most of the common use cases. But as this puzzle is a unique project you need to start from scratch.
  4. In Project Setup Step 1, you don't need to change anything just click Next Step.
  5. In Project Setup Step 2, set an Initial Page. Enter the page name, for example — “PuzzlePage”, and click “+ Create New” under Blank Page.
  6. Click Start Building.

This will take you to the project editor (or, the UI Builder) where you can access the various widgets and customize your app by modifying their properties.

Building the puzzle UI

The UI Builder of FlutterFlow makes it really simple to create the user interface of any app by dragging and dropping widgets (UI elements) onto the canvas.

Canvas is the area where you would initially see “Empty Screen” written. The widgets can be found inside the Widget Panel section (present on the left) under the Widgets tab.

Let’s start by building a basic layout for an app.

  1. Drag and drop an AppBar widget onto the canvas.
  2. Go to the Properties Panel (present on the right), customize the AppBar properties. For example, set the Background Color to PrimaryBG.
  3. Add a Text widget as the title inside the AppBar.
  4. Customize the text and properties to make it attractive. For example, change the text to “Slide Puzzle” and select Text Color as Primary.
  5. Set the Background Color of the page (Scaffold) to PrimaryBG as well.

It’s time to build the main puzzle layout, follow the steps below:

  1. Start by placing a Column widget on the canvas.
  2. Add a Row widget inside the Column, place two Text widgets side-by-side. One them will display the text “Moves:” and the other will show the number of moves (it’s hardcoded here but later it will be set from a variable).
  3. Add a Container widget to the Column and place it below the Row. Set its width to 80%, height to 50%, and background color as PrimaryBG.
  4. Drag and drop a GridView widget inside the Container.
  5. Add a Button widget to the GridView.
The above steps are just to give you a basic idea about how to build the puzzle layout. Feel free to customize and modify the properties as per your requirement.

Initial puzzle layout is complete but does it look like a puzzle at all? — No, it’s because we haven’t generated the rest of the tiles yet. By tile here is referred to the Button widget that you just added.

FlutterFlow allows you to easily generate dynamic children of the GridView from a variable. Before that, you need to create a variable, and we’ll be defining it as a Local State variable because this will actually contain the list integers to be placed on the puzzle board.

Defining Local State variables

The Local State variables can be stored inside the app and are accessible from all the pages. Though in the case of the puzzle, you won’t need to access the variables from different pages as it contains just a single page but storing the variables like this helps in easily updating their states (or values).

Define the following Local State variables:

  • boardNumbers: to store the list of integers to be displayed inside the board tiles
  • moves: to track the number of tile moves inside the puzzle
  • isStarted: to track whether the puzzle game has started

Follow the steps below to add the variables:

  1. Navigate to the Local State page from the Navigation Menu (left-most menu)
  2. Click + Add State Variable.
  3. In the Add Field dialog, enter the field name and the type of variable (example, Integer).
  4. If it’s of list type then enable List using the toggle.
  5. Click Create.

Keep the Persisted toggle disabled as you don’t need to persist the state of any of the variables between app restarts.

Create these variables with their respective data types and default values, following table has all the types listed:

Field Name Data Type List Persisted Default Value
boardNumbers Integer true false -
moves Integer false false 0
isStarted Boolean false false false

Initializing the puzzle

The boardNumbers variable doesn’t have any default value, so it should be initialized with a list of integers before displaying them inside the GridView.

It’s time to do some coding (very minimal code), you need to define a Custom Action to shuffle and return a list of integers.

  1. Navigate to the Custom Code page from the Navigation Menu.
  2. Click + Create.
  3. Enter the Action Name as shuffleBoard.
  4. Enable the Return Value toggle, set the Data Type to Integer and Is List to True.
  5. Click View Boilerplate Code, then select Copy to Editor.
  6. Add the following code for shuffling the list of integers and returning them:
    // Add this import statement
    import 'dart:math' as math; 
    
    Future<List<int>> shuffleBoard() async {
      // Add the following code to the function
      List<int> numbersList = [0, 1, 2, 3, 4, 5, 6, 7, 8];
      numbersList.shuffle();
      return numbersList;
    }
    
  7. Click on Check Errors, select Compile.
  8. Wait for the compiling to complete. If there are no errors, click Save.

Now, add a new Button widget to start the game by calling the shuffleBoard Action and setting the initial values to the local state variables inside the onTap gesture.

  1. Drag and drop a Button widget, change the text to “Start Game”.
  2. Select the Start Game button, go to the Actions tab from the Properties panel (the right menu).
  3. Click + Add Action button.
  4. Choose the gesture from the dropdown as On Tap.
  5. First action:
    1. Select the Action Type as Custom Action.
    2. Choose Custom Action Name as shuffleBoard.
    3. Enter the Output Variable Name as shuffledNumList.
  6. Second Action:
    1. Select the Action Type as Update Local State.
    2. Set Select field to update as boardNumbers.
    3. Select Update Type as Set Value.
    4. Select the Value Source as From Variable.
    5. Choose Source as Action Outputs.
    6. Select shuffledNumList under Available Options.
  7. Third Action:
    1. Select the Action Type as Update Local State.
    2. Set Select field to update as isStarted.
    3. Select Update Type as Set Value.
    4. Select the Value Source as Specific Value.
    5. Choose Value as True.

Generating tiles

The tiles (Buttons with numbers) can be generated dynamically inside the GridView by using the boardNumbers local state variable.

  1. Select the GridView widget.
  2. Go to Generate Dynamic Children tab present inside the Properties panel.
  3. Enter the Variable Name as currNumList.
  4. Select the Source as Local State.
  5. Under Available Options, choose boardNumbers.
  6. Click Save, then click Ok inside the dialog.

To display the numbers in the tiles from the list variable:

  1. Select the Button widget.
  2. Under Button Text, click Set from Variable.
  3. Select the Source as currNumList item (Integer).
  4. Keep the Number Format Options as No Formatting.
  5. Click Save.

Currently the boardNumbers variable contains "0" as well but it shouldn't be visible on the board, you need to hide the 0th title so that it looks like an empty area there.

There's a simple trick to hide the tile containing "0":

  1. Select the Button (tile).
  2. In the Properties Menu, expand the Visibility section.
  3. Set the source as Condition.
  4. Select the First Value Source as currNumList item.
  5. Change the condition type to Not Equals To.
  6. Set the Second Value Source as Specific Value.
  7. Enter the Value as "0".
  8. Click Save.

Adding empty list widget

The boardNumbers variable would be empty initially, it would only the initialized when the Start Game button is tapped. So, initially the GridView would have nothing to display. You can add a Component to be displayed while the GridView has an empty list.

Let's add a component that would look similar to the puzzle but with tiles in the correct order (solved state) and when the Start Game button is clicked the tiles would be shuffled.

To get the list of integers in the solved state you need to define a simple Custom Function.

  1. Go to the Custom Code page from the Navigation Menu.
  2. Select the Custom Functions tab.
  3. Click + Create.
  4. Enter the function name as initBoard.
  5. Set the return Data Type as Integer and set Is List to True.
  6. Go to the Code tab, add the following inside the function:
    List<int> numbersList = [1, 2, 3, 4, 5, 6, 7, 8];
    return numbersList;
    
  7. Click Check Errors. If there are no errors, then click Save.

Create a Component by following the steps below:

  1. Go to Select Page or Component from the Navigation Menu.
  2. Select the Components tab.
  3. Click + Create New.
  4. Enter the name of the component as InitialGridView and click + Create New under Blank Component.
  5. Add a Container widget with width and height set to 100%.
  6. Drag and drop a GridView widget and add a Button inside it.
  7. Select the GridView and go to the Generate Dynamic Children tab present inside the Properties panel.
  8. Enter the Variable Name as initNumList.
  9. Select Source as Custom Function.
  10. Under Function Name, select initBoard.
  11. Click Save, then click Ok inside the dialog.
  12. Select a Button, click Set From Variable under Button Text.
  13. Select the Source as initNumList item, click Save.

Set the component inside the GridView of PuzzlePage when the list is empty:

  1. Select the GridView widget, expand the Empty List Widget (inside Properties panel).
  2. Check the Show Empty List Widget checkbox.
  3. Select the Widget Type as Component.
  4. Choose InitialGridView as the Component Name.

Puzzle logic

This is the most important piece of code that you'll be adding to the puzzle, it estimates whether a tile can move and to which position it can move by finding out the empty area.

We won't go into the details of how this logic works, you can simply define the following as a Custom Action and proceed to the next step. This action would require three parameters to be passed and its return type would be JSON.

Follow the steps below:

  1. Navigate to the Custom Code page.
  2. Go to Custom Actions tab.
  3. Click + Create.
  4. Enter the action name as onClick.
  5. Enable the Return Value toggle, set the Data Type as JSON.
  6. Define the following Arguments, by clicking on + Add Parameter:
    • Argument 1: list, Data Type: Integer, Is List: True.
    • Argument 2: index, Data Type: Integer, Is List: False.
    • Argument 3: moves, Data Type: Integer, Is List: False.
  7. Click View Boilerplate Code, select Copy to Editor.
  8. Download the following code and copy-paste it inside the function:
  1. Click Check Errors, then select Compile inside the dialog.
  2. If no errors are found, click Save.

In the above code, the updated list of numbers and the moves are returned in JSON format.

To make this logic work, you'll need to pass the index of the tapped tile. But from the grid tiles, only the number can be retrieved not its index inside the list. To get the index you need to add one more simple Custom Function:

  • Function Name: getIndex
  • Return Type: Integer (Is List: False)
  • Function Arguments:
    • board --> Data Type: Integer, Is List: True
    • value --> Data Type: Integer, Is List: False
  • Code:
    import 'dart:math' as math;
    
    int getIndex(
      List<int> board,
      int value,
    ) {
      return board.indexOf(value);
    }
    

Trigger action on tile tap

Once you have the onClick action defined, you are ready to set them as onTap action of the Button widget inside the GridView. The following actions should be defined on the Button in order:

  1. Call the onClick Custom Action by providing the required variables which will return the updated list and the moves in a JSON format.
  2. Use the Update Local State action to update the list of board numbers from the JSON.
  3. Use the Update Local State action to update the number of moves from the JSON.

Follow the steps below to define these actions:

  1. Select the Button (tile) inside GridVew.
  2. Go to the Actions tab from the Properties panel (the right menu).
  3. Click + Add Action button.
  4. Choose the gesture from the dropdown as On Tap.
  5. First action:
    1. Select the Action Type as Custom Action.
    2. Choose Custom Action Name as onClick.
    3. Pass the list argument from: From Variable > Local State > boardNumbers.
    4. Pass the index argument from: From Variable > Custom Function > getIndex.
    5. Set the index function argument board from From Variable > Local State > boardNumbers, and value from From Variable > currNumList item.
    6. Pass the moves argument from: From Variable > Local State > moves.
    7. Enter the Output Variable Name as listMovesMap.
  6. Second Action:
    1. Select the Action Type as Update Local State.
    2. Set Select field to update as boardNumbers.
    3. Select Update Type as Set Value.
    4. Select the Value Source as From Variable.
    5. Choose Source as Action Outputs.
    6. Select listMovesMap under Available Options.
    7. Select JSON Path and define the path as $.list.
  7. Third Action:
    1. Select the Action Type as Update Local State.
    2. Set Select field to update as moves.
    3. Select Update Type as Set Value.
    4. Select the Value Source as From Variable.
    5. Choose Source as Action Outputs.
    6. Select listMovesMap under Available Options.
    7. Select JSON Path and define the path as $.moves.

Show solved dialog

Once a user completes solving the puzzle, you should show something on the UI to indicate that.

Add a new Custom Action called isSolved with the following code:

import 'dart:math' as math;
import 'package:flutter/foundation.dart';

bool isSolved(List<int> board) {
  List<int> solvedList = [1, 2, 3, 4, 5, 6, 7, 8, 0];
  return listEquals(solvedList, board);
}

Then, you need to add a few more actions to the Button (or tile), follow the steps below:

  1. Select the Button (tile) inside GridVew.
  2. Go to the Actions tab from the Properties panel (the right menu).
  3. Click + Add Action button.
  4. Choose the Type of Action as On Tap.
  5. First action (call the custom action):
    1. Select action as Custom Action.
    2. Choose Custom Action Name as isSolved.
    3. Pass the board argument from: From Variable > Local State > boardNumbers.
    4. Enter the Output Variable Name as isSolved.
  6. Second Action (stop the timer):
    1. Select action as Update Local State.
    2. Set Select field to update as isStarted.
    3. Select Update Type as Set Value.
    4. Select the Value Source as Specific Value.
    5. Select Value as False.
    6. Enable the Set Conditional Execution toggle.
    7. Select the Source as Action Outputs.
    8. Under Available Options, choose isSolved.
  7. Third Action (show dialog box):
    1. Select the action as Alert Dialog.
    2. Select Alert Dialog Type as Informational Dialog.
    3. Enter Title as "Solved".
    4. Enter Message as "You have successfully solved the puzzle!".
    5. Enter Dismiss Text as "OK".
    6. Enable the Set Conditional Execution toggle.
    7. Select the Source as Action Outputs.
    8. Under Available Options, choose isSolved.

The second and third actions will only be triggered if the isSolved custom action output is True.

As the puzzle is solved, the dialog will be displayed like this:

Once you have added this final set of actions, your puzzle is ready!

Trying out the app

You can use the Run button present on the Tool Bar (present on the top) of FlutterFlow to test out the puzzle on your web browser.

Typically it requires around 2-4 minutes to build and start the app using Run mode.

The Flutter Slide Puzzle app in action:

Try out this version of the Slide Puzzle by going to this link.

Tips and ideas for your project

Some ideas that you can incorporate inside your puzzle on top of the sample that we created in this article are as follows:

Stopwatch

Notice in the above demo, a stopwatch starts as soon as the Start Game button is tapped. This is one of the basic features that you can incorporate inside your puzzle. Though FlutterFlow doesn't provide a Timer widget, it won't stop you from adding a Custom Widget 😉.

Create a Custom Widget by going to the Custom Code page. Enter the widget name as TimerWidget and define the following parameters:

  • fontSize: Double (Is List: False)
  • color: Color (Is List: False)

Then download the following code and upload it there:

Adaptive layout

As this Flutter challenge is focused on the Web platform, responsive layout is one of the major criteria to get an edge over the other competitors. In FlutterFlow, you can select any widget and use the Responsive Visibility section (present inside the Properties Panel) to set on which type of display that particular widget should be visible. With the help of this, you can use different dimensions of widgets depending upon the screen sizes.

Adding dark mode support

Dark mode support can be easily added to the Puzzle by going to the Settings & Integrations > Theme > Theme Colors, enable Dark Mode Theme using the toggle, and choose the appropriate colors to be used in the dark mode.

Click the Preview Themes button to see how the light and the dark mode themes  look when applied on your PuzzlePage:

Wrapping up

FlutterFlow should help you to build your version of the Slide Puzzle even if you don't know how to write Flutter code at all.

Finally, to submit your project to this challenge:

  • You can go to the challenge page to start your submission.
  • You need to provide a link to your code during submission. Using FlutterFlow you can directly upload your code to GitHub. Just share that link, don't forget to make your repository public.
  • Record a video (less than 3 minutes) of the puzzle by starting it in the Run mode of FlutterFlow. Upload it to YouTube, Vimeo, Facebook Video, etc., and provide the link during submission.
  • Lastly, you should host your Flutter web app and provide the link. You can use Firebase Hosting or GitHub Pages to host the website.

Share your puzzle on Twitter with the #FlutterPuzzleHack and don't forget to tag FlutterFlow (@flutterflow). We can't wait to see your creations!

Spread the word