Can't access onPressed method of a Widget when creating it dynamically - flutter

First of all, let me fill you in the details:
I have this Widget:
class MenuAction extends StatelessWidget {
const MenuAction({
Key? key,
this.height,
required this.icon,
required this.label,
required this.willNavigate,
this.route,
this.onPressed,
required this.identifier,
}) : super(key: key);
final double? height;
final IconData icon;
final String label;
final bool willNavigate;
final Widget? route;
final VoidCallback? onPressed;
final String identifier;
#override
Widget build(BuildContext context) {
return SizedBox(
width: 70,
height: height ?? 80,
child: Column(
children: [
ClipOval(
child: Material(
color: Palette.aliceBlue,
child: IconButton(
onPressed: () async {
increaseUsage(identifier: identifier);
willNavigate ? navigate(context, route) : onPressed;
},
icon: Icon(
icon,
color: Palette.celticBlue,
size: 25,
),
),
),
),
const Spacer(),
Text(
label,
style: TextStyle(
color: Palette.oxfordBlue,
fontSize: 12,
),
textAlign: TextAlign.center,
),
const Spacer(
flex: 3,
),
],
),
);
}
}
This widget must have an icon, a label, an identifier and it should be specified if this widget willNavigate to a route or not.
If it should navigate, a route should be specified. If it shouldn't navigate, a onPressed function should be specific.
I also have a list of predefined actions built:
List<MenuAction> actions = [
MenuAction(
icon: Icons.cloud_download_outlined,
label: "Sincronizar",
willNavigate: false,
onPressed: () async {},
identifier: "syncDat",
),
MenuAction(
icon: Icons.business_center_outlined,
label: "Proposta Comercial",
willNavigate: true,
route: AppRoutes.comercialProposalList,
identifier: "propCom",
),
MenuAction(
icon: Icons.shopping_cart_outlined,
label: "Pedidos",
willNavigate: true,
route: AppRoutes.orderList,
identifier: "order",
),
MenuAction(
icon: Icons.cloud_upload_outlined,
label: "Enviar Pedidos",
willNavigate: false,
onPressed: () {},
identifier: "sendOrd",
),
MenuAction(
icon: Icons.receipt_long_rounded,
label: "Nota Fiscal",
willNavigate: true,
route: AppRoutes.invoiceList,
identifier: "invoice",
),
MenuAction(
icon: Icons.person_outline,
label: "Clientes",
willNavigate: true,
route: AppRoutes.clientsList,
identifier: "clients",
),
MenuAction(
icon: Icons.map_outlined,
label: "Endereços",
willNavigate: true,
route: AppRoutes.addressesList,
identifier: "address",
),
MenuAction(
icon: Icons.inventory_2_outlined,
label: "Estoque",
willNavigate: true,
route: AppRoutes.inventoryList,
identifier: "invent",
),
];
I access these actions through this function:
MenuAction getActionByIdentifier({String identifier = ""}) {
return actions.elementAt(
actions.indexWhere((element) => element.identifier == identifier),
);
}
Why go through all this trouble to create some simple widgets? they'll be used at my app's home screen, where all actions will be available in a horizontal slider. Also, the most used actions will be displayed below to the user. A count of how many times said action was used is stored in my local database along with the action's identifier, allowing me to retrieve and create it later using the function described earlier. Why did I did it this way? because I wanted to code my action only once and access it from different sources.
Now, if I create my actions statically, their onPressed event works flawlessly.
This is the Widget that creates all my actions statically:
class HomeActionsListView extends StatefulWidget {
const HomeActionsListView({
Key? key,
}) : super(key: key);
#override
State<HomeActionsListView> createState() => _HomeActionsListViewState();
}
class _HomeActionsListViewState extends State<HomeActionsListView> {
#override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.horizontal,
children: [
getActionByIdentifier(identifier: "syncDat"),
getActionByIdentifier(identifier: "propCom"),
getActionByIdentifier(identifier: "order"),
getActionByIdentifier(identifier: "sendOrd"),
getActionByIdentifier(identifier: "invoice"),
getActionByIdentifier(identifier: "clients"),
getActionByIdentifier(identifier: "address"),
getActionByIdentifier(identifier: "invent"),
],
);
}
}
Keep in mind that the onPressed event is ALWAYS present within my widget, since ALL actions increase their usage count in the database. As you can see in the code, the increaseUsage is called and then the widget will decide if it will only navigate to the provided route OR execute the specified custom onPressed function.
But for some reason, when I create the same widgets using the getActionByIdentifier function and try to pass it's label and onPressed event to a custom Card I built, the event doesn't trigger.
This is the FutureBuilder that creates my cards dynamically within a ListView based on the first most used actions:
FutureBuilder(
future: getMostUsedActions(),
builder: (BuildContext context,
AsyncSnapshot<List<String>> snapshot) {
if (snapshot.hasData) {
if (snapshot.data!.isNotEmpty) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
MenuAction action = getActionByIdentifier(
identifier: snapshot.data![index],
);
return RecentActionCard(
text: action.label,
onPressed: action.onPressed,
);
},
);
} else {
return const Center(
heightFactor: 15,
child: Text(
"Nenhuma ação utilizada recentemente!",
),
);
}
} else {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
}
},
);
This is the code to my custom Card:
class RecentActionCard extends StatelessWidget {
const RecentActionCard({
Key? key,
required this.text,
this.onPressed,
}) : super(key: key);
final String text;
final VoidCallback? onPressed;
#override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: Palette.celticBlue,
width: 2,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
fit: FlexFit.tight,
child: Padding(
padding: const EdgeInsets.only(left: 30),
child: Text(
text,
style: TextStyle(
fontFamily: "Barlow",
fontSize: 16,
fontWeight: FontWeight.bold,
color: Palette.oxfordBlue,
),
),
),
),
SizedBox(
height: 55,
width: 55,
child: IconButton(
onPressed: onPressed,
icon: Icon(
Icons.arrow_forward_ios,
color: Palette.oxfordBlue,
),
),
)
],
)
],
),
);
}
}
Finally, my question is: Why do my statically created widget's onPressed event work and when I try to create them dinamically they are returned as null?
Also, any way to make it work?
And, last but not least, if you have any suggestions on how to improve my code, they'll be much appreciated.

