Is it possible to resume the state of a certain widget page in flutter after popping the page out? - flutter

Let's say for example when I press on the Like button (from the Like_button package) it will set its boolean to true, meaning that it will turn red. But when I pop this page out of the navigation and go back to it, how do I get it to show that I have previously pressed the button already? (right now what it does is it shows the same state as if i didn't press on it before) I want to make it into like 'favourite article' system.
import 'package:flutter/material.dart';
import 'package:like_button/like_button.dart';
class Article extends StatefulWidget {
#override
_ArticleState createState() => _ArticleState();
}
class _ArticleState extends State<Article> {
bool isLiked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[50],
body: SingleChildScrollView(
GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/N1');
},
child: Container(
margin: const EdgeInsets.symmetric(vertical:10),
child: Padding(
padding: EdgeInsets.fromLTRB(30, 0, 30, 0),
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25.0)),
child: Column(
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: Image.asset('assets/CoverImage1.jpeg')),
Positioned(
top: 15,
right: 15,
child: Container(
width:50,
height:50,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15.0)),
border: Border.all(color: Color.fromRGBO(141, 141, 141, 1.0).withAlpha(40)),
),
child: Center(
child: LikeButton(
size: 25,
isLiked: isLiked,
likeBuilder: (isLiked) {
final color = isLiked ? Colors.red : Colors.grey;
return Icon(Icons.favorite, color:color, size:25);
},
onTap: (isLiked) async {
this.isLiked = !isLiked;
return !isLiked;
}
),
),
),
),
],
),

you can store the value of variable with shared preferences.
you can check the documentation here: https://pub.dev/packages/shared_preferences/example
try these steps:
store value with something like prefs.setBool()
get the value with getBool in initState(). initState() always perform before UI widget is built.
with these steps enables the app to get the value even when apps is closed

Related

How to implement Popup with Flutter?

I have a Flutter app with screens rendered conditionally with an array. Anyway, I need to have a popup screen like this :
If have stored all my "popup screens" in an array and rendered the main screen and the popup screen in a stack. I don't know if this is the best solution and I guess I will have performance issues.
Here is the PopupContainerclass, this Widget is rendered on every Popup Screen with the child passed as content :
class PopupContainer extends StatefulWidget {
final Widget? child;
const PopupContainer({
Key? key,
this.child,
}) : super(key: key);
#override
State<PopupContainer> createState() => _PopupContainerState();
}
class _PopupContainerState extends State<PopupContainer> {
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Consumer<ScreenManager>(
builder: (context, manager, child) => Stack(
alignment: Alignment.bottomCenter,
children: [
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Container(
decoration: BoxDecoration(color: Colors.white.withOpacity(0.0)),
),
),
Container(
height: height * 0.8,
width: double.infinity,
padding: const EdgeInsets.all(32),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
boxShadow: [
BoxShadow(
blurRadius: 37,
spreadRadius: 0,
color: Color.fromRGBO(28, 48, 72, 0.24),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
alignment: Alignment.topRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
primary: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () => manager.closePopup(),
child: SvgPicture.asset('assets/close.svg'),
),
),
widget.child ?? const SizedBox.shrink(),
],
),
),
],
),
);
}
}
The consumer is used for handling the screens states :
enum ScreensName {
homeScreen,
favoriteProductsScreen,
archivedListsScreen,
recipesScreen,
}
enum PopupsName {
newProductPopup,
archivedListPopup,
editProductPopup,
newRecipePopup,
}
const screens = <ScreensName, Widget>{
ScreensName.homeScreen: HomeScreen(),
ScreensName.favoriteProductsScreen: FavoriteProductsScreen(),
ScreensName.archivedListsScreen: ArchivedListsScreen(),
ScreensName.recipesScreen: RecipesScreen(),
};
const popups = <PopupsName, Widget>{
PopupsName.newProductPopup: NewProductPopup(),
};
class ScreenManager extends ChangeNotifier {
static ScreensName screenName = ScreensName.homeScreen;
static PopupsName? popupName = PopupsName.newProductPopup;
get currentScreen => screens[screenName];
get currentPopup => (popups[popupName] ?? Container());
/// Open the given popup.
void openPopup(PopupsName newPopupName) {
popupName = newPopupName;
notifyListeners();
}
/// Closes the current popup.
void closePopup() {
popupName = null;
notifyListeners();
}
/// Change the screen.
void setScreen(ScreensName newScreenName) {
screenName = newScreenName;
notifyListeners();
}
}
And finally, the main component build method (I also have some theme styling but useless here) :
Widget build(BuildContext context) {
DatabaseHelper.initDb();
return Consumer<ScreenManager>(
builder: (context, screenManager, child) => Material(
child: MaterialApp(
title: _title,
theme: _customTheme(),
home: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
screenManager.currentScreen,
screenManager.currentPopup,
],
),
),
),
);
}
PS : I am a web developer so I know the main programming principles but Dart and mobile dev is brand new for me. Also, I could share my code with you, however, this project is splitted into files and it would take too much space in the post. Ask if you need it !
Maybe an easier solution would be to use the showDialog function where you need to trigger the popup. Check out the docs https://api.flutter.dev/flutter/material/showDialog.html
showDialog(context: context, builder: (context) => AlertDialog(title: Text('Title'), content: Text('Here content'),));

