Flutter, prevent the keyboard from showing once the time is entered - flutter

I would like to prevent the keyboard from showing once the time is entered, how could I do it?
Updated
I tried using FocusScope.of(context).unfocus(); and it works for the first try, but it doesn't for the second one. It's kinda weird. Look at this.
The first works, the second doesn't work, but the third works, also noticed that the keyboard show up even before than the TimePicker is showed. (Sorry for my bad english)
this is the code;
TextEditingController _startTime = TextEditingController();
Widget _createTimePicker(String text, TextEditingController controller) {
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
text,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
SizedBox(
height: 10,
),
TextFormField(
validator: (String value) {
if (value.isEmpty) {
return 'Es necesario especificar una hora.';
}
},
controller: controller,
decoration: InputDecoration(
border: InputBorder.none,
fillColor: Color(0xfff3f3f4),
filled: true),
onTap: () {
Navigator.of(context).push(
showPicker(
context: context,
value: _time,
onChange: onTimeChanged,
is24HrFormat: true,
),
);
FocusScope.of(context).unfocus();
},
)
],
),
);
}
TimeOfDay _time = TimeOfDay.now().replacing(minute: 30);
void onTimeChanged(TimeOfDay newTime) {
setState(() {
_time = newTime;
_startTime.text = _time.format(context);
});
}

You can copy paste run 2 full code below
Solution 1: Quick fix for current code
You can use Future.delayed and FocusManager.instance.primaryFocus.unfocus
onTap: () async {
Navigator.of(context).push(
showPicker(
context: context,
value: _time,
onChange: onTimeChanged,
is24HrFormat: true,
),
);
await Future.delayed(Duration(milliseconds: 50), () {
FocusManager.instance.primaryFocus.unfocus();
});
})
Solution 2: Assume you do not need keyboard to show up all the time
You can use GestureDetector wrap TextFormField and set enable to false
GestureDetector(
onTap: () async {
Navigator.of(context).push(
showPicker(
context: context,
value: _time,
onChange: onTimeChanged,
is24HrFormat: true,
),
);
},
child: TextFormField(
enabled: false,
working demo 1
working demo 2
full code 1
import 'package:day_night_time_picker/lib/daynight_timepicker.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _startTime = TextEditingController();
final _formKey = GlobalKey<FormState>();
Widget _createTimePicker(String text, TextEditingController controller) {
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
text,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
SizedBox(
height: 10,
),
TextFormField(
//enabled: false,
validator: (String value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
controller: controller,
decoration: InputDecoration(
errorStyle: TextStyle(color: Colors.red),
border: InputBorder.none,
fillColor: Color(0xfff3f3f4),
filled: true),
onTap: () async {
Navigator.of(context).push(
showPicker(
context: context,
value: _time,
onChange: onTimeChanged,
is24HrFormat: true,
),
);
await Future.delayed(Duration(milliseconds: 200), () {
FocusManager.instance.primaryFocus.unfocus();
});
})
],
),
);
}
TimeOfDay _time = TimeOfDay.now().replacing(minute: 30);
void onTimeChanged(TimeOfDay newTime) {
setState(() {
_time = newTime;
_startTime.text = _time.format(context);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_createTimePicker("", _startTime),
ElevatedButton(
onPressed: () {
if (_formKey.currentState.validate()) {}
},
child: Text('Submit'),
)
],
),
),
),
);
}
}
full code 2
import 'package:day_night_time_picker/lib/daynight_timepicker.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _startTime = TextEditingController();
final _formKey = GlobalKey<FormState>();
Widget _createTimePicker(String text, TextEditingController controller) {
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
text,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
SizedBox(
height: 10,
),
GestureDetector(
onTap: () async {
Navigator.of(context).push(
showPicker(
context: context,
value: _time,
onChange: onTimeChanged,
is24HrFormat: true,
),
);
},
child: TextFormField(
enabled: false,
validator: (String value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
controller: controller,
decoration: InputDecoration(
errorStyle: TextStyle(color: Colors.red),
border: InputBorder.none,
fillColor: Color(0xfff3f3f4),
filled: true),
),
)
],
),
);
}
TimeOfDay _time = TimeOfDay.now().replacing(minute: 30);
void onTimeChanged(TimeOfDay newTime) {
setState(() {
_time = newTime;
_startTime.text = _time.format(context);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_createTimePicker("", _startTime),
ElevatedButton(
onPressed: () {
if (_formKey.currentState.validate()) {}
},
child: Text('Submit'),
)
],
),
),
),
);
}
}

