The Function of the App
It has a two items in the bottom navigation bar where in the first section (Courses) the user can add a list of courses and it will update their overall GPA and highest average for them each time they add a new one. The other one is a simple "What do I need to get on my final to pass" section (Finals Predictor).
The following is the code for the at the top of the screen that shows the user's overall gpa, number of courses, and highest average
import 'package:flutter/material.dart';
import 'package:gradecalculator/models/course.dart';
class TopDisplay extends StatefulWidget {
final List<Course> courses;
final maxAverage;
final overallGPA;
TopDisplay(this.courses, this.maxAverage, this.overallGPA);
@override
_TopDisplayState createState() => _TopDisplayState();
}
class _TopDisplayState extends State<TopDisplay> {
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return Card(
margin: EdgeInsets.all(10),
elevation: 5,
color: Colors.grey[400],
child:Row(
children: <Widget> [
Container(
margin: EdgeInsets.all(10),
child: CircleAvatar(
child: widget.courses.isEmpty ? Text(
"0.0",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 25,
),
)
: Text(
"${widget.overallGPA.toStringAsFixed(2)}",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 25
),
),
radius: 45,
backgroundColor: Colors.red,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
SizedBox(
height: mediaQuery.size.height * 0.07,
),
Row(
children:<Widget> [
const Text(
'Number of Courses: ',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold
),
),
Text(
'${widget.courses.length}',
style: TextStyle(
color: Colors.white,
fontSize: 16
),
),
],
),
Row(
children: <Widget>[
const Text(
'Highest Average: ',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold
),
),
widget.courses.isEmpty ? Text(
'0.0',
style: TextStyle(
color: Colors.white,
fontSize: 16
),
)
:
Text(
'${widget.maxAverage}%',
style: TextStyle(
color: Colors.white,
fontSize: 16
),
)
]
),
],
),
],
),
);
}
}
The Problem
If I add courses everything will work fine and everything that needs to be displayed will be displayed. If I navigate to the Finals Predictor then back to Courses, the list of courses is still shown but the Overall GPA and Highest Average go back to 0.0.
The Code for the Tabs Screen with bottomNavigationBar
import 'package:flutter/material.dart';
import 'predictions_screens.dart';
import '../models/course.dart';
import 'home_page.dart';
class TabsScreen extends StatefulWidget {
final List<Course> listedCourses;
TabsScreen(this.listedCourses);
@override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
final List<Map<String, Object>> _pages = [
{'page': HomePage(), 'title': 'Courses'},
{'page': PredictionsScreen(), 'title': 'Final Grade Calculator'}
];
int _selectedPageIndex = 0;
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(_pages[_selectedPageIndex]['title']),
),
body: _pages[_selectedPageIndex]['page'],
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
backgroundColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.white,
selectedItemColor: Theme.of(context).accentColor,
currentIndex: _selectedPageIndex,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.book),
title: Text('Courses')),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.scatter_plot),
title: Text('Finals Predictor'))
],
),
);
}
}
Code in main.dart file
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'screens/predictions_screens.dart';
import 'screens/tabs_screen.dart';
import './models/course.dart';
import './screens/home_page.dart';
void main() {
//To ensure that the app only is used in portrait mode
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(GradeCalculator());
}
class GradeCalculator extends StatefulWidget {
@override
_GradeCalculatorState createState() => _GradeCalculatorState();
}
class _GradeCalculatorState extends State<GradeCalculator> {
List<Course> coursesOnScreen = [];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Grade Calculator",
theme: ThemeData(
primarySwatch: Colors.red,
accentColor: Colors.blueAccent[200],
),
//home: HomePage(),
routes: {
'/': (ctx) => TabsScreen(coursesOnScreen),
HomePage.routeName: (ctx) => HomePage(),
PredictionsScreen.routeName: (ctx) => PredictionsScreen()
},
);
}
}
Code for course list output
import 'package:flutter/material.dart';
import 'package:gradecalculator/models/course.dart';
class CourseList extends StatelessWidget {
static const routeName = '/course-list';
final List<Course> courses;
final Function deletec;
final Function setHighestAverage;
final Function calculateGPA;
CourseList(this.courses, this.deletec, this.setHighestAverage, this.calculateGPA);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: courses.length,
itemBuilder: (ctx, index){
return Card(
elevation: 5,
margin: EdgeInsets.symmetric(
vertical: 5,
horizontal: 8,
),
child: ListTile(
leading: CircleAvatar(
radius: 20,
child: Padding(
padding: EdgeInsets.all(6),
child: FittedBox(
child: Text(
'${courses[index].courseAverage}'
),
),
),
),
title: Text(
courses[index].courseName,
style: TextStyle(
fontWeight: FontWeight.bold
),
),
subtitle: Text(
'Course Weight: ${courses[index].courseWeight}'
),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Colors.grey,
),
onPressed: () {
deletec(courses[index].courseName);
setHighestAverage(courses);
calculateGPA(courses);
},
),
),
);
}
);
}
}
HomePage File which calls TopDisplay
import 'package:flutter/material.dart';
import '../models/course.dart';
import '../widgets/newCourse.dart';
import '../widgets/course_list.dart';
import '../widgets/top_display.dart';
class HomePage extends StatefulWidget {
static const routeName = '/home-page';
final List<Course> visibleCourses = [];
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double maxAverage = 0.00;
double overallGPA = 0.00;
//List for courses as users add courses, this is where they will be stored
//Calculates the total GPA shown in the top chart
void calculateGPA(List<Course> courseList) {
double totalWeight = 0;
double intermediateGPA = 0;
for (int i = 0; i < courseList.length; i++) {
totalWeight += courseList[i].courseWeight;
intermediateGPA +=
(courseList[i].courseAverage * courseList[i].courseWeight);
overallGPA = (intermediateGPA / totalWeight);
}
}
//Finds the highest average that is displayed in the top chart
void setHighestAverage(List<Course> courseList) {
calculateGPA(courseList);
for (int i = 0; i < courseList.length; i++) {
if (courseList[i].courseAverage >= maxAverage) {
maxAverage = courseList[i].courseAverage;
}
}
}
//Opens up the modal sheet that the user can use to enter a new course
void _startAddNewCourse(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
child: NewCourse(_addNewCourse),
);
},
);
}
//Initializes the new course and adds it to the courses list above
void _addNewCourse(String cTitle, double cAverage, double cWeight) {
final newc = Course(
courseName: cTitle,
courseAverage: cAverage,
courseWeight: cWeight,
);
setState(() {
widget.visibleCourses.add(newc);
setHighestAverage(widget.visibleCourses);
});
}
void _deleteCourse(String courseName) {
setState(() {
widget.visibleCourses.removeWhere((c) {
return c.courseName == courseName;
});
});
}
@override
Widget build(BuildContext context) {
//Setting up a mediaquery variable so that sizes can be determined based on phone size
final mediaQuery = MediaQuery.of(context);
return SafeArea(
child: Scaffold(
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TopDisplay(widget.visibleCourses, maxAverage, overallGPA),
Container(
height: mediaQuery.size.height * 0.7,
child: CourseList(
widget.visibleCourses, _deleteCourse, setHighestAverage, calculateGPA),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _startAddNewCourse(context),
backgroundColor: Colors.red,
child: Icon(Icons.add),
),
),
);
}
}