I am new to Flutter. I am facing an issue with the showModalBottomSheet function of the Material package. Here is the output I am looking for-
Output- When the Add Button(floating action button) is pressed, the showModalBottomSheet will be triggered. It will pop up with a form containing TextFields and a submit Button. When the user fills in and submits the form, the data will be converted into a list and added to the HomePage with state change (The Homepage-main. dart is a stateful Widget.)
Problem- I've tried 2 ways to solve this issue & facing 2 different problems-
Test 1- I tried to pass the main context from the Material build function to the showModalBottomSheet. Problem- I got this error-
No MediaQuery widget ancestor was found.
Test-2 At this time I surrounded my showModalBottomSheet with a Builder widget and pass a separate context. Problem- At this time there is no MediaQuery error but a logical error. When I am trying to fill the TextField inside showModalBottomSheet, it automatically got refreshes when I leave the TextField and go to another TextField.For instance, when I input Title and pressed the Amount field, the Title field got empty.
Here is the code of the main. dart file-
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import './widgets/addtask.widget.dart';
import './widgets/transactionlist.widget.dart';
void main() => runApp(const HomePage());
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List transactions = [];
String currentTime() {
final DateTime now = DateTime.now();
final DateFormat formatter = DateFormat('yyyy-MM-dd');
return formatter.format(now);
}
void addTaskBtn(String title, double amount) {
final newTransaction = {
'title': title,
'amount': amount,
'time': currentTime()
};
setState(() {
transactions.add(newTransaction);
});
}
void startAddNewTransaction(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) => GestureDetector(
child: AddTask(addTaskBtn),
onTap: () => null,
behavior: HitTestBehavior.opaque,
),
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text(
'Expense Tracker',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.red[900],
actions: [
IconButton(
onPressed: () => startAddNewTransaction(context),
icon: Icon(Icons.add))
],
),
body: SingleChildScrollView(
child: Column(
children: [
// Chart
Container(
padding: const EdgeInsets.all(10),
child: const Card(
child: Text('CHART'),
),
),
TransactionList(transactions),
// Testing if TextField really works n it is working outside showModalBottomSheet function.It works.
AddTask(addTaskBtn)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => startAddNewTransaction(context),
child: Icon(Icons.add),
),
// floatingActionButton: Builder(
// builder: (bContext) => FloatingActionButton(
// child: Icon(Icons.add),
// onPressed: () => startAddNewTransaction(bContext))),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
),
);
}
}
Special Note-
- I've tried with GestureDetector & without GestureDetector.Same output.
- I've tested the AddTask widget(custom widget containing TextField and Submit Button-a stateless widget) in the Homepage.It works perfectly. But when it is used inside showModalBottomSheet,it gives weird behavior.
Edit- Here is the code for the AddTask custom widget-
import 'package:flutter/material.dart';
class AddTask extends StatelessWidget {
final Function addTaskBtn;
final titleController = TextEditingController();
final amountContr4oller = TextEditingController();
AddTask(this.addTaskBtn);
void addNewTransaction() {
final title = titleController.text;
final amount = double.parse(amountContr4oller.text);
if (title != '' || amount >= 0) {
addTaskBtn(title, amount);
}
}
@override
Widget build(BuildContext context) {
return Card(
child: Column(children: [
TextField(
controller: titleController,
decoration: const InputDecoration(labelText: 'Title'),
),
TextField(
keyboardType: TextInputType.number,
// onSubmitted: (x) => this.addNewTransaction(),
controller: amountContr4oller,
decoration: const InputDecoration(labelText: 'Amount'),
),
Container(
margin: const EdgeInsets.all(10),
child: TextButton(
onPressed: this.addNewTransaction, child: const Text('ADD')),
)
]),
);
}
}
CodePudding user response:
You need to add a MaterialApp
to your code.
Now, although you did add a MaterialApp
in your code:
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text(
'Expense Tracker',
but there's no MaterialApp
above this context
.
So, the easiest way to fix the problem is to add a MaterialApp
as a top-level widget, meaning in your main
function:
void main() => runApp(
MaterialApp(home: const HomePage()),
);
CodePudding user response:
Finally, I got the answer. It took me a long time to figure it out. I am sharing my answer here and explaining it as I think it might help someone in the future. The solution is very simple. I just converted the TextField inside the showModalBottomSheet function from a Stateless widget to a Stateful widget(See the AddTask custom widget, it was a Stateless Widget at first).
Explanation- Let's see what is going on under the hood. Flutter reevaluates its Widgets from time to time. And it reevaluates when the user moves from one TextField to another TextField inside the showModalBottomSheet function. So, if you use Stateless Widget, at the time of reevaluation, the old state is just gone. Because the Stateless widget can't hold any data. In the case of the Stateful widget, though the Widget itself got reevaluated, the State is not changed. Because State is separate from the widget itself. That's why after the evaluation, the data remains.
Here is my updated code for AddTask Custom Widget-
import 'package:flutter/material.dart';
class AddTask extends StatefulWidget {
final Function addTaskBtn;
AddTask(this.addTaskBtn);
@override
State<AddTask> createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
final titleController = TextEditingController();
final amountContr4oller = TextEditingController();
void addNewTransaction() {
final title = titleController.text;
final amount = double.parse(amountContr4oller.text);
if (title != '' || amount >= 0) {
widget.addTaskBtn(title, amount);
}
}
@override
Widget build(BuildContext context) {
return Card(
child: Column(children: [
TextField(
controller: titleController,
decoration: const InputDecoration(labelText: 'Title'),
),
TextField(
keyboardType: TextInputType.number,
onSubmitted: (x) => this.addNewTransaction(),
controller: amountContr4oller,
decoration: const InputDecoration(labelText: 'Amount'),
),
Container(
margin: const EdgeInsets.all(10),
child: TextButton(
onPressed: this.addNewTransaction, child: const Text('ADD')),
)
]),
);
}
}