on the callback function of the time selection dismiss the keyboard
FocusScope.of(context).unfocus();

if this field is only editable through the time picker, you can make the text field read-only by setting its attribute
readOnly: true
if the field can be edited by keyboard, then you can await for the dialog result and then call
FocusScope.of(context).requestFocus(new FocusNode());

After set time to TextInput or on confirm time select, call this FocusScope.of(context).unfocus() function.
// update your function
void onTimeChanged(TimeOfDay newTime) {
FocusScope.of(context).unfocus();
...
}
and,
TextFormField(
readOnly: true,
...
)

Related

Deleting Item out of List, Listview.build shows wrong data

I have a Stateful widget that i pass a list to (for example 2 items).
After I delete an item, the widget should rebuild itself.
Unfortunately, the deleted item is still displayed and the other one is not.
When I re-enter the widget, the correct item is loaded.
There is a similar problem List not updating on deleting item
but maybe someone can explain me what i did wrong and why provider is helping me here instead of setState?
My code is:
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:trip_planner/util/dialog_box.dart';
import 'package:trip_planner/util/previewUrl.dart';
class BookingPage extends StatefulWidget {
final List toDoList;
BookingPage({
super.key,
required this.toDoList,
});
#override
State<BookingPage> createState() => _BookingPageState();
}
class _BookingPageState extends State<BookingPage> {
//text controller
final _controller = TextEditingController();
final _database = FirebaseDatabase.instance.ref();
//Liste is an example what i have in my list
List toDoList2 = [
["https://www.booking.com/Share-Rnv2Kf", true],
["https://www.booking.com/Share-3hKQ0r", true],
];
void initState(){
super.initState();
}
void deleteTask(int index){
setState(() {
widget.toDoList.removeAt(index);
});
//DatabaseReference _testRef = _database.child("Hotel:");
//_testRef.set(widget.toDoList.toString());
}
//save new Item
void saveNewItem(){
setState(() {
widget.toDoList.add([_controller.text, false]);
//DatabaseReference _testRef = _database.child("Hotel:");
//_testRef.set(widget.toDoList.toString());
_controller.clear();
});
Navigator.of(context).pop();
}
void createNewItem(){
showDialog(
context: context,
builder: (context){
return DialogBox(
controller: _controller,
onSave: saveNewItem,
onCancel: () => Navigator.of(context).pop(),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Booking Seiten'),
elevation: 0,
),
floatingActionButton: FloatingActionButton(
onPressed: createNewItem,
child: Icon(Icons.add),
),
body: ListView.builder(
itemCount: widget.toDoList.length,
itemBuilder: (context, index){
return PreviewUrl(
url2: widget.toDoList[index][0],
deleteFunction: (context) => setState(() => deleteTask(index)),
);
},
),
);
}
}
i thought setState does the same thing as when i re-enter the widget, but it doesn't.
import 'package:any_link_preview/any_link_preview.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:url_launcher/url_launcher.dart';
class PreviewUrl extends StatelessWidget {
final String url2;
//Function(bool?)? onChanged;
Function(BuildContext)? deleteFunction;
PreviewUrl({
super.key,
required this.url2,
required this.deleteFunction,
//required this.onChanged,
});
Future openBrowserURL({
required String url,
bool inApp = false,
}) async {
if(await canLaunch(url)){
await launch(
url,
forceSafariVC: inApp, //iOS
forceWebView: inApp, //Android
enableJavaScript: true, //Android
);
}
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(1.0),
child: Slidable(
endActionPane: ActionPane(
motion: StretchMotion(),
children: [
SlidableAction(
onPressed: deleteFunction,
icon: Icons.delete,
backgroundColor: Colors.red.shade300,
borderRadius: BorderRadius.circular(12),
)
],
),
child: Container(
child: AnyLinkPreview.builder(
link: url2,
itemBuilder: (context, metadata, imageProvider) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (imageProvider != null)
GestureDetector(
onTap: () async {
final url = url2;
openBrowserURL(url: url, inApp: true);
},
child: Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.width *0.25,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12)),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
),
Container(
width: double.infinity,
color: Theme.of(context).primaryColor.withOpacity(0.6),
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (metadata.title != null)
Text(
metadata.title!,
maxLines: 1,
style:
const TextStyle(fontWeight: FontWeight.w500),
),
const SizedBox(height: 5),
if (metadata.desc != null)
Text(
metadata.desc!,
maxLines: 1,
style: Theme.of(context).textTheme.bodySmall,
),
Text(
metadata.url ?? url2,
maxLines: 1,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
),
),
),
),
);
}
}
If you run the simplified version of your code in DartPad - it will work:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
List toDoList = [
["Button 1", true],
["Button 2", true],
];
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: BookingPage(toDoList: toDoList),
),
),
);
}
}
class BookingPage extends StatefulWidget {
final List toDoList;
const BookingPage({
super.key,
required this.toDoList,
});
#override
State<BookingPage> createState() => _BookingPageState();
}
class _BookingPageState extends State<BookingPage> {
//Liste is an example what i have in my list
List toDoList2 = [
["Button 1", true],
["Button 2", true],
];
#override
void initState() {
super.initState();
}
void deleteTask(int index) {
setState(() {
widget.toDoList.removeAt(index);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Booking Seiten'),
elevation: 0,
),
body: ListView.builder(
itemCount: widget.toDoList.length,
itemBuilder: (context, index) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightBlue,
padding: const EdgeInsets.all(12),
textStyle: const TextStyle(fontSize: 22),
),
child: Text(widget.toDoList[index][0]!),
onPressed: () => setState(() => deleteTask(index)),
);
},
),
);
}
}
Which tells me that the problem is your PreviewUrl. My guess is - it is a statful widget, and when the tree rebuilds - it will link the old State object to the first item.
Using Keys might help, something like:
return PreviewUrl(
key: ObjectKey(widget.toDoList[index]),
url2: widget.toDoList[index][0],
deleteFunction: (context) => setState(() => deleteTask(index)),
);