Related

How do I create a function that changes data on page so I don't have to create multiple different pages for each stall

I have to create multiple files for different stalls but it seems so wrong and I know there's a better way but I just don't know how. Is there a way to create something like a page builder that will let me create multiple pages with different information from a single file. The difficult part is to make the onTap function of the images send the user to the stall_page of the selected stall. I tried doing this by making a view attribute in which I create a page and manually import the page route. But that involves creating a stall_info and stall_page for every single stall.
Instead of creating stall1_page, stall2_page and so on, can I create a generic stall function that will use the same page but just change the data? I know that's LITERALLY the point of object oriented programming languages but I'm really new to them as you'll tell my previous stupid questions.
This is the homescreen dashboard
class GridDashboard extends StatelessWidget {
Item item1 = Item(
title: 'Tray blazers',
subtitle: 'Open',
event: 'by Chef Tracy',
img: 'assets/images/tray_blazers-cr.png',
view: stallPage,
);
Item item2 = Item(
title: 'Papa Rimz',
subtitle: 'Open',
event: '',
img: 'assets/images/papa_rimz.png',
view: papaRimzPage,
);
Item item3 = Item(
title: 'W SAUCE',
subtitle: 'Open',
event: '',
img: 'assets/images/w_sauce-removebg.png',
view: wSaucePage,
);
Item item4 = Item(
title: 'African Kitchen',
subtitle: 'Open',
event: '',
img: 'assets/images/cherry-kitchen.png',
view: africanKitchenPage,
);
Item item5 = Item(
title: 'Suya Craze',
subtitle: 'Open',
event: '',
img: 'assets/images/suya_craze.png',
view: suyaCrazePage,
);
Item item6 = Item(
title: 'Zulkys cafe',
subtitle: 'Open',
event: '',
img: 'assets/images/zulkys-removeb.png',
view: zulkysCafePage,
);
Item item7 = Item(
title: 'Street food',
subtitle: 'Open',
event: '',
img: 'assets/images/street_food--removebg-.png',
view: streetFoodPage,
);
#override
Widget build(BuildContext context) {
List<Item> myList = [
item1,
item2,
item3,
item4,
item5,
item6,
item7,
];
return Flexible(
child: GridView.count(
childAspectRatio: 1.0,
padding: const EdgeInsets.only(left: 16, right: 16),
crossAxisCount: 2,
crossAxisSpacing: 18,
mainAxisSpacing: 18,
children: myList.map(
(data) {
return Container(
decoration: BoxDecoration(
color: const Color(0xff453658),
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(data.view);
},
child: Image.asset(
data.img,
width: 90, //double.infinity
),
),
const SizedBox(height: 14),
Text(
data.title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
data.subtitle,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 10,
color: Colors.white38,
),
),
const SizedBox(height: 8),
// Text(
// data.event,
// style: const TextStyle(
// fontWeight: FontWeight.w600,
// fontSize: 11,
// color: Colors.white70,
// ),
// ),
],
),
);
},
).toList(),
),
);
}
}
class Item {
String title;
String subtitle;
String event;
String img;
String view;
Item({
required this.title,
required this.subtitle,
required this.event,
required this.img,
required this.view,
});
}
This is my stall_page:
class StallPage extends StatefulWidget {
const StallPage({super.key});
#override
State<StallPage> createState() => _StallPageState();
}
class _StallPageState extends State<StallPage> {
var selected = 0;
final pageController = PageController();
final stall = Stall.generateRestaurant1();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xff392850), //kBackground,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomAppBar(
Icons.arrow_back_ios_outlined,
Icons.search_outlined,
leftCallback: () => Navigator.of(context).pop(),
),
StallInfo(), //
FoodList(
selected,
(int index) {
setState(() {
selected = index;
});
pageController.jumpToPage(index);
},
stall,
),
Expanded(
child: FoodListView(
selected,
(int index) {
setState(() {
selected = index;
});
},
pageController,
stall,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 25),
height: 60,
child: SmoothPageIndicator(
controller: pageController,
count: stall.menu.length,
effect: CustomizableEffect(
dotDecoration: DotDecoration(
width: 8,
height: 8,
color: Colors.grey.withOpacity(0.5),
borderRadius: BorderRadius.circular(8),
),
activeDotDecoration: DotDecoration(
width: 10,
height: 10,
color: kBackground,
borderRadius: BorderRadius.circular(10),
dotBorder: const DotBorder(
color: kPrimaryColor,
padding: 2,
width: 2,
),
),
),
onDotClicked: (index) => pageController.jumpToPage(index),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: kPrimaryColor,
elevation: 2,
child: const Icon(
Icons.shopping_cart_outlined,
color: Colors.black,
size: 30,
),
),
);
}
}
This is my stall_info
class StallInfo extends StatelessWidget {
final stall = Stall.generateRestaurant1();
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
stall.name,
style: const TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Row(
children: [
Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.blueGrey.withOpacity(0.4),
borderRadius: BorderRadius.circular(5),
),
child: Text(
stall.label,
style: const TextStyle(
color: Colors.white,
),
)),
const SizedBox(
width: 10,
),
],
)
],
),
ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset(
stall.logoUrl,
width: 80,
),
),
],
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
stall.desc,
style: const TextStyle(fontSize: 16),
),
Row(
children: [
const Icon(
Icons.star_outline,
color: Colors.amber,
),
Text(
'${stall.score}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 15),
],
)
],
)
],
),
);
}
}
And this is stall
class Stall {
String name;
String label;
String logoUrl;
String desc;
num score;
Map<String, List<Food>> menu;
Stall(
this.name,
this.label,
this.logoUrl,
this.desc,
this.score,
this.menu,
);
static Stall generateRestaurant1() {
return Stall(
'Tray blazers',
'Restaurant',
'assets/images/tray_blazers.jpg',
'Tray Blazers by Chef Tracy',
4.5,
{
'Recommended': Food.generateRecommendedFoods1(),
'Popular': Food.generatePopularFoods1(),
'Smoothie': [],
'Rice': [],
},
);
}
}
If I understand the question correctly, you want to open the StallPage but show different values on the page depending on which image (pertaining to a given 'Stall') was selected on the previous page? I.e. clicking on item2 should open the StallPage with the restaurant title "Papa Rimz" etc.?
In that case, you can pass the argument to your new route builder via the onTap() function as a constructor parameter instead of calling Stall.generateRestaurant1() with hardcoded values in a given dart file.
StallInfo
Instead of getting your stall data inside the build method, you simply accept it as a required parameter for your widget. Now you have access to the data (title, ...) anywhere inside here.
class StallInfo extends StatelessWidget {
// Contains the stall object with its name, label, menu etc.
final Stall stall;
StallInfo({super.key, required this.stall});
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
...
),
);
}
}
HomeScreen
I'm a bit confused as to what the item list in your your home screen is for. Are these food items in a restaurant? Because if so, I think it would be much easier to save them inside the stall as a list of items and then use that list here:
List<Stall> _stalls = [...];
I'd like to note here that you hardcoded all the items by name and then, in your build method, added them to a list. Since you don't need their names anywhere, it would be just a little bit better to move the List<Stall> myList outside the build method and simply assign the objects directly (that is, before you add a real database):
class GridDashboard extends StatelessWidget {
List<Stall> _stalls = [
Stall('Tray blazers', ...),
Stall('Papa Rimz', ...),
];
#override
Widget build(BuildContext context) {
// do something with your stalls, onTap, pass the element directly
....
children: _stalls.map(
(data) {
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => StallPage(stall: data)
));
}
);
}),
}
}
If you use a builder function for your GridView (which you should if there can be a lot of stalls), in the onTap() you can instead call:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => StallPage(stall: _stalls.elementAt(index))
));
StallPage
This page will look something like this
class StallPage extends StatefulWidget {
final Stall stall; // Take in the stall you passed from your home screen
const StallPage({super.key, required this.stall});
#override
State<StallPage> createState() => _StallPageState();
}
class _StallPageState extends State<StallPage> {
var selected = 0;
final pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
...
StallInfo(stall: widget.stall), // This is how you can access the values passed inside a StatefulWidget
...
);
}
}