Why does my image disappear when I scroll down?

I have 4 cards to take user inputs in a page, this 4 card widgets is showing in the listView, when user tap AddPhoto card, user can add photo from camera or gallery successfully, but after user added photo and scrolling page to give in inputs to the other cards, when AddPhoto card disappears because of sliding, after user turn back to the initial point of the page, the picture that user added is showing nothing.
How can I solve it?
class AddPhotoCard extends StatefulWidget {
AddPhotoCard({this.subCategoryCardId,this.subCategoryId});
final int subCategoryId;
final int subCategoryCardId;
#override
_AddPhotoCardState createState() => _AddPhotoCardState();
}
class _AddPhotoCardState extends State<AddPhotoCard> {
String path;
#override
Widget build(BuildContext context) {
return AnimatedPadding(
duration: Duration(milliseconds: 500),
padding: path==null?EdgeInsets.only(top: 7.5,left: 30,right: 30,bottom:7.5):
EdgeInsets.only(top: 7.5,left: 7.5,right: 7.5,bottom:7.5),
child: GestureDetector(
onTap: (){
_showOptions(context);
},
child: AnimatedContainer(
duration: Duration(milliseconds:500),
height: path==null?200:400,
child: Container(
decoration: BoxDecoration(
border: Border.all(style: BorderStyle.solid, width: 1),
borderRadius: BorderRadius.circular(30),
color:categoryModels[widget.subCategoryId].subCategoryModels[widget.subCategoryCardId].categoryColor.withOpacity(0.5),
),
child: Padding(
padding:EdgeInsets.all(5.0),
child: Column(
children: [
Expanded(
child: AspectRatio(
aspectRatio: 600/600,
child: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fitWidth,
alignment: FractionalOffset.center,
image: path==null?AssetImage("images/stickerForRecipeScreen.png"):FileImage(File(path)),
)
),
),
),
),
),
path==null?Text(
"${categoryModels[widget.subCategoryId].subCategoryModels[widget.subCategoryCardId]
.subCategoryName} tarifiniz için bir resim çekin",
style: TextStyle(
color: Colors.black,
fontFamily: "OpenSans",
fontWeight: FontWeight.bold,
fontSize: 20),): SizedBox.shrink(),
path==null?Icon(
Icons.camera_alt_rounded,
color: Colors.black,size: 70,): SizedBox.shrink(),
path==null?SizedBox(height: 20): SizedBox.shrink(),
],
),
),
),
),
),
);
}
}
This problem occurs because the ListView automatically destroys any widgets that are "scrolled away". You need to keep them alive or increase the cache:
Solution 1: Add this to your ListView:
cacheExtent: 4 //the number of widgets you have and want to keepAlive
Solution 2: Make your widgets a keepAlive:
//add this to the AddPhoto build method
super.build(context);
//add this to the end of the AddPhoto state, outside the build method
#override
bool get wantKeepAlive => true;
//add this then to your ListView
addAutomaticKeepAlives: true

how to make flutter searchDelagate a separate screen that can be navigated to independently?