Flutter sharedPreferences initialization

i want to initialize my sharedPref object before app loading.I tried init func etc. but always on the secreen appears "Instance of Future" text then my sharedPref object initialization end then getStringList method is executed properly.I mean i don't want to see "Instance of Future".What should i do?
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
_MyHomePageState().createSharedObject();
runApp(
MyApp(),
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
_MyHomePageState().createSharedObject();
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String baslik, mesaj;
List<String> kaydedilenNotlar = [];
List<String> kaydedilenBasliklar = [];
List<String> deneme = [];
SharedPreferences sharedPreferences;
Future createSharedObject() async {
sharedPreferences = await SharedPreferences.getInstance();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
backgroundColor: Colors.yellowAccent[100],
title: Text(
"Yeni Not",
textAlign: TextAlign.center,
),
content: Container(
height: 250,
child: Column(
children: [
Expanded(
child: TextField(
onChanged: (value) {
setState(() {
baslik = value;
});
},
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Başlık",
hintStyle: TextStyle(
color: Colors.black,
fontStyle: FontStyle.italic),
),
),
),
TextField(
onChanged: (value) {
setState(() {
mesaj = value;
});
},
maxLines: 5,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Not",
),
),
TextButton(
onPressed: () async {
kaydedilenNotlar.add(mesaj);
kaydedilenBasliklar.add(baslik);
sharedPreferences =
await SharedPreferences.getInstance();
/* sharedPreferences.setStringList(
"not", kaydedilenNotlar);
sharedPreferences.setStringList(
"baslik", kaydedilenBasliklar);*/
},
child: Text(
"Kaydet",
style: TextStyle(fontSize: 18),
))
],
),
),
);
});
},
),
appBar: AppBar(
centerTitle: true,
title: Text("AJANDA"),
bottom: TabBar(
tabs: [
Tab(
child: Text("Yapılacak"),
),
Tab(
child: Text("Yapılıyor"),
),
Tab(
child: Text("Yapıldı"),
)
],
),
),
body: TabBarView(
children: [
Center(
child: Text(
"${sharedPreferences == null ? createSharedObject() : sharedPreferences.getStringList("not")}")),
Center(child: Text("2. sayfa")),
Center(child: Text("3. sayfa")),
],
),
),
);
}
}
The reason for this is that your createSharedObject() function is a Future. And in the lines
child: Text("${sharedPreferences == null ? createSharedObject() : sharedPreferences.getStringList("not")}")),
you are using that Future value and not awaiting for it.
So, if you want to await for the actual value, you'll need a FutureBuilder.
FutureBuilder<String>(
future: your_future_function,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState==ConnectionState.done){
return Text(snapshot.data);
} else {
return Text('Loading');
}
});
I must say I think there are better ways to deal with the loading of values from SharedPreferences, but for the sake of simplicity, the solution above should work.

Flutter how to get user input using text form in show dialog?