My Flutter ListView is always removing the last item from the list

I'm creating a Flutter Widget and when I try to remove an item from the list I'm using, it always removes the last one, I was thinking it could be a Key problem, but nothing suits it, do anyone know how I could solve this?
The code
create_game.dart
import 'package:flutter/material.dart';
import 'package:pontinho/components/custom_input.dart';
class CreateGame extends StatefulWidget {
const CreateGame({super.key});
#override
State<CreateGame> createState() => _CreateGameState();
}
class _CreateGameState extends State<CreateGame> {
List<String> names = [''];
void changeName(int nameIndex, String change) {
setState(() {
names[nameIndex] = change;
});
}
void removeName(int nameIndex) {
print(names);
print(nameIndex);
setState(() {
names.removeAt(nameIndex);
});
}
ListView createNamesInput() {
return ListView.builder(
itemCount: names.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
key: ObjectKey(index),
title: CustomInput(
key: ObjectKey(index),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
);
},
);
// return names
// .asMap()
// .entries
// .map((el) => CustomInput(
// key: ObjectKey('${el.key}'),
// labelText: "Nome",
// onChanged: changeName,
// index: el.key,
// text: names[el.key],
// onRemoved: removeName,
// ))
// .toList();
}
void addName() {
setState(() {
names.add('');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: GestureDetector(
onTap: (() => Navigator.pop(context)),
child: const Icon(
Icons.arrow_back,
color: Colors.black,
size: 40,
),
),
backgroundColor: Colors.white,
titleTextStyle: const TextStyle(
color: Colors.black,
fontSize: 20,
),
title: const Text("CRIE SEU JOGO"),
),
body: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
// child: createNamesInput(),
child: Column(
children: [
createNamesInput(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: addName,
child: Row(
children: const [
Icon(Icons.add),
Text('Adicionar Jogador'),
],
),
),
],
),
),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () => print('Iniciar!'),
child: const Text('Iniciar!'),
),
)
],
),
),
);
}
}
custom_input.dart
import 'package:flutter/material.dart';
typedef OneArgumentCallback = void Function(String changed);
class CustomInput extends StatefulWidget {
final OneArgumentCallback onChanged;
final VoidCallback onRemoved;
final String labelText;
final String text;
const CustomInput({
super.key,
required this.onChanged,
required this.labelText,
required this.text,
required this.onRemoved,
});
#override
State<CustomInput> createState() => _CustomInputState();
}
class _CustomInputState extends State<CustomInput> {
late final TextEditingController inputController;
#override
void initState() {
super.initState();
inputController = TextEditingController(text: widget.text);
}
void changeContent(String value) {
widget.onChanged(
value,
);
}
#override
Widget build(BuildContext context) {
return TextFormField(
key: widget.key,
controller: inputController,
textDirection: TextDirection.ltr,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: widget.labelText,
suffixIcon: IconButton(
onPressed: () => widget.onRemoved(),
icon: const Icon(
Icons.close,
color: Colors.red,
),
),
),
autocorrect: false,
onChanged: (value) => changeContent(value),
);
}
}
Indeed it is a key issue, you have to create a combined key that must be unique for each item, I merged the index with names[index],
CustomInput(
key: ObjectKey('$index:${names[index]}'),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
note that if you try this code alone the textfield will lose focus because the key has changed, this will be solved by removing the setState inside the onChange
void changeName(int nameIndex, String change) {
names[nameIndex] = change;
}
here you don't need setState because the UI will be updated by default when you are typing in the textfield
I hope I made it clear
I was thinking it could be a Key problem
That's correct; You need to use names[index] as the value for your Key:
ListTile(
key: ObjectKey(names[index]),
title: CustomInput(

I have created GenderWidget for male female...want to change colour of selected gender...how to do it on tap

I have created gender widget for selecting gender male in BMI app learning, female... here I want to show selected gender with some colour difference...on tap
I don't know what I am missing to complete it...there should be a bool variable but hw to let it know what gender is clicked...
here is my coding..
Row(
children: [
Expanded(
child: MyContainer(
child: GenderWidget(onclick:(){
maleselected=true;
femaleselected=false;
setState(() {
});
},title: 'Male',
icon: Icons.male,
)
)),
Expanded(
child: MyContainer(
child: GenderWidget(
onclick: (){
maleselected=false;
femaleselected=true;
},
title: 'Female',
icon: Icons.female,
)
)),
],
),
and here is my custom widget
class GenderWidget extends StatelessWidget {
final VoidCallback onclick;
final String title;
final IconData icon;
GenderWidget({
required this.onclick,
required this.title,
required this.icon,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onclick,
child: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
title,
style: mytextgender,
),
Icon(
icon,
////??? What variable should i use to finish
// color: isselected==true?Colors.red:Colors.black,
size: 80,
),
SizedBox(
height: 20,
//??? What variable should i use to finish
//child: isselected==true?Text('Selected'):null,
)
],
)),
),
);
}
You can use another variable to GenderWidget for selected,
class GenderWidget extends StatelessWidget {
final VoidCallback onclick;
final String title;
final IconData icon;
final bool isSelected;
GenderWidget({
required this.isSelected,
required this.onclick,
required this.title,
required this.icon,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onclick,
child: Container(
color:
isSelected ? Colors.purple : null, //change color based on your need
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
title,
),
Icon(
icon,
////??? What variable should i use to finish
color: isSelected == true ? Colors.red : Colors.black,
size: 80,
),
SizedBox(
height: 20,
//??? What variable should i use to finish
child: isSelected == true ? Text('Selected') : null,
)
],
)),
),
);
}
}
Using enum will be handy
enum Gender {
male,
female,
//....
}
Use like
Row(
children: [
Expanded(
child: GenderWidget(
onclick: () {
selected = Gender.male;
setState(() {});
},
isSelected: Gender.male == selected,
title: 'Male',
icon: Icons.male,
)),
Expanded(
child: GenderWidget(
isSelected: Gender.female == selected,
onclick: () {
selected = Gender.female;
setState(() {});
},
title: 'Female',
icon: Icons.female,
)),
],
),