i have a page called searchUsersSCreen which is this:
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:myApp/models/otherUser.dart';
import 'package:myApp/ui/widgets/user_profile.dart';
import 'database.dart';
class SearchUsersScreen extends StatefulWidget {
#override
_SearchUsersScreenState createState() => _SearchUsersScreenState();
}
class _SearchUsersScreenState extends State<SearchUsersScreen> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => showSearch(
context: context,
delegate: SearchUsers(
DatabaseService().fetchUsersInSearch(),
),
));
}
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).primaryColor,
);
}
}
and inside the same dart file is have this searchDelegate :
//Search delegate
class SearchUsers extends SearchDelegate<OtherUser> {
final Stream<QuerySnapshot> otherUser;
final String hashtagSymbol = 'assets/svgs/flaticon/hashtag_symbol.svg';
SearchUsers(this.otherUser);
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
#override
Widget buildLeading(BuildContext context) {
return Container(
width: 0,
height: 0,
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
width: 0,
height: 0,
color: Theme.of(context).primaryColor,
);
}
#override
Widget buildSuggestions(BuildContext context) {
showUserProfile(String userId) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfileView(
userUid: userId,
)));
}
return StreamBuilder<QuerySnapshot>(
stream: DatabaseService().fetchUsersInSearch(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
final handlesResults = snapshot.data.documents
.where((u) => u['username'].contains(query));
if (!snapshot.hasData) {
return Container(
color: Theme.of(context).primaryColor,
child: Center(
child: Text(
'',
style: TextStyle(
fontSize: 16, color: Theme.of(context).primaryColor),
),
),
);
}
if (handlesResults.length > 0) {
return Container(
color: Theme.of(context).primaryColor,
child: ListView(
children: handlesResults
.map<Widget>((u) => GestureDetector(
child: Padding(
padding: const EdgeInsets.all(0.1),
child: Container(
padding: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
border: Border(
bottom: BorderSide(
width: 0.3, color: Colors.grey[50]))),
child: ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).primaryColor,
backgroundImage:
NetworkImage(u['userAvatarUrl']),
radius: 20,
),
title: Container(
padding: EdgeInsets.only(left: 10),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(u['username'],
style: TextStyle(
fontSize: 16,
color: Theme.of(context)
.accentColor),
overflow: TextOverflow.ellipsis),
SizedBox(
height: 5,
),
Text(u['name'],
style: TextStyle(
fontSize: 16,
color: Colors.grey[500],
),
overflow: TextOverflow.ellipsis),
],
),
),
trailing: Container(
padding: EdgeInsets.only(left: 10),
height: 43.0,
width: MediaQuery.of(context).size.width / 2,
),
),
),
),
onTap: () {
showUserProfile(u['id']);
},
))
.toList(),
),
);
} else {
return Container(
color: Theme.of(context).primaryColor,
child: Center(
child: Text(
'No results found',
style: TextStyle(
fontSize: 16,
color: Theme.of(context).accentColor,
),
),
),
);
}
});
}
}
i wanted to user the class SearchUsers as a separate screen that i can navigate to independently...but couldn't achieve that as SearchUsers doesn't evaluate to a widget.
so i built SearchUsersScreen statefulWidget and inside it's initState() i called this:
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => showSearch(
context: context,
delegate: SearchUsers(
DatabaseService().fetchUsersInSearch(),
),
));
}
as to make the search feature starts automatically when the user navigates to SearchUsersScreen.
and i ended up into two problems:
SearchUsers is being displayed in full screen ontop of SearchUsersSCreen (which i don't want this behavior), i want it to be displayed inside of it.
actually its covering the BottomNavigationBar i built for navigation between screens.
after SearchUsers is being displayed (and its doing its job well), when i tap the device back button...i leave SearchUsers and get back to SearchUsersScreen....which is indeed a blank screen.
so to wrap it up...all i want is to use SearchUsers class as a widget that i can navigate to and navigate from independently...thats it.
any help would be much appreciated.
thanks for your time reading.
Instead of trying to create a separate widget SearchUsers, try to create a dialog and show it when anyone wants to search users. You can also use the navigator and the back button in this case and get arguments passed from the next screen to the previous screen.

Flutter change Text on Button click in Gridview

Currently, I have a String with 12 words which is extracted from an Argument from another page:
Widget build(BuildContext context) {
final String seed = ModalRoute.of(context).settings.arguments;
I then split the String and shuffle them into an Array which then display as MaterialButton in a GridView. I need that when the user click on a MaterialButton it will add the splitted word into another String Array which then display in a Container above the GridView. The GridView is built by using a for loop to add MaterialButton to <Widget>[] which call a setState() to change a bool value which in turn change the appearance of the button and add the word into the Container. At first I had trouble when clicking on a button, the setState() is called thus refreshing the whole page and reshuffle the word, but after finding Update a part of the UI on Flutter I wrap my MaterialButton in a StatefulBuilder and preventing it from rebuilding the whole page. But that also stop the Text in the container above from updating. What can I do so that when the user click a button, it change it appearance and also update the Text in the Container above?
Here is my code so far:
import 'package:flutter/material.dart';
import 'package:myapp/color_utils.dart';
class SeedConfirmPage extends StatefulWidget {
SeedConfirmPage({Key key, this.title}) : super(key: key);
final String title;
#override
_SeedConfirmPageState createState() => _SeedConfirmPageState();
}
class _SeedConfirmPageState extends State<SeedConfirmPage> {
List<bool> clicked = [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
];
#override
Widget build(BuildContext context) {
final String seed = ModalRoute.of(context).settings.arguments;
List<String> seedArray = seed.split(" ")..shuffle();
final seedChip = <Widget>[];
List<String> seedText = [];
for (var i = 0; i < seedArray.length; i++) {
seedChip.add(StatefulBuilder(builder: (BuildContext context, setState) {
return MaterialButton(
color: clicked[i] ? colorBlack : Colors.white,
textColor: clicked[i] ? Colors.white : colorBlack,
minWidth: MediaQuery.of(context).size.width,
height: 32,
child: Text(
seedArray[i],
style: TextStyle(fontSize: 12),
),
onPressed: () {
setState(() {
clicked[i] = !clicked[i];
if (clicked[i]) {
seedText.add(seedArray[i]);
} else {
seedText.remove(seedArray[i]);
}
});
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
);
}));
}
return Scaffold(
appBar: AppBar(
backgroundColor: splashBG,
leading: BackButton(color: Colors.white),
title: Text("Back"),
),
backgroundColor: splashBG,
body: Container(
padding: EdgeInsets.only(
left: 16,
right: 16,
),
child: Column(
children: <Widget>[
Flexible(
child: Container(
width: MediaQuery.of(context).size.width / 2,
child: Image(
image: AssetImage('assets/images/logo_1.png'),
),
),
),
Container(
height: 120,
padding: EdgeInsets.all(32.0),
margin: EdgeInsets.only(top: 28, bottom: 28),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: colorBlack),
child: Center(
child: Text(
seedText.toString(),
style: TextStyle(color: Colors.white),
),
),
),
Container(
width: MediaQuery.of(context).size.width,
height: 120,
child: GridView.count(
crossAxisCount: 4,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: MediaQuery.of(context).size.width /
(MediaQuery.of(context).size.height / 5),
children: seedChip,
),
),
],
),
),
);
}
}
I know this is not optimal to check the state of the button to know whether it is click or not. So a side question is what can I do to check the if the button has been clicked yet?
EDIT
Here is the screenshot of what I am trying to archive:
Before user press:
After user press:
You can do it in the same ways you changed button colors. In MaterialButton add this to child
child: clicked[i] ?
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(seedArray[i],style: TextStyle(fontSize: 12),),
Text('x',style: TextStyle(fontSize: 12),),
]) :
Text(seedArray[i],style: TextStyle(fontSize: 12),),