I'm trying to get the user input to change the title using a text form in show dialog but it seems the state is rebuilding whenever the keyboard shows/closes, my code is working before, but when I did flutter upgrade to v1.17 it's not working anymore. I've been stuck here for a couple of days now and I don't know what's wrong with my code or what error might be causing it, I can only see "getSelectedText on inactive InputConnection" and "mSecurityInputMethodService is null" in the debug console, please help.
Here's a sample of my code:
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
final TextEditingController titleController = new TextEditingController();
final GlobalKey<FormState> _keyDialogForm = new GlobalKey<FormState>();
#override
void initState() {
super.initState();
titleController.text = 'Hello';
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Center(
child: Column(
children: <Widget>[
Text(titleController.text),
SizedBox(
height: 50,
),
FlatButton(
color: Colors.redAccent,
onPressed: () {
showTitleDialog();
},
child: Text(
'Show Dialog',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
))
],
),
));
}
Future showTitleDialog() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Form(
key: _keyDialogForm,
child: Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.ac_unit),
),
maxLength: 8,
textAlign: TextAlign.center,
onSaved: (val) {
titleController.text = val;
},
autovalidate: true,
validator: (value) {
if (value.isEmpty) {
return 'Enter Title Name';
}
return null;
},
)
],
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
if (_keyDialogForm.currentState.validate()) {
_keyDialogForm.currentState.save();
Navigator.pop(context);
}
},
child: Text('Save'),
color: Colors.blue,
),
FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Cancel')),
],
);
});
}
}
You can copy paste run full code below
You can call setState in onSaved
code snippet
onSaved: (val) {
titleController.text = val;
setState(() {});
},
working demo
full code
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
final TextEditingController titleController = new TextEditingController();
final GlobalKey<FormState> _keyDialogForm = new GlobalKey<FormState>();
#override
void initState() {
super.initState();
titleController.text = 'Hello';
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Center(
child: Column(
children: <Widget>[
Text(titleController.text),
SizedBox(
height: 50,
),
FlatButton(
color: Colors.redAccent,
onPressed: () {
showTitleDialog();
},
child: Text(
'Show Dialog',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
))
],
),
));
}
Future showTitleDialog() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Form(
key: _keyDialogForm,
child: Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.ac_unit),
),
maxLength: 8,
textAlign: TextAlign.center,
onSaved: (val) {
titleController.text = val;
setState(() {});
},
autovalidate: true,
validator: (value) {
if (value.isEmpty) {
return 'Enter Title Name';
}
return null;
},
)
],
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
if (_keyDialogForm.currentState.validate()) {
_keyDialogForm.currentState.save();
Navigator.pop(context);
}
},
child: Text('Save'),
color: Colors.blue,
),
FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Cancel')),
],
);
});
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Test(),
);
}
}

flutter validate radio buttons

How can I add a validator function to a list of RadioButtons in order to have them validated (like TextFormFields with _formKey.currentState.validate()) after the User submits the Form?
You can copy paste run full code below
You can use package https://pub.dev/packages/flutter_form_builder
It support bulid-in validators such as FormBuilderValidators.required() you can directly use
you can also use custom validator function https://pub.dev/packages/flutter_form_builder#custom-validator-function
FormBuilderRadio(
decoration:
InputDecoration(labelText: 'My chosen language'),
attribute: "best_language",
leadingInput: true,
onChanged: _onChanged,
validators: [FormBuilderValidators.required()],
options:
["Dart", "Kotlin", "Java", "Swift", "Objective-C"]
.map((lang) => FormBuilderFieldOption(
value: lang,
child: Text('$lang'),
))
.toList(growable: false),
),
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter FormBuilder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(color: Colors.purple),
),
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() {
return MyHomePageState();
}
}
class MyHomePageState extends State<MyHomePage> {
var data;
bool autoValidate = true;
bool readOnly = false;
bool showSegmentedControl = true;
final GlobalKey<FormBuilderState> _fbKey = GlobalKey<FormBuilderState>();
final GlobalKey<FormFieldState> _specifyTextFieldKey =
GlobalKey<FormFieldState>();
ValueChanged _onChanged = (val) => print(val);
var genderOptions = ['Male', 'Female', 'Other'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FormBuilder Example"),
),
body: Padding(
padding: EdgeInsets.all(10),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
FormBuilder(
// context,
key: _fbKey,
autovalidate: true,
initialValue: {
'movie_rating': 5,
},
readOnly: false,
child: Column(
children: <Widget>[
FormBuilderRadio(
decoration:
InputDecoration(labelText: 'My chosen language'),
attribute: "best_language",
leadingInput: true,
onChanged: _onChanged,
validators: [FormBuilderValidators.required()],
options:
["Dart", "Kotlin", "Java", "Swift", "Objective-C"]
.map((lang) => FormBuilderFieldOption(
value: lang,
child: Text('$lang'),
))
.toList(growable: false),
),
],
),
),
Row(
children: <Widget>[
Expanded(
child: MaterialButton(
color: Theme.of(context).accentColor,
child: Text(
"Submit",
style: TextStyle(color: Colors.white),
),
onPressed: () {
if (_fbKey.currentState.saveAndValidate()) {
print(_fbKey.currentState.value);
} else {
print(_fbKey.currentState.value);
print("validation failed");
}
},
),
),
SizedBox(
width: 20,
),
Expanded(
child: MaterialButton(
color: Theme.of(context).accentColor,
child: Text(
"Reset",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_fbKey.currentState.reset();
},
),
),
],
),
],
),
),
),
);
}
}