Data not updating in DB Sqflite Flutter

The task text in edit_todo_screen is not updated. I'm using the same code as in item_tasks, where I change the status of the task to move between "Done" and "Archived" - everything works well here. I tried to change only the status in edit_todo_screen, but it does not change, although the code is identical to the code in item_tasks. Perhaps the problem is that I'm not passing the parameters correctly to edit_todo_screen. I need to be able to change the status of the task and the text of the task itself in edit_todo_screen. Attached below is a screenshot of the error that occurs when clicking the button in edit_todo_screen
Tell me, please, what could be my mistake?
cubit_db
class AppCubit extends Cubit<AppStates> {
AppCubit() : super(AppInitialState());
static AppCubit get(context) => BlocProvider.of(context);
void updateDatabase(String status, int id) async {
database!.rawUpdate(
'UPDATE tasks SET status = ? WHERE id = ?', [status, id]).then((value) {
getDataBase(database);
emit(AppUpdateDatabaseState());
});
}
void createDatabase() {
openDatabase(
'todo.db',
version: 1,
onCreate: (database, version) {
database
.execute(
'CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT, status TEXT)')
.then((value) => print('Table Created'))
.catchError((error) {
print('Error When Creating Table ${error.toString()}');
});
},
onOpen: (database) {
getDataBase(database);
print('database opened');
},
).then((value) {
database = value;
emit(AppCreateDatabaseState());
});
}
inserToDatabase({required String title}) async {
await database!.transaction((txn) async {
txn
.rawInsert(
'INSERT INTO tasks (title, status) VALUES ("$title","New")')
.then((value) {
getDataBase(database);
print('$value Inserted Successfully');
emit(AppInsertDatabaseState());
}).catchError((error) {
print('Error When inserting Table ${error.toString()}');
});
});
}
new_tasks_list
class NewTasksScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocConsumer<AppCubit, AppStates>(
listener: (context, state) {},
builder: (context, state) {
var tasks = AppCubit.get(context).newTasks;
return SingleChildScrollView(
child: Column(children: [
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: tasks.length,
itemBuilder: (context, index) => TaskItem(tasks: tasks[index]),
),
]),
);
},
);
tasks_item
class TaskItem extends StatelessWidget {
Map? tasks;
TaskItem({this.tasks});
#override
Widget build(BuildContext context) {
return Card(
key: Key(tasks!['title']),
shadowColor: Colors.blueGrey,
margin: const EdgeInsets.only(left: 15, right: 15, top: 8),
color: Colors.black,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey.shade800, width: 0.5),
borderRadius: BorderRadius.circular(10),
),
borderOnForeground: false,
child: ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(
// '${state.loadedUser[index].description}',
tasks!['title'],
style: const TextStyle(
fontSize: 21.0,
// fontWeight: FontWeight.bold,
),
),
// Text(
// tasks!['status'],
// style: const TextStyle(fontSize: 21.0),
// ),
]),
trailing: IconButton(
tooltip: 'Archive Todo',
highlightColor: Colors.red,
onPressed: () {
AppCubit.get(context).updateDatabase('Archive', tasks!['id']);
},
icon: const Icon(
Icons.archive,
color: Colors.white,
),
),
leading: IconButton(
tooltip: 'Done Todo',
highlightColor: Colors.green,
onPressed: () {
AppCubit.get(context).updateDatabase('Done', tasks!['id']);
},
icon: const Icon(
Icons.check,
color: Colors.white,
),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditTodoScreen(
title: tasks!['title'],
id: tasks!['id'],
),
),
);
},
),
);
}
}
edit_todo_screen
class EditTodoScreen extends StatelessWidget {
// Map? tasks;
String title;
int id;
EditTodoScreen({Key? key, required this.title, required this.id})
: super(key: key);
final _controller = TextEditingController();
#override
Widget build(BuildContext context) {
_controller.text = title;
return BlocConsumer<AppCubit, AppStates>(
listener: (context, state) {},
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Edit Todo',
style: TextStyle(fontSize: 20.0),
),
),
body: _body(context),
);
});
}
Widget _body(context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
TextFormField(
controller: _controller,
autocorrect: true,
maxLines: 2,
decoration: const InputDecoration(hintText: 'Enter todo message'),
),
const SizedBox(
height: 10.0,
),
// ElevatedButton(
// // style:,
// onPressed: () {
// AppCubit.get(context).updateDatabase('Done', id);
// },
// child: Text(
// 'Update Data',
// style: TextStyle(color: Colors.amber.shade700),
// ),
// ),
InkWell(
onTap: () {
AppCubit.get(context).updateDatabase('Done', id);
Navigator.pop(context);
},
child: _updateBtn(context),
)
],
),
);
}
Widget _updateBtn(context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 50.0,
decoration: BoxDecoration(
color: Colors.black, borderRadius: BorderRadius.circular(10.0)),
child: Center(
child: Text(
'Update Todo',
style: TextStyle(
fontSize: 17.0,
color: Colors.amber.shade700,
fontWeight: FontWeight.bold),
),
),
);
}
}
I think your problem has to do with the fact that database is not set in the second case. The code fails because you try to access a null value that then is checked with the "!" operator. Look where you set the database and check if that code is called in both code flows.
edit:
I think this line in edit todo screen is your problem: AppCubit.get(context).updateDatabase('Done', id);. If I am not mistaken AppCubit.get(context) returns null. The easiest way to check if I am right would be to replace it with the following:
final appCubit = AppCubit.get(context);
print('$appCubit');
appCubit.updateDatabase('Done', id);
If I am right, you should see "null" in your terminal.
What I think happens is, that the app cubit is not provided in the context anymore, because you pushed the todo screen as a new screen on the navigator. With that, the context that is provided is that of the navigator (or probably the material app in your case) which is above the point where you provide the AppCubit.
I am kind of guessing though, because I only see half of your code. I hope it helps nevertheless. :)

