You might know that I am currently learning Flutter. I watched few YouTube tutorials, wrote few posts about my learnings, and signed up for the #30DaysOfFlutter. I decided to take my learning journey to the next level by actually implementing something
The best way to learn something is by building something
Offline Programmer ・ Oct 19 '20
BrainTrainer is a math game where you have 30 seconds to solve as many questions as possible. Your highest score will be displayed on the app bar and saved in Firebase.
The game has two screens in total. On main.dart we watch the firebase user to determine which screen to display.
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
return Scaffold(
appBar: AppBar(
title: Text('BrainTrainer'),
),
body: firebaseUser != null ? loadGameScreen(context) : LoginPage());
}
Future<Object> loadGameScreen(BuildContext context) {
Future.delayed(Duration.zero, () {
return Navigator.of(context).pushNamedAndRemoveUntil(
GameScreen.routeName, (Route<dynamic> route) => false);
});
}
}
We are using a provider to enable (Apple Sign In) using Firebase. check my previous post on that
Flutter Firebase Authentication: Apple Sign In
Offline Programmer ・ Feb 17 '21
We are using few widgets in the game:
ActionButtons Widget: This is where we show the play button to start the game. we also use to indicate to the player if their selected answer is correct or now
import 'package:brain_trainer_app/models/game.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ActionButtons extends StatefulWidget {
const ActionButtons({
Key key,
}) : super(key: key);
@override
_ActionButtonsState createState() => _ActionButtonsState();
}
class _ActionButtonsState extends State<ActionButtons> {
@override
Widget build(BuildContext context) {
final _game = Provider.of<Game>(context);
return SizedBox(
height: 100,
child: GestureDetector(
onTap: () {
_game.playTheGame();
},
child: Card(
margin: EdgeInsets.all(10),
elevation: 8,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(color: Colors.white, spreadRadius: 3),
],
),
padding: EdgeInsets.all(10),
child: Consumer<Game>(
builder: (context, game, child) {
return Image.asset(
_game.actionButtonImage,
fit: BoxFit.cover,
);
},
),
),
),
),
);
}
}
AnswerItem Widget: This represents an item on a grid to display a number for the player to select. One of those items will be the correct answer.
import 'package:brain_trainer_app/models/game.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AnswerItem extends StatelessWidget {
final Answer answer;
final int index;
static const _answercolot = [
Color.fromRGBO(224, 81, 98, 1),
Color.fromRGBO(84, 160, 86, 1),
Color.fromRGBO(68, 150, 224, 1),
Color.fromRGBO(111, 64, 222, 1),
];
const AnswerItem({Key key, this.answer, this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
final _game = Provider.of<Game>(context);
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: GridTile(
child: GestureDetector(
onTap: () {
_game.answerSelected(this.answer);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: _answercolot[this.index],
boxShadow: [
BoxShadow(color: Colors.white, spreadRadius: 3),
],
),
padding: EdgeInsets.all(10),
child: Center(
child: Text(
answer.value.toString(),
style: TextStyle(
fontSize: 24,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold),
)),
),
),
),
);
}
}
AnswersGrid Widget: A grid of 4 AnswerItem widgets.
import 'package:brain_trainer_app/models/game.dart';
import 'package:brain_trainer_app/widgets/answer_item.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AnswersGrid extends StatelessWidget {
@override
@override
Widget build(BuildContext context) {
final gameData = Provider.of<Game>(context);
final answers = gameData.answers;
return GridView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: answers.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 3 / 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10),
itemBuilder: (BuildContext context, int index) {
return AnswerItem(
answer: answers[index],
index: index,
);
});
}
}
GameAds Widget: This is where we display Google AdMob Ads
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class GameAds extends StatefulWidget {
@override
_GameAdsState createState() => _GameAdsState();
}
class _GameAdsState extends State<GameAds> {
BannerAd _bannerAd;
@override
void initState() {
super.initState();
MobileAds.instance.initialize();
_bannerAd = BannerAd(
size: AdSize.banner,
adUnitId: BannerAd.testAdUnitId,
listener: AdListener(),
request: AdRequest(),
);
_bannerAd..load();
}
@override
void dispose() {
super.dispose();
_bannerAd.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(height: 50, child: AdWidget(ad: _bannerAd));
}
}
GameConfetti Widget: We use this fun widget to display a celebration animation when the play finish the game
import 'package:brain_trainer_app/models/game.dart';
import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
class GameConfetti extends StatefulWidget {
@override
_GameConfettiState createState() => _GameConfettiState();
}
class _GameConfettiState extends State<GameConfetti> {
ConfettiController _controllerCenter;
@override
void initState() {
_controllerCenter =
ConfettiController(duration: const Duration(seconds: 10));
_controllerCenter.play();
super.initState();
}
@override
void dispose() {
_controllerCenter.dispose();
super.dispose();
}
_showAlert(context) {
Future.delayed(Duration.zero, () async {
final String _gameMsg =
Provider.of<Game>(context, listen: false).completionMsg;
var alertStyle = AlertStyle(
animationType: AnimationType.fromTop,
isCloseButton: false,
isOverlayTapDismiss: false,
descStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
descTextAlign: TextAlign.start,
animationDuration: Duration(milliseconds: 400),
overlayColor: Colors.transparent,
alertBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0.0),
side: BorderSide(
color: Colors.grey,
),
),
titleStyle: TextStyle(
color: Colors.green,
),
alertAlignment: Alignment.center,
);
Alert(
style: alertStyle,
context: context,
type: AlertType.success,
title: "Well done",
desc: _gameMsg,
buttons: [
DialogButton(
child: Text(
"Brilliant!!!",
style: TextStyle(color: Colors.white, fontSize: 16),
),
onPressed: () => Navigator.pop(context),
width: 120,
)
],
).show();
});
}
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topCenter,
child: ConfettiWidget(
confettiController: _controllerCenter,
blastDirectionality: BlastDirectionality
.explosive, // don't specify a direction, blast randomly
shouldLoop: false,
gravity: 0.5,
emissionFrequency: 0.05,
numberOfParticles:
20, // start again as soon as the animation is finished
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
], // manually specify the colors to be used
child: _showAlert(context)
),
);
}
}
TimerQuestionScoreRow Widget: Here, we show the 30s timer, The math question, and we keep track of the player score/
import 'package:brain_trainer_app/models/game.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TimerQuestionScoreRow extends StatefulWidget {
const TimerQuestionScoreRow({
Key key,
}) : super(key: key);
@override
_TimerQuestionScoreRowState createState() => _TimerQuestionScoreRowState();
}
class _TimerQuestionScoreRowState extends State<TimerQuestionScoreRow> {
@override
Widget build(BuildContext context) {
final _game = Provider.of<Game>(context);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 3,
child: SizedBox(
height: 100,
child: Card(
margin: EdgeInsets.all(10),
elevation: 8,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color.fromRGBO(255, 152, 0, 1),
boxShadow: [
BoxShadow(color: Colors.white, spreadRadius: 3),
],
),
padding: EdgeInsets.all(10),
child: Consumer<Game>(
builder: (context, game, child) {
return Center(
child: Text(
'${game.timer}s',
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
));
},
),
),
),
),
),
Expanded(
flex: 4,
child: SizedBox(
height: 100,
child: Card(
margin: EdgeInsets.all(10),
elevation: 8,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(color: Colors.white, spreadRadius: 3),
],
),
padding: EdgeInsets.all(10),
child: Consumer<Game>(
builder: (context, game, child) {
return Center(
child: Text(
'${game.question}',
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
));
},
),
),
),
),
),
Expanded(
flex: 3,
child: SizedBox(
height: 100,
child: Card(
margin: EdgeInsets.all(10),
elevation: 8,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color.fromRGBO(3, 169, 244, 1),
boxShadow: [
BoxShadow(color: Colors.white, spreadRadius: 3),
],
),
padding: EdgeInsets.all(10),
child: Consumer<Game>(
builder: (context, game, child) {
return Center(
child: Text(
'${game.score}',
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
));
},
),
),
),
),
),
],
);
}
}
The game use three models:
Game: This is the primary model where we set up the game. Here we also verify the selected answer and keep track of the timer.
Answer: This is a simple model to represent the answer value
Player: This is where we have the player information we get from Firebase, and we also use it to track the high score.
We are using DataRepository class to retrieves and saves the data.
import 'package:brain_trainer_app/models/player.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class DataRepository {
final CollectionReference collection =
FirebaseFirestore.instance.collection('players');
Future<void> addPlayer(Player player) {
return collection.doc(player.uid).set(player.toJson());
}
updatePlayer(Player player) async {
await collection.doc(player.uid).update(player.toJson());
}
Future<Player> getPlayer(String uid) async {
var doc = await collection.doc(uid).get();
if (doc.data() == null) {
return null;
}
return Player.fromJson(doc.data());
}
}
The game is on Apple Store here and Google Play here
Check the code here
brain_trainer_app
Do you enjoy math? Do you enjoy games? Want to relax and train your brain? Try this amazing app for some fun – Brain Trainer.
Brain Trainer consists of simple math-based games designed to exercise memory, speed, and attention. Take a break from your busy schedule and use Brain Trainer to relax and practice some math.
Follow me on Twitter for more tips about #coding, #learning, #technology...etc.
Check my Apps on Google Play
Cover image Amol Tyagi on Unsplash
Top comments (0)