Method is called twice in StreamBuilder which contains custom dialog in Flutter

I create a loading dialog and put it in StreamBuilder. At the same time, there is a method named _loadingText as the dialog parameter. When I click the 'Go Run' button, the _loadingText method is called twice.
As the same way, I used the flutter build-in dialog showAboutDialog, everything is OK.
If I remove the StreamBuilder, the _loadingText is called once too.
It takes me one day!!!
Any help is appreciated. Thanks in advance...
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:view_animation/loading_dialog.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<String> _streamController;
TextEditingController _inputController;
#override
void initState() {
super.initState();
_streamController = StreamController<String>.broadcast();
_inputController = TextEditingController();
_inputController.addListener(() {
_streamController.add(_inputController.text);
});
}
#override
void dispose() {
super.dispose();
_streamController.close();
}
String _loadingText() {
print('===== 2. Method run OVER =====');
return 'Loading...';
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_inputContainer(),
SizedBox(
height: 20,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(26),
),
child: StreamBuilder(
stream: _streamController.stream.map((text) => text.length > 4),
builder: (context, snap) {
return FlatButton(
color: Color(0xFFFFAC0B),
disabledColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12.5),
onPressed: snap.data != null && snap.data
? () {
print('===== 1. show dialog =====');
showDialog(
context: context,
builder: (BuildContext context) {
return LoadingDialog(
loadingText: _loadingText(),
);
});
// showAboutDialog(context: context, applicationName: _loadingText());
}
: null,
child: Text(
'GO RUN',
style: TextStyle(fontSize: 12, color: Colors.white),
),
);
},
),
),
],
)),
);
}
Widget _inputContainer() {
return Container(
width: 200,
padding: EdgeInsets.only(left: 20, right: 20),
decoration: BoxDecoration(
color: Color(0xFFFFAC0B),
borderRadius: BorderRadius.circular(36.0),
),
child: TextField(
controller: _inputController,
keyboardType: TextInputType.number,
maxLines: 1,
cursorColor: Colors.orange,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Let's GO",
hintStyle: TextStyle(color: Colors.white54, fontSize: 20),
),
),
);
}
}
loading_dialog.dart
import 'package:flutter/material.dart';
class LoadingDialog extends StatefulWidget {
final String loadingText;
final bool outsideDismiss;
final Function dismissCallback;
final Future<dynamic> requestCallback;
LoadingDialog(
{Key key,
this.loadingText = "Loading...",
this.outsideDismiss = true,
this.dismissCallback,
this.requestCallback,
})
: super(key: key);
#override
_LoadingDialogState createState() => _LoadingDialogState();
}
class _LoadingDialogState extends State<LoadingDialog> {
void _dismissDialog(){
if(widget.dismissCallback != null) {
widget.dismissCallback();
}
Navigator.of(context).pop();
}
#override
void initState() {
print('===== 3. loading init =====');
if (widget.requestCallback != null) {
widget.requestCallback.then((_) => Navigator.of(context).pop());
}
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.outsideDismiss ? _dismissDialog : null,
child: Material(
type: MaterialType.transparency,
child: Center(
child: SizedBox(
width: 120.0,
height: 120.0,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(),
new Padding(
padding: const EdgeInsets.only(
top: 20.0,
),
child: new Text(
widget.loadingText,
style: new TextStyle(fontSize: 12.0),
),
),
],
),
),
),
),
),
);
}
}
log gif here
That's because when you tap on button first time your TextField is still active that means new state comes and flutter rebuilds itself. When you tap on button second your Textfield is inactive.
The points are when you pass the function to the onTap widget it's going to execute when it building state and calling a function without tapping on it:
So instead of a passing method to the OnTap, try something like this:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () widget.outsideDismiss ? ()
{
this._dismissDialog();
} : null,
...