I'm new to flutter, I trying to pass a value from textfield and when i click a button submit, display it in textformfield in another screen, my problem, I don't know the right way to get value
Some Code :
String txt = "";
TextEditingController controllerTxt = new TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text('Create'),
actions: <Widget>[
FlatButton(
child: Text('Submit'),
textColor: Colors.white,
onPressed: () {
setState(() {
//txt = (controllerTxt.text);
Navigator.pushNamed(context, '/ResultPage');
});
},
),
],
),
body: new Container(
child: new Column(
children: <Widget>[
new TextField(
controller: controllerTxt,
maxLines: 5,
decoration: new InputDecoration(
),
),
],
),
),
);
}
}
class _ResultPageState extends State<ResultPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text('Result'),
),
body: new Container(
padding: EdgeInsets.all(10.0),
child: new Column(
children: <Widget>[
new TextFormField(
decoration: InputDecoration(
labelText: 'Name :',
),
),
new Text("${controllerTxt.text}"),
],
),
),
);
}
}
I have done the same thing by passing data through the constructor
Navigator.push(context,
MaterialPageRoute(builder: (context) => ResultPage(controllerTxt.text)));
class ResultPage extends StatefulWidget {
final String result;
ResultPage(this.result);
Related
I am a beginner in Flutter. I am trying to add a new list item widget to screen when floating action button is pressed. How do I achieve this?
I am trying to create a list of items. When the floating action button is clicked, a dialog box is prompted and user is asked to enter details. I want to add a new list item with these user input details.
This is my input_page.dart file which I am calling in main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MedPage extends StatefulWidget {
#override
_MedPageState createState()=> _MedPageState();
}
class _MedPageState extends State<MedPage> {
Future<String>createAlertDialog(BuildContext context) async{
TextEditingController customController= new TextEditingController();
return await showDialog(context: context,builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: (){
Navigator.of(context).pop(customController.text.toString()); // to go back to screen after submitting
}
)
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
Expanded(
child: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
ReusableListItem(Color(0xFFd2fddf),"Name 1"),
ReusableListItem(Colors.orange,"Name 2"),
ReusableListItem(Color(0xFF57a1ab), "Name 3"),
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
print("Clicked");
createAlertDialog(context).then((onValue){
print(onValue);
setState(() {
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour,this.pill);
Color colour;
String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colour,
borderRadius: BorderRadius.circular(15.0)
),
child: Center(
child: Text(pill)
),
);
}
}
You don't need to change much in your code, maintain a variable that stores the values entered to be able to show them in the list. You should use Listview.builder() in order to dynamically render the items.
Here's your code:
class MedPage extends StatefulWidget {
#override
_MedPageState createState() => _MedPageState();
}
class _MedPageState extends State<MedPage> {
List<String> items = [];
Future<String> createAlertDialog(BuildContext context) async {
TextEditingController customController = new TextEditingController();
return await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop(customController.text
.toString()); // to go back to screen after submitting
})
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return ReusableListItem(Color(0xFFd2fddf), items[index]);
},
itemCount: items.length,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print("Clicked");
createAlertDialog(context).then((onValue) {
// print(onValue);
setState(() {
items.add(onValue);
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour, this.pill);
final Color colour;
final String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration:
BoxDecoration(color: colour, borderRadius: BorderRadius.circular(15.0)),
child: Center(child: Text(pill)),
);
}
}
Firstly you need to use ListView.builder() rather than ListView because you have dynamic content. Also you need to hold your items in a list.
// create a list before
ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Text(list[index]);
}
)
When you click on FloatingActionButton() you will call AlertDialog() method.
FloatingActionButton(
onPressed: (){
AlertDialog(
content: Form(), // create your form here
actions: [
// add a button here
]
)
})
This method will show a dialog(you will add a form inside of the dialog). When the user completes the form(after clicking the button) you will add a new object to the list and update the state with setState({})
onPressed: (){
setState({
// add new object to the list here
});
Navigator.pop(context); // this will close the dialog
}
Can you tell me how can I include the condition for Image.file on the following page? I would like to build it only when controller.image is not null.
I got an error:
The following NoSuchMethodError was thrown building Container(padding: EdgeInsets.all(32.0)):
The method '[]' was called on null.
Receiver: null
Tried calling:
when I first redirect to this page (and controller.image is null):
class HomePage extends GetView<HomeController> {
final myController1 = TextEditingController();
final myController2 = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Grobonet'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
controller: myController1,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Nazwisko'),
),
TextField(
controller: myController2,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Miejscowosc'),
),
IconButton(
icon: Icon(
Icons.add,
size: 30,
),
onPressed: () =>
Get.toNamed(
AppRoutes.PICK_IMAGE
),
color: Colors.pink,
),
IconButton(
icon: Icon(
Icons.save_outlined,
size: 30,
),
/*onPressed: () =>
Get.toNamed(
AppRoutes.PICK_IMAGE
),*/
color: Colors.pink,
),
Container(
padding: EdgeInsets.all(32),
child: GetBuilder<HomeController>(
builder: (_) {
return Image.file(controller.image);
},
),
),
]
),
),
);
}
}
Controller:
class HomeController extends GetxController {
final image = Get.arguments['image'];
final file_loaded = Get.arguments['file_loaded'];
}
You can use collection-if. Just add if (controller.image != null) like so:
if (controller?.image != null)
Container(
padding: EdgeInsets.all(32),
child: GetBuilder<HomeController>(
builder: (_) {
return Image.file(controller.image);
},
),
),
Use Get your arguments on the controller's onInit() method:
class HomeController extends GetxController {
final File image;
final File file_loaded;
onInit(){
image = Get.arguments['image'];
file_loaded = Get.arguments['file_loaded'];
}
}
In this way, you don't need to perform additional null checks except Get.arguments is actually null.
Update
And you also need to update your view like this:
Container(
padding: EdgeInsets.all(32),
child: GetBuilder<HomeController>(
builder: (_) {
return Image.file(_.image); // not return Image.file(controller.image);
// _ here is the instance of HomeController given by the GetBuilder
},
),
),
Update 2
As you mentioned in the comments, you want to send data to a previously opened page (HomePage) from a second page (OCRDetailsPage). Then you don't need to pass arguments. You can get the HomeController instance in your OCRDetailsPage with Get.find() and set the variables and update the state like:
class OCRDetailsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final OCRDetailsController controller = Get.find();
return Scaffold(
appBar: AppBar(title: Text('OCR Details Page')),
body: Center(
child: FlatButton(
color: Colors.blue,
child: Icon(Icons.save_outlined),
onPressed: () {
final HomeController homeController = Get.find();
homeController.ocr_text = controller.text;
homeController.update();
Get.toNamed(
AppRoutes.HOME,
);
}),
),
);
}
}
I am using an autocomplete textfield in my flutter application. While typing text in the textfield the user gets the suggestions (via JSON). Then the user should click on a suggestion and should be forwarded to the "SecondPage". At the same time the country of the selected player should also be passed to the "SecondPage".
In the part itemSubmitted I tried to integrate my plan but it doesn't work. The "SecondPage" doesn't start. Can you help here?
This is my code:
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text('Search'),
),
body: new Center(
child: new Column(children: <Widget>[
new Column(children: <Widget>[
searchTextField = AutoCompleteTextField<PlayersForSearch>(
style: new TextStyle(color: Colors.black, fontSize: 16.0),
decoration: new InputDecoration(
suffixIcon: Container(
width: 85.0,
height: 60.0,
),
contentPadding: EdgeInsets.fromLTRB(10.0, 30.0, 10.0, 20.0),
filled: true,
hintText: 'Search Player Name',
hintStyle: TextStyle(color: Colors.black)),
itemSubmitted: (item) {
SecondPage(item.country);
MaterialPageRoute(builder: (context) => SecondPage(item.country));
setState(() => searchTextField.textField.controller.text =
item.autocompleteterm);
},
clearOnSubmit: false,
key: key,
suggestions: PlayerViewModel.player_search,
itemBuilder: (context, item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(item.autocompleteterm,
style: TextStyle(
fontSize: 16.0
),),
Padding(
padding: EdgeInsets.all(15.0),
),
Text(item.country,
)
],
);
},
itemSorter: (a, b) {
return a.autocompleteterm.compareTo(b.autocompleteterm);
},
itemFilter: (item, query) {
return item.autocompleteterm
.toLowerCase()
.startsWith(query.toLowerCase());
}),
]),
])));
}
I believe what's missing is the Navigator.push call to push the SecondPage onto the stack of routes. A MaterialPageRoute will not place itself onto the stack of pages/routes.
Example
When you focus on the text field and press Enter, it will navigate to the SecondPage with the value of the TextFormField.
import 'package:flutter/material.dart';
class NavTextFieldPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Nav TextField Submit'),
),
body: NavTextfieldExample(),
);
}
}
class NavTextfieldExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'Navigate to next page',
),
initialValue: 'Japan',
onFieldSubmitted: (item) {
/// Using default Navigator from Scaffold, *push* onto stack SecondPage
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondPage(item)));
},
)
],
);
}
}
class SecondPage extends StatelessWidget {
final String country;
SecondPage(this.country);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Nav Second Page'),
),
body: Center(
child: Text('Country: $country'),
),
);
}
}
The key piece above is:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondPage(item)));
which uses the Navigator object to push routes onto your stack of routes (i.e. pages).
please try the following :
. create a SecondPage.dart
. put in a stateless or stateful Widget
. create a route in your materialApp()
I have simple form , inside it have CircularAvatar when this is pressed show ModalBottomSheet to choose between take picture from gallery or camera. To make my widget more compact , i separated it to some file.
FormDosenScreen (It's main screen)
DosenImagePicker (It's only CircularAvatar)
ModalBottomSheetPickImage (It's to show ModalBottomSheet)
The problem is , i don't know how to passing value from ModalBottomSheetPickImage to FormDosenScreen. Because value from ModalBottomSheetPickImage i will use to insert operation.
I only success passing from third Widget to second Widget , but when i passing again from second Widget to first widget the value is null, and i think the problem is passing from Second widget to first widget.
How can i passing from third Widget to first Widget ?
First Widget
class FormDosenScreen extends StatefulWidget {
static const routeNamed = '/formdosen-screen';
#override
_FormDosenScreenState createState() => _FormDosenScreenState();
}
class _FormDosenScreenState extends State<FormDosenScreen> {
String selectedFile;
#override
Widget build(BuildContext context) {
final detectKeyboardOpen = MediaQuery.of(context).viewInsets.bottom;
print('trigger');
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Tambah Dosen'),
actions: <Widget>[
PopupMenuButton(
itemBuilder: (_) => [
PopupMenuItem(
child: Text('Tambah Pelajaran'),
value: 'add_pelajaran',
),
],
onSelected: (String value) {
switch (value) {
case 'add_pelajaran':
Navigator.of(context).pushNamed(FormPelajaranScreen.routeNamed);
break;
default:
}
},
)
],
),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
DosenImagePicker(onPickedImage: (file) => selectedFile = file),
SizedBox(height: 20),
Card(
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormFieldCustom(
onSaved: (value) {},
labelText: 'Nama Dosen',
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
prefixIcon: Icon(Icons.email),
labelText: 'Email Dosen',
keyboardType: TextInputType.emailAddress,
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
keyboardType: TextInputType.number,
inputFormatter: [
// InputNumberFormat(),
WhitelistingTextInputFormatter.digitsOnly
],
prefixIcon: Icon(Icons.local_phone),
labelText: 'Telepon Dosen',
),
],
),
),
),
SizedBox(height: kToolbarHeight),
],
),
),
Positioned(
child: Visibility(
visible: detectKeyboardOpen > 0 ? false : true,
child: RaisedButton(
onPressed: () {
print(selectedFile);
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: colorPallete.primaryColor,
child: Text(
'SIMPAN',
style: TextStyle(fontWeight: FontWeight.bold, fontFamily: AppConfig.headerFont),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
textTheme: ButtonTextTheme.primary,
),
),
bottom: kToolbarHeight / 2,
left: sizes.width(context) / 15,
right: sizes.width(context) / 15,
)
],
),
);
}
}
Second Widget
class DosenImagePicker extends StatefulWidget {
final Function(String file) onPickedImage;
DosenImagePicker({#required this.onPickedImage});
#override
DosenImagePickerState createState() => DosenImagePickerState();
}
class DosenImagePickerState extends State<DosenImagePicker> {
String selectedImage;
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: InkWell(
onTap: () async {
await showModalBottomSheet(
context: context,
builder: (context) => ModalBottomSheetPickImage(
onPickedImage: (file) {
setState(() {
selectedImage = file;
widget.onPickedImage(selectedImage);
print('Hellooo dosen image picker $selectedImage');
});
},
),
);
},
child: CircleAvatar(
foregroundColor: colorPallete.black,
backgroundImage: selectedImage == null ? null : MemoryImage(base64.decode(selectedImage)),
radius: sizes.width(context) / 6,
backgroundColor: colorPallete.accentColor,
child: selectedImage == null ? Text('Pilih Gambar') : SizedBox(),
),
),
);
}
}
Third Widget
class ModalBottomSheetPickImage extends StatelessWidget {
final Function(String file) onPickedImage;
ModalBottomSheetPickImage({#required this.onPickedImage});
#override
Widget build(BuildContext context) {
return SizedBox(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: <Widget>[
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(quality: 80, returnFile: ReturnFile.BASE64);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.green,
child: Icon(Icons.camera_alt),
),
),
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(returnFile: ReturnFile.BASE64, isCamera: false);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.blue,
child: Icon(Icons.photo_library),
),
),
],
),
),
);
}
}
The cleanest and easiest way to do this is through Provider. It is one of the state management solutions you can use to pass values around the app as well as rebuild only the widgets that changed. (Ex: When the value of the Text widget changes). Here is how you can use Provider in your scenario:
This is how your model should look like:
class ImageModel extends ChangeNotifier {
String _base64Image;
get base64Image => _base64Image;
set base64Image(String base64Image) {
_base64Image = base64Image;
notifyListeners();
}
}
Don't forget to add getters and setters so that you can use notifyListeners() if you have any ui that depends on it.
Here is how you can access the values of ImageModel in your UI:
final model=Provider.of<ImageModel>(context,listen:false);
String image=model.base64Image; //get data
model.base64Image=resultBase64; //set your image data after you used ImagePicker
Here is how you can display your data in a Text Widget (Ideally, you should use Selector instead of Consumer so that the widget only rebuilds if the value its listening to changes):
#override
Widget build(BuildContext context) {
//other widgets
Selector<ImageModel, String>(
selector: (_, model) => model.base64Image,
builder: (_, image, __) {
return Text(image);
},
);
}
)
}
You could achieve this easily. If you are using Blocs.
I've tried to populate the dropdown menu button with the data from the SQLite database.
Then on the onTap Function I wanted to navigate to the selected category.
When I tap on the category it does not navigate.
I have saved each category with an id in the database which is used the identify the selected item.
Here is the code:
'''
class _HomeState extends State<Home> {
TodoService _todoService;
var _selectedValue;
var _categories = List<DropdownMenuItem>();
List<Todo>_todoList=List<Todo>();
#override
initState(){
super.initState();
_loadCategories();
}
_loadCategories() async {
var _categoryService = CategoryService();
var categories = await _categoryService.readCategory();
categories.forEach((category) {
setState(() {
_categories.add(DropdownMenuItem(
child: Text(category['name']),
value: category['name'],
onTap: ()=>Navigator.of(context).push(MaterialPageRoute(builder:(context)=>TodosByCategory(category: category['name'],))),
));
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _globalKey,
appBar: AppBar(
actions: <Widget>[
DropdownButtonHideUnderline(
child: DropdownButton(
value: _selectedValue,
items: _categories,
dropdownColor: Colors.blue,
style: TextStyle(color: Colors.white,fontSize: 16.0),
iconDisabledColor: Colors.white,
iconEnabledColor: Colors.white,
onChanged: (value) {
setState(() {
_selectedValue = value;
});
},
),
),
'''
Here is the todosByCategory():
'''
class _TodosByCategoryState extends State<TodosByCategory> {
List<Todo>_todoList=List<Todo>();
TodoService _todoService=TodoService();
#override
initState(){
super.initState();
getTodosByCategories();
}
getTodosByCategories()async{
var todos=await _todoService.readTodoByCategory(this.widget.category);
todos.forEach((todo){
setState(() {
var model= Todo();
model.title=todo['title'];
model.dueDate=todo['dueDate'];
_todoList.add(model);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos By Category'),
),
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: _todoList.length,
itemBuilder: (context, index){
return Padding(
padding: EdgeInsets.only(top:8.0, left: 8.0, right: 8.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
elevation: 8.0,
child: ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(_todoList[index].title)
],
),
subtitle: Text(_todoList[index].dueDate),
// trailing: Text(_todoList[index].dueDate),
),
),
);
},),
)
],
),
);
}
}
'''
Please help me out.
Instead of writing the navigation code inside onTap of DropdownMenuItem, you can write it inside onChanged of DropdownButton where you are also getting the category name string as the value. It should work then.