I am working on my first ever mobile application using Flutter and Firebase. As I am a regular gym goer and interested in powerlifting I decided to make a workout tracker app, where you will be able to track your workouts, see how you have improved over time.
I started by by building out the UI in Flutter because was not sure what I wanted the app to look like. So I started building the initial pages with help from a tutorial on https://fireship.io:
- Workout
- Exercises
- Network
- History
- Profile
- Login
Lets start with the login page.
At the moment there, it is nothing special. You can either sign in as guest or sign in using google. I am planning on adding the ability to sign in using email and password (not associated with google).
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:barbellplus/services/auth.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/login-background.jpg'),
fit: BoxFit.cover,
),
),
padding: const EdgeInsets.all(30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
FontAwesomeIcons.dumbbell,
size: 150,
color: Color.fromARGB(255, 209, 5, 5),
),
const Text(
'Barbell Plus',
textAlign: TextAlign.center,
style: TextStyle(
color: Color.fromARGB(255, 209, 5, 5),
fontSize: 40,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 120),
Flexible(
child: LoginButton(
icon: FontAwesomeIcons.userNinja,
text: 'Continue as Guest',
loginMethod: AuthService().anonLogin,
color: Colors.deepPurple,
),
),
LoginButton(
text: 'Sign in with Google',
icon: FontAwesomeIcons.google,
color: Colors.blue,
loginMethod: AuthService().googleLogin,
),
],
),
),
);
}
}
class LoginButton extends StatelessWidget {
final Color color;
final IconData icon;
final String text;
final Function loginMethod;
const LoginButton(
{super.key,
required this.text,
required this.icon,
required this.color,
required this.loginMethod});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: ElevatedButton.icon(
icon: Icon(
icon,
color: Colors.white,
size: 20,
),
style: TextButton.styleFrom(
padding: const EdgeInsets.all(24),
backgroundColor: color,
),
onPressed: () => loginMethod(),
label: Text(text, textAlign: TextAlign.center),
),
);
}
}
I had never used flutter or dart before but had previously made some web application using react.js. It was very interesting seeing the difference in the way things are done in flutter. It does sometimes get annoying that some widgets need specific things to make them do what you what, for example positioning widgets on a specific point on the screen.
Now moving on to the remaining pages.
This page is a list of all exercises available in the application. Originally, I used sample data just to get the UI right.
As began to integrate with the database, I needed a real dataset. I couldn't find anything that fitted my requirements. So for the time being I decided to web-scrape the data I needed. I'm not sure of the legal problems associated with this, but as there are only a handful of people who will use this app. I think I will be ok with taking the risk. However, if I do find a satisfactory dataset I will use that instead.
import 'package:barbellplus/exercises/exercise_dialog.dart';
import 'package:barbellplus/services/firestore.dart';
import 'package:barbellplus/services/models.dart';
import 'package:flutter/material.dart';
class ExerciseList extends StatelessWidget {
const ExerciseList({super.key});
@override
Widget build(BuildContext context) {
return Expanded(
child: DecoratedBox(
decoration: const BoxDecoration(
color: Colors.white,
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: FutureBuilder<List<Exercise>>(
future: FirestoreService().getExercises(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Color.fromARGB(255, 209, 5, 5)),
);
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) => ExerciseItem(
exercise: snapshot.data![index],
),
);
},
),
),
],
),
),
),
);
}
}
class ExerciseItem extends StatelessWidget {
final Exercise exercise;
// ignore: prefer_const_constructors_in_immutables
ExerciseItem({super.key, required this.exercise});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExerciseDialog(exercise: exercise),
),
);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 5),
height: 65,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
const SizedBox(width: 10),
Stack(
children: [
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(10),
),
),
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
image: const DecorationImage(
image:
AssetImage('assets/images/image-unavailable.png'),
fit: BoxFit.cover,
),
),
),
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
image: DecorationImage(
image: NetworkImage(exercise.image),
fit: BoxFit.cover,
),
),
),
],
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Text(exercise.name,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 14,
)),
const SizedBox(height: 5),
Text(
'${exercise.muscle} • ${(exercise.equipment)} • ${exercise.difficulty}',
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
fontSize: 12,
)),
],
),
),
const SizedBox(width: 10),
],
)),
);
}
}
Moving on to the profile page: This page is not yet complete at the moment. It has a bit of personal information, A carousel of personal progress images and some other button which give some other functionality which has not yet been decided.
The remaining pages, workout and network are not yet complete. The workout page will be where you choose your workout program and enter the data of your workout as you do them. I am currently in the process of integrating it with cloud firestore but am having some difficulties.
The Network page is something I am not sure about it was supposed to be a social aspect of the app where one could share there workout progress with friends. I am not sure how I want it to work.
Top comments (0)