Fifty games in one Flutter workspace. This page covers everything from opening the project to uploading a build, and it describes what the code does rather than what a template usually promises.
Prerequisites start here if you are new
You need a working Flutter install and, for store builds, the platform toolchain for whichever store you are shipping to.
| What | Version | Notes |
|---|---|---|
| Flutter | 3.44 or newer | Built and tested on 3.44.7 stable. |
| Dart | ^3.12.2 | Comes with Flutter; the constraint is in pubspec.yaml. |
| Android | Studio + SDK 36 | compileSdk 36, targetSdk 36, minSdk 24 (Android 7.0). |
| iOS | Xcode 15+ | Deployment target 13.0. macOS only. |
| Python 3 | 3.9+ | Only for regenerating the app icons. |
Check your setup before anything else:
flutter doctor
Every line that matters for this project should be a tick. A warning about Chrome or Linux desktop is harmless — Sweetbox targets iOS and Android.
Getting started
The package unzips to a folder with the Flutter workspace in source/.
Everything below is run from there.
cd source
flutter pub get
flutter run
That launches the hub: the fifty game launcher, with search, category filters and a "carry on" row for whatever you last played. No account, no Firebase project, no ad account and no store setup is needed — the app runs on device storage with Google's test ad units.
The one file you edit first. lib/app_config.dart
holds every choice this project asks you to make: hub or single game, which
game, ads on or off, purchases on or off, ad unit ids, and the economy
numbers. Nothing else in the app reads a feature flag.
What you get
- Fifty complete games, each playable, each saving its own progress.
- Thirteen screens per game, shared: splash, onboarding, main menu, mode select, board, pause, win, lose, leaderboard, daily reward, shop, settings and the interstitial. A game supplies only its mode select and its board.
- Seven engine families: grid, match, card, board, arcade, quiz and idle.
- Monetisation: AdMob banner, interstitial and rewarded, an IAP shop, UMP consent, the iOS ATT prompt and an age gate that runs before all of it.
- One theme layer:
lib/theme/tokens.dartdrives colour, radii, type and the gel button look across every screen. - 825 tests, including independent solvers that verify the puzzle generators actually produce solvable, single-answer boards.
- The design kits: the HTML UI designs every game was drawn from.
What is not in v1.0. There is no online multiplayer, no quest system, no seasonal pass and no daily spin. Some of those screens exist as designs in the kits; they are drawings, not features. Board and card games offer pass and play on one device.
Configuration lib/app_config.dart
The file is divided into five numbered sections. These are the values you will actually change.
1 · What to build
| Constant | Default | What it does |
|---|---|---|
appMode | AppMode.hub | Hub launcher, or a single game as its own app. |
singleGame | GameId.game2048 | Read only when appMode is singleGame. |
appTitleOverride | null | Splash and store title. Null uses the catalogue title. |
2 · Services
| Constant | Default | What it does |
|---|---|---|
backend | Backend.local | Device storage, or Firebase — see Saves & backend. |
adsEnabled | true | Master switch. False builds a completely ad free app. |
iapEnabled | true | Master switch for in app purchases. |
consentEnabled | true | UMP consent and the iOS ATT prompt. |
3 · Ads
See Ads & consent. In short: AdConfig.units
stays AdUnits.test until your own AdMob ids are in the six
fields below it.
4 · Purchases
Product ids, exactly as you enter them in App Store Connect and the Play Console, plus how many coins each coin product grants.
5 · Economy
Hearts, the refill timer, whether losing costs a heart, the seven day reward
ladder and what a rewarded ad pays. The full tuning table — booster prices,
starting balances, what the booster bundle grants — is in
lib/core/economy/balance.dart.
One game as its own app
Every game is self-contained: its engine and its two own screens live in one
folder under lib/games/, and no game imports another. A test
enforces that rule, which is what makes the following safe.
./tools/build_single.sh sudoku
That one command:
- sets
appModetosingleGameandsingleGameto that game; - rewrites
lib/games/games.dartso only that game is compiled in — the other forty nine never reach the binary; - sets the Android
applicationIdand all six iOSPRODUCT_BUNDLE_IDENTIFIERentries tocom.webkoding.sweetbox.sudoku; - installs that game's launcher icon on both platforms;
- runs the analyzer to prove the result still compiles.
Run it with no arguments to list the fifty names. To go back:
./tools/build_single.sh hub
Change the bundle id prefix from com.webkoding.sweetbox to your
own in tools/build_single.sh before your first release build.
Licensing. Each standalone game you publish is a separate end product and needs its own Envato licence. See the FAQ.
App icons
Every game has its own drawn icon, and so does the hub. One script turns each 1024 master into the four files a build needs, and refuses to run if a game in the catalogue has no artwork.
# all of them
python3 tools/build_icons.py
# just one
python3 tools/build_icons.py sudoku
Output goes to assets/icons/<slug>/:
| File | Used for |
|---|---|
1024.png | The iOS icon, and the source flutter_launcher_icons scales from. |
512.png | The Google Play listing icon. |
foreground.png | The Android adaptive foreground: the same drawing inset into the safe zone, so no launcher mask clips it. |
background.txt | The colour behind that foreground, sampled from the artwork rather than guessed. |
To use your own artwork, replace assets/icons/<slug>/1024.png
with a 1024×1024 PNG and re-run the script, then
./tools/build_single.sh <game> to install it. The script
needs Python's Pillow (pip3 install pillow); it does not need
a browser or any design software.
Ads & consent
The project ships with Google's official test ad unit ids. They fill every time and earn nothing, which is exactly what you want while building.
Going live
- Create the app and the three ad units in your AdMob account.
- Paste the six ids into
AdConfiginlib/app_config.dart. - Set
AdConfig.unitstoAdUnits.production. - Put your AdMob app id into
android/app/src/main/AndroidManifest.xmlandios/Runner/Info.plist.
Testing with production ids on your own device is a policy violation and
gets accounts suspended. Leave AdUnits.test set until release.
How often ads show
| Setting | Default | Effect |
|---|---|---|
interstitialEveryNLevels | 3 | An interstitial after every third level exit. |
interstitialCooldownSeconds | 45 | Never two interstitials closer than this. |
bannerFreeSessions | 2 | No board banner for the first two sessions. |
Raise these for a gentler first session. Both stores penalise apps that open with an interstitial.
Consent, ATT and the age gate
The age gate runs before anything else — before consent, before the ad SDK,
before the first screen. A player who answers under the age of consent gets
non-personalised ads and no tracking prompt. On iOS, ATT is requested after
UMP, in the order Apple requires. Set consentEnabled to false
only if you are shipping without ads entirely.
Purchases
Four products ship configured:
| Product id | Type | Grants |
|---|---|---|
remove_ads | Non-consumable | Removes every ad, permanently. Restorable. |
coins_small | Consumable | 500 coins |
coins_medium | Consumable | 1 500 coins |
coins_large | Consumable | 5 000 coins |
booster_bundle | Consumable | 20 hints, 20 undos, 10 shuffles, 10 extra times, 5 skips |
Create these ids in App Store Connect and the Play Console exactly as spelled
above, or change the spelling in IapConfig to match what you
created. The shop screen builds its rows from what the store actually
returned, so a product you have not set up simply does not appear —
and the coin-priced booster half of the shop keeps working regardless.
Restore is always visible, as the App Store requires.
Economy
One wallet, shared by all fifty games: coins, hearts and boosters.
- Hearts — five at most, one regenerating every twenty minutes.
Losing costs one. Set
EconomyConfig.heartsCostOnLossto false for a relaxed build; puzzle audiences often prefer it. - Coins — earned per round (each game decides what a good round is worth from its own score), from the daily reward, and from rewarded ads. Spent on hearts and boosters.
- Boosters — hint, undo, shuffle, extra time and skip level.
Priced in
Balance.boosterCost. A new player starts with three hints and three undos so the first one is free. - Daily reward — a seven day ladder, 50 coins climbing to 500. The day is stored in UTC, so the reward cannot be claimed twice by changing the phone clock or crossing a time zone, and missing a day restarts the streak.
To make the game more generous, raise EconomyConfig.dailyRewardCoins
and rewardedAdCoins; to make it tighter, raise the booster prices
in Balance. Hot restart and the whole suite follows.
Saves & backend
By default everything is on the device, in shared_preferences:
progress, high scores, the resume snapshot for each game and the wallet. The
leaderboard fills itself with generated opponents around the player's own
score, so the screen is never empty and no server is needed.
Games never touch storage themselves. They hand a snapshot to
GameSession, and the shell writes it. That is why swapping the
backend is a two line change in main() rather than a rewrite.
Firebase
For accounts, cloud save and a shared score table, documentation/firebase.md
in the package carries the complete adapter source, the Firestore security
rules and the main() wiring. The adapters are shipped as source
rather than compiled in because the Firebase Android plugin refuses to build
without your own google-services.json — a file we cannot ship —
so a package with Firebase pre-wired would fail the first build anyone ran.
If you set backend to Backend.firebase and forget to
pass the adapters, boot stops with a message telling you so, rather than
quietly saving to the device.
Project structure
source/
├── lib/
│ ├── app_config.dart the only file you have to edit
│ ├── main.dart boot, then hub or single game
│ ├── core/ services: ads, iap, consent, economy, storage
│ ├── theme/ tokens, the theme, the shared widgets
│ ├── shell/ the thirteen screens every game shares
│ ├── hub/ the fifty game launcher
│ └── games/
│ ├── _shared/ widgets more than one game uses
│ └── <game>/ one folder per game: engine/ + screens/ + game.dart
├── packages/
│ ├── sweetbox_domain/ models and repository interfaces — no Flutter import
│ └── sweetbox_engines/ the reusable engine families
├── assets/icons/ the generated icon masters
├── test/ 825 tests
└── tools/
├── build_single.sh turn the workspace into one game's app
└── build_icons.py redraw the icon masters
Two rules are enforced by tests rather than by convention:
sweetbox_domain may not import Flutter, and no game may import
another game.
Adding your own game
A game is a folder and a class. The thirteen shared screens come free.
- Add a value to
GameIdand an entry toGameCataloginpackages/sweetbox_domain/— slug, title, tagline, glyph, accent and engine kind. - Create
lib/games/<id>/withgame.dart, anengine/for the rules and ascreens/for the mode select and the board. - Extend
SweetboxGame: return yourGameId, build the selection screen and build the board. - Use
GameSessionfor saving, pausing, finishing and rewarded ads. Never navigate, never touch the ad SDK, never write to disk directly. - Run
./tools/build_single.sh hubto register it, andpython3 tools/build_icons.py <slug>to draw its icon.
Keep the engine free of Flutter imports and it stays testable without a widget test — which is how every generator in this package is verified.
Tests
cd source
flutter test # 825 tests
cd packages/sweetbox_domain && dart test # the catalogue and its claims
The suite is not decoration. It includes independently written checkers that verify each generator's output: that a Sudoku has exactly one solution, that a Nonogram is line-solvable, that a Sokoban is reachable, that a slide puzzle has the right parity, that a tube sort can actually be finished, and a chess perft that matches the published move counts. It also checks the listing's own claims — that no tagline sells a feature that does not exist, and that every game in the catalogue is registered in the build.
Release checklist
- Set
appModeand, for a single game, run./tools/build_single.sh <game>. - Change the bundle id prefix from
com.webkoding.sweetboxto your own. - Paste your AdMob ids into
AdConfigand setunitstoAdUnits.production. - Put your AdMob app id in
AndroidManifest.xmlandInfo.plist. - Create the product ids in both stores.
- Replace the app name in
AndroidManifest.xmlandInfo.plist. - Set your own signing config — the Android build ships with debug signing so it runs out of the box, and you must replace it before uploading.
- Bump
version:inpubspec.yaml. - Write your privacy policy URL into the store listing; the app requests consent and ATT, and both stores require a policy for that.
flutter build appbundle --release # Google Play
flutter build ipa --release # App Store
Troubleshooting
The Android build fails in a Kotlin task
Kotlin's incremental compiler and Gradle 9.1 disagree about a cache
directory. android/gradle.properties sets
kotlin.incremental=false for this reason. If you remove that
line and the build starts failing on a clean tree, put it back.
Ads never appear
Check three things in order: adsEnabled is true, the player has
not bought remove ads, and consent was granted. The ad SDK is only
initialised after consent allows it — that is deliberate, not a bug.
The shop is empty
The rows come from what the store returned. On a debug build with no store configuration there are no products, and the screen says so. The coin-priced boosters at the top still work.
Icon generation fails
tools/build_icons.py drives Google Chrome headlessly and expects
it at /Applications/Google Chrome.app. Change the
CHROME constant at the top of the script for another location or
platform.
A single game build still shows the hub
Run ./tools/build_single.sh <game> rather than editing
app_config.dart by hand — the mode and the registered game list
have to agree, and the script sets both.
FAQ
Can I publish all fifty games separately?
Technically yes, and the tooling is built for it. Commercially, each published app is a separate end product under the Envato licence and needs its own licence. Fifty apps means fifty licences.
Do I need a Firebase project?
No. Every game runs fully offline on device storage. Firebase is optional and documented.
Can I change the look without touching every screen?
Yes. lib/theme/tokens.dart holds the colours, radii, spacing and
type. Change a token and all fifty games follow.
Is there online multiplayer?
No. Board and card games offer pass and play on one device. Some kit designs draw online screens; those are designs, not features.
What language is the app in?
English. All user-facing strings live in the widgets rather than in an ARB
file, so adding flutter_localizations is a normal Flutter task
rather than an untangling job.
Where did the content come from?
Every word list, question bank and puzzle set was written for this product.
The Piano Tiles melodies are public domain and each records its own source.
Flags are Unicode emoji, so no flag artwork is bundled. The full register is
in documentation/licences.md.
Support
Six months of support is included with your purchase and can be extended to twelve. Updates are free for life. Write to support@webkoding.com — include your Envato purchase code, your Flutter version and the exact error, and you will normally have a reply within one business day.