type 'Null' is not a subtype of type 'Icon'

after trying to add an icon to my list tile I constantly get this error:
════════ Exception caught by widgets library ═══════════════════════════════════
The following _TypeError was thrown building Alertbox(dirty, state: _AlertboxState#08465):
type 'Null' is not a subtype of type 'Icon'
The relevant error-causing widget was
Alertbox
lib/screens/team_screen.dart:58
When the exception was thrown, this was the stack
#0 _AlertboxState.build
package:trainings_app/widgets/alertbox_widget.dart:76
#1 StatefulElement.build
package:flutter/…/widgets/framework.dart:4691
#2 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4574
#3 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:4746
#4 Element.rebuild
package:flutter/…/widgets/framework.dart:4267
...
════════════════════════════════════════════════════════════════════════════════
Here are the classes referenced by the error:
team_screen
void newTeam() {
showDialog<Alertbox>(
context: context,
builder: (BuildContext context) {
return Alertbox('Namen auswählen:', addTeam);
},
);
}
alertbox_widget: the error gets caused by "onSubmitted" inside the TextField
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:trainings_app/config/palette.dart';
class Alertbox extends StatefulWidget {
final String title;
final Function(String, Icon) parseText;
const Alertbox(this.title, this.parseText);
#override
_AlertboxState createState() => _AlertboxState(title, parseText);
}
class _AlertboxState extends State<Alertbox> {
final String title;
final Function(String, Icon) parseText;
final textController = TextEditingController();
_AlertboxState(this.title, this.parseText);
var iconChoice;
List icons = [
Icons.sports_volleyball_outlined,
Icons.sports_handball_outlined,
Icons.sports_baseball_outlined,
];
#override
void dispose() {
textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding: EdgeInsets.all(10),
child: Center(
child: Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(const Radius.circular(20)),
color: Colors.white,
),
padding: const EdgeInsets.all(16.0),
child: SizedBox(
height: 100,
child: Column(
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
),
),
Row(
children: [
DropdownButton(
value: iconChoice,
onChanged: (newIcon) {
setState(() {
iconChoice = newIcon;
});
},
items: icons.map((icon) {
return DropdownMenuItem(
value: icon,
child: Icon(icon),
);
}).toList(),
),
SizedBox(width: 12),
Expanded(
child: TextField(
onSubmitted: parseText(textController.text, iconChoice),
autofocus: true,
textAlign: TextAlign.center,
controller: textController,
),
),
SizedBox(width: 12),
ElevatedButton(
onPressed: () {
parseText(textController.text, iconChoice);
},
child: const Icon(CupertinoIcons.checkmark),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Palette.orange),
),
),
SizedBox(width: 8),
],
),
],
),
),
),
),
);
}
}
I hope anyone can help me or has some kind of solution/work-a-round that can help fix that problem
So it took me a while to figure out whats the problem, but I think I got it.
I don't know exactly your planned functionality - for e.g. when the fields are empty (text and/or icon), are you doing any validation? You should probably catch that with deactivating the button or displaying an error message. But apart from that:
// method which is called when clicking the submit button
addTeam(String text, Icon icon) {
// do whatever you do in here
log('$text $icon');
}
class Alertbox extends StatefulWidget {
// you dont need to pass the title to the state, you can access it via
// widget.title, same for the onParseText method
final String title;
final Function(String, Icon) onParseText;
const Alertbox(this.title, this.onParseText);
#override
_AlertboxState createState() => _AlertboxState();
}
class _AlertboxState extends State<Alertbox> {
final textController = TextEditingController();
// you somehow messed up Icon and IconData, try to define which type it is
// it can be null since there is no default icon set
Icon? iconChoice;
// also define the type for the list
List<Icon> icons = [
Icon(Icons.sports_volleyball_outlined),
Icon(Icons.sports_handball_outlined),
Icon(Icons.sports_baseball_outlined),
];
#override
void dispose() {
textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding: EdgeInsets.all(10),
child: Center(
child: Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(const Radius.circular(20)),
color: Colors.white,
),
padding: const EdgeInsets.all(16.0),
child: SizedBox(
height: 100,
child: Column(
children: [
Text(
widget.title,
style: TextStyle(
fontSize: 18,
),
),
Row(
children: [
// you can define the value type of the Dropdown, set it
// to Icon
DropdownButton<Icon>(
value: iconChoice,
onChanged: (newIcon) {
setState(() {
iconChoice = newIcon;
});
},
items: icons.map((icon) {
return DropdownMenuItem(
value: icon,
child: icon,
);
}).toList(),
),
SizedBox(width: 12),
Expanded(
child: TextField(
onSubmitted: (text) {
// this is what causes the error.
// you have to check if iconChoice is null
// I'm not really sure why you use onSubmitted
// here since you already have the submitbutton?
if (iconChoice != null) {
widget.onParseText(text, iconChoice!);
}
},
autofocus: true,
textAlign: TextAlign.center,
controller: textController,
),
),
SizedBox(width: 12),
ElevatedButton(
onPressed: () {
// you need to make sure iconChoice is not null
// before calling that method. Use some kind of
// validation here, like mentioned above
widget.onParseText(textController.text,
iconChoice!);
},
child: const Icon(CupertinoIcons.checkmark),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.orange),
),
),
SizedBox(width: 8),
],
),
],
),
),
),
),
);
}
}