How to display a single property of an object in listview

I have a list of goal objects with two properties, description (what I want to display) and ID (used as a key to identify it). Ultimately I want a list of goal descriptions (ex. mow lawn, get groceries etc) but I'm confused how to specify a single property with the listview builder. The reason I'm using an object is because I want to use swipe to dismiss on the list. I'm using an object to give each goal a unique key, therefore when I swipe to dismiss I can safely undo the dismissal / reorder the list.
File Structure: lib folder contains functions, goals and main. A sub-folder in the lib folder called UI contains form and home.
main.dart
import 'package:flutter/material.dart';
import 'package:aurelius/UI/home.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context){
return new MaterialApp(
debugShowCheckedModeBanner: false,
title: "ToDo",
home: myWidgets(),
);
}
}
Widget myWidgets(){
return GoalsList();
}
home.dart
import 'package:flutter/material.dart';
import 'package:aurelius/goals.dart';
import 'package:aurelius/functions.dart';
//Goals List Variables
var goals = List<Goals>();
final TextEditingController listCtrl = new TextEditingController();
class GoalsList extends StatefulWidget{
#override
_GoalsListState createState() => _GoalsListState();
}
class _GoalsListState extends State<GoalsList>{
final formKey = GlobalKey<FormState>(); //key for goal form
#override
Widget build(BuildContext context){
final listSize = MediaQuery.of(context).size.height * 1;
return Scaffold(
resizeToAvoidBottomPadding: false,
extendBody: true,
backgroundColor: Colors.black,
//Navigation Bar
floatingActionButton: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
),
borderRadius: BorderRadius.circular(25.0),
),
child: FloatingActionButton.extended(
elevation: 4.0,
icon: const Icon(Icons.add),
label: const Text('Add Goal'),
backgroundColor: Colors.black,
splashColor: Colors.white,
//Pop-up Dialogue
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context){
return AlertDialog(
title: Center(child: new Text("New Goal:",)),
content: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
),
controller: listCtrl,
),
RaisedButton(
child: Text("ADD"),
onPressed: (){
goals.add(createGoal(listCtrl.text));
listCtrl.clear();
Navigator.pop(context);
},
splashColor: Colors.blue,
elevation: 2,
)
]
),
)
);
}
);
},
),
),
),
floatingActionButtonLocation:FloatingActionButtonLocation.centerDocked,
//Bottom App Bar
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: -30.0,
color: Colors.black,
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(icon: Icon(Icons.person_outline),color: Colors.white,splashColor: Colors.white, onPressed: (){},),
IconButton(icon: Icon(Icons.settings),color: Colors.white,splashColor: Colors.white, onPressed: (){},),
],
),
),
//Goals List Box
body: Column(children: <Widget>[
SizedBox(height: listSize,
child: ListView.builder(
itemCount: goals.length,
itemBuilder: (context,index){
return Dismissible(
key: UniqueKey(),
//Green background and icon for left side swipe
background: Container(
color: Colors.green[300],
padding: EdgeInsets.symmetric(horizontal: 20),
alignment: AlignmentDirectional.centerStart,
child: Icon(
Icons.check_box,
color: Colors.white,
),
),
//Green background and icon for right side swipe
secondaryBackground: Container(
color: Colors.green[300],
padding: EdgeInsets.symmetric(horizontal: 20),
alignment: AlignmentDirectional.centerEnd,
child: Icon(
Icons.check_box,
color: Colors.white,
),
),
onDismissed:(direction){
if(goals.contains(index)){
setState((){
goals.removeAt(index);
});
}
},
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(goals[index].description),
],
),
),
);
},
),
),
//Potential more rows here
],
)
);
}
}
}
goals.dart
import 'package:flutter/material.dart';
import 'package:aurelius/UI/home.dart';
class Goals{
String description; //part visible to user
int id;
Goals({this.description,this.id});
}
functions.dart
import 'package:flutter/material.dart';
import 'package:aurelius/goals.dart';
import 'package:uuid/uuid.dart';
createGoal(String text){
var goal = new Goals();
goal.description = text;
goal.id = new DateTime.now().millisecondsSinceEpoch;
return goal;
}
form.dart
import 'package:flutter/material.dart';
class AddButton extends StatefulWidget {
#override
AddButtonState createState() => new AddButtonState();
}
class AddButtonState extends State<AddButton>{
Color addbuttoncolor = Colors.red;
IconData addIcon = Icons.add;
void onPressed(){
setState((){
if (addIcon == Icons.add) {
addIcon = Icons.clear;
}
else{
addIcon = Icons.add;
}
});
}
#override
Widget build(BuildContext context){
return Scaffold(
body: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RawMaterialButton(
onPressed: onPressed,
child: new Icon(
addIcon,
color: Colors.blue,
size: 35.0,
),
shape: new CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(15.0),
),
]
),
),
)
);
}
}
I think the problem is when you creating the goal, you don't return created goal. Your createGoal() method should return the goal like below:
createGoal(String text){
var goal = new Goals();
goal.description = text;
goal.id = new DateTime.now().millisecondsSinceEpoch;
return goal; // Add this
}
The only issue I see is that you don't return the object you create in createGoal().
To display the description with the Dismissible, you would just set its child to child: Text(goals[index].description)
To optimize the code, you can initialize the Goal id directly in the class itself. You can set the constructor as Goals(this.description) and the createGoal function will not be needed anymore.
PS: Keeping your class names singular is a better practice