Flutter Hero Animation dismissing keyboard on textfield - flutter

I have two screens with different size of text fields. I am trying to make a hero animation for them. I want the end text field to auto focus, but the keyboard get dismissed at the second screen.
I tried to use flightShuttleBuilder to check if the animation is completed and request focus. But it is not working.
Here is the code for the second screen
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Search extends ConsumerStatefulWidget {
#override
ConsumerState createState() => _SearchState();
}
class _SearchState extends ConsumerState<Search> {
FocusNode focusNode = FocusNode();
#override
void initState() {
// TODO: implement initState
super.initState();
// focusNode = FocusNode();
// focusNode?.requestFocus()
}
#override
void dispose() {
// TODO: implement dispose
focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => () => focusNode.requestFocus());
Size size = MediaQuery.of(context).size;
focusNode.requestFocus();
return Scaffold(
appBar: AppBar(
title: Container(
child: Hero(
tag:'searchBar',
flightShuttleBuilder: ((flightContext, animation, flightDirection, fromHeroContext, toHeroContext){
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
focusNode.requestFocus();
}
});
return toHeroContext.widget;
}),
child: Material(
type: MaterialType.transparency,
child: SizedBox(
height:size.height * 0.05,
child: TextField(
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search, size: 25.0,),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.0),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
filled: true,
hintStyle: TextStyle(color: Colors.grey[800]),
hintText: 'Search a word',
fillColor: Colors.white,
// isDense: true,
contentPadding: EdgeInsets.all(0.0),
),
),
),
),
),
),
),
body: Container(),
);
}
}

My first advice is avoid doing calculations or calling methods in the build method, this will cause to be called each time the widget tree needs to refresh (unless you use Flutter Hooks which holds the state of what is currently doing, but thats a different story)
class Search extends ConsumerStatefulWidget {
#override
ConsumerState createState() => _SearchState();
}
class _SearchState extends ConsumerState<Search> {
FocusNode focusNode = FocusNode();
#override
void initState() {
// TODO: implement initState
super.initState();
// focusNode = FocusNode();
// focusNode?.requestFocus()
}
#override
void dispose() {
// TODO: implement dispose
focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
/// no Widget WidgetsBinding.instance.addPostFrameCallback nor requestFocus call here
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: Container(
child: Hero(
tag: 'searchBar',
flightShuttleBuilder: ((flightContext, animation, flightDirection,
fromHeroContext, toHeroContext) {
/// Don't try to add a Listener here, use a StatefulWidget that uses that logic in its initState
return _FocustWidget(
animation: animation,
child: toHeroContext.widget,
focusNode: focusNode,
);
}),
child: Material(
type: MaterialType.transparency,
child: SizedBox(
height: size.height * 0.05,
child: TextField(
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.search,
size: 25.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.0),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
filled: true,
hintStyle: TextStyle(color: Colors.grey[800]),
hintText: 'Search a word',
fillColor: Colors.white,
// isDense: true,
contentPadding: EdgeInsets.all(0.0),
),
),
),
),
),
),
),
body: Container(),
);
}
}
class _FocustWidget extends StatefulWidget {
final Widget child;
final Animation<double> animation;
final FocusNode focusNode;
const _FocustWidget({
Key? key,
required this.focusNode,
required this.animation,
required this.child,
}) : super(key: key);
#override
State<_FocustWidget> createState() => __FocustWidgetState();
}
class __FocustWidgetState extends State<_FocustWidget> {
#override
void initState() {
super.initState();
widget.animation.addStatusListener(_status); ///Check for the animation state
}
void _status(AnimationStatus status) {
if (status == AnimationStatus.completed) {
Future.microtask(() => mounted ? widget.focusNode.requestFocus() : null);
}
}
#override
void dispose() {
widget.animation.removeStatusListener(_status); /// dispose the listener
super.dispose();
}
#override
Widget build(BuildContext context) => widget.child;
}
Now I talked about hooks because I see you using Consumer so I believe you use riverpod (and therefore maybe you are familiar with hooks if you saw hooks_riverpod package). If you're still grasping the concept of riverpod without hook then this is it, else you could try using hooks to reduce the statefulWidget:
class Search extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final focusNode = useFocusNode(); /// created once with hook
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: Container(
child: Hero(
tag: 'searchBar',
flightShuttleBuilder: ((flightContext, animation, flightDirection,
fromHeroContext, toHeroContext) {
return HookBuilder(
builder: (context) {
final mounted = useIsMounted();
useEffect(() {
if (animation.status == AnimationStatus.completed) {
Future.microtask(
() => mounted() ? focusNode.requestFocus() : null,
);
return null;
}
void _status(AnimationStatus status) {
if (status == AnimationStatus.completed) {
Future.microtask(
() => mounted() ? focusNode.requestFocus() : null,
);
}
}
animation.addStatusListener(_status);
return () => animation.removeStatusListener(_status);
}, const []);
return toHeroContext.widget;
},
);
}),
child: Material(
type: MaterialType.transparency,
child: SizedBox(
height: size.height * 0.05,
child: TextField(
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.search,
size: 25.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.0),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
filled: true,
hintStyle: TextStyle(color: Colors.grey[800]),
hintText: 'Search a word',
fillColor: Colors.white,
// isDense: true,
contentPadding: EdgeInsets.all(0.0),
),
),
),
),
),
),
),
body: Container(),
);
}
}

Related

passing a widget that has setState to another page without stateful/stateless widget

Is Any Way How to pass a widget function to another page that is without any stateless/stateful? The file only includes widgets such as textfields, buttons and etc. I am trying not to cluster every fields in one page. Any helps/ideas would be appreciated!
Main.dart
class MainPage extends StatefulWidget {
const MainPage({super.key});
#override
State<Main Page> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
// bool for toggling password
bool isSecuredPasswordField = true;
#override
Widget build(BuildContext context) {
return Container();
}
// widget function that I need to pass on widget_fields.dart
Widget togglePassword() {
return IconButton(
onPressed: () {
setState(() {
isSecuredPasswordField = !isSecuredPasswordField;
});
},
icon: isSecuredPasswordField
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
);
}
}
widget_fields.dart
Widget userPasswordField(_passwordUserCtrlr) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: TextFormField(
obscureText: true,
controller: _passwordUserCtrlr,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
isDense: true,
suffixIcon: togglePassword(), //<-- I wanna call that function here
prefixIcon: const Icon(Icons.lock),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xFFCECECE)),
borderRadius: BorderRadius.circular(12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFCECECE)),
),
hintText: 'Password',
hintStyle: const TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
),
fillColor: const Color(0xFFFEFEFE),
filled: true,
),
validator: (value) {
if (value!.isEmpty) {
return "Please enter your password.";
} else if (value.length < 8) {
return "Password should be min. 8 characters.";
} else {
return null;
}
},
),
);
}
You can pass functions like any other variable. I made a full working example that's different than yours to show a more minimal example but you can apply the same logic for your code
main.dart
import 'package:flutter/material.dart';
import 'column.dart';
void main() {
runApp(const MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
Widget returnSomeText() {
return const Text("test");
}
#override
Widget build(BuildContext context) {
return Scaffold(body: createColumn(returnSomeText));
}
}
column.dart
import 'package:flutter/material.dart';
Widget createColumn(Function widgetFunction) {
return Column(
children: [widgetFunction(), widgetFunction()],
);
}
As you can see the togglePassword from your code corresponds to returnSomeText in mine. and userPasswordField is like createColumn. But it must be said that it's not recommended to use helper functions like createColumn here but to turn it into a StatelessWidget, like this for example:
import 'package:flutter/material.dart';
class CreateColumn extends StatelessWidget {
final Function widgetFunction;
const CreateColumn({Key? key, required this.widgetFunction}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [widgetFunction(), widgetFunction()],
);
}
}
And then in main.dart:
return Scaffold(body: CreateColumn(widgetFunction: returnSomeText));
See also this YouTube video: Widgets vs helper methods
This is Example that how you call Widget in another class:
class MainApge extends StatefulWidget {
const MainApge({Key? key}) : super(key: key);
#override
State<MainApge> createState() => _MainApgeState();
}
class _MainApgeState extends State<MainApge> {
#override
Widget build(BuildContext context) {
return Column(
children: [
ContainerTextFields.customsTextField(
"User Name",
'enter name',
userNameController,
),
],
);
}
}
This is Custom Widget Class:
class ContainerTextFields {
static Widget customsTextField(
String label, String cusHintText, TextEditingController _controller) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: EdgeInsets.only(
left: SizeConfig.screenHeight! * 0.05,
top: SizeConfig.screenHeight! * 0.03),
child: Text(
label,
style: AppStyle.kUnSyncedDialogeText.copyWith(
color: AppColors.kTextFieldLabelColorGrey,
fontWeight: FontWeight.bold),
)),
Padding(
padding: EdgeInsets.only(
top: SizeConfig.screenHeight! * 0.02,
left: SizeConfig.screenHeight! * 0.042,
right: SizeConfig.screenWidth! * 0.042),
child: SingleChildScrollView(
child: Container(
height: SizeConfig.screenHeight! * 0.065,
child: TextFormField(
controller: _controller,
decoration: InputDecoration(
hintText: cusHintText,
hintStyle: TextStyle(
color: AppColors.kLoginPopUpColor,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
),
)
]);
}
}
You can pass the widget as parameter to child widget:
class MyTextField extends StatelessWidget {
const MyTextField({Key? key,
this.togglePassword,
this.passwordUserCtrlr
})
: super(key: key);
final Widget? togglePassword;
final TextEditingController? passwordUserCtrlr;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: TextFormField(
obscureText: true,
controller: passwordUserCtrlr,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
isDense: true,
suffixIcon: togglePassword, //<-- I wanna call that function here
prefixIcon: const Icon(Icons.lock),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xFFCECECE)),
borderRadius: BorderRadius.circular(12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFCECECE)),
),
hintText: 'Password',
hintStyle: const TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
),
fillColor: const Color(0xFFFEFEFE),
filled: true,
),
validator: (value) {
if (value!.isEmpty) {
return "Please enter your password.";
} else if (value.length < 8) {
return "Password should be min. 8 characters.";
} else {
return null;
}
},
),
);
}
}
And can easily call from main widget:
class MainPage extends StatefulWidget {
const MainPage({super.key});
#override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
// bool for toggling password
bool isSecuredPasswordField = true;
TextEditingController? passwordUserCtrlr = TextEditingController();
#override
Widget build(BuildContext context) {
return MyTextField(
togglePassword: togglePassword(),
passwordUserCtrlr: passwordUserCtrlr,
);
}
// widget function that I need to pass on widget_fields.dart
Widget togglePassword() {
return IconButton(
onPressed: () {
setState(() {
isSecuredPasswordField = !isSecuredPasswordField;
});
},
icon: isSecuredPasswordField
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
);
}
}
You can create class like GlobalWidget for example, like this:
class GlobalWidget {
// widget function that I need to pass on widget_fields.dart
Widget togglePassword(Function()? onPressed, bool value) {
return IconButton(
onPressed: onPressed,
icon: value
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
);
}
}
And You can call the Widget like that :
GlobalWidget().togglePassword(() => setState(() {
isSecuredPasswordField = !isSecuredPasswordField;
}), isSecuredPasswordField)
What you are trying to do is impossible in the Flutter framework. You cannot call methods belonging to other widgets
Also, it is discouraged to use function to return widgets as this impacts the framework's ability to optimize the build process.
One possible solution is to package your complete password entry in a set of custom (statefull) widgets. You can collect those into a single source file if you like. Be sure to create a class for every widget.

How to send a data from listview screen to form screen using flutter

I am trying to send a data from ontap listview screen to form screen like image below. I have searched many references on google but I can't find any references that can help me, if you can provide solutions or references, I will greatly appreciate it.
enter image description here
This is my sample code (ListPage Screen) :
const ListPage({Key? key}) : super(key: key);
#override
State<ListPage> createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
TextEditingController textFieldController = TextEditingController();
var _controller = TextEditingController();
late bool searching, error;
var data;
late String query;
String dataurl = "https://www.something.co.id/mobile/search_data.php";
#override
void initState() {
searching = true;
error = false;
query = "";
super.initState();
}
void getSuggestion() async {
//get suggestion function
var res = await http
.post((Uri.parse(dataurl + "?query=" + Uri.encodeComponent(query))));
//in query there might be unwant character so, we encode the query to url
if (res.statusCode == 200) {
setState(() {
data = json.decode(res.body);
//update data value and UI
});
} else {
//there is error
setState(() {
error = true;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: AppLayout.getHeight(100),
automaticallyImplyLeading: false,
title: searchField(),
backgroundColor: Styles.background,
elevation: 0.0,
),
body: SingleChildScrollView(
child: Container(
alignment: Alignment.center,
child: data == null
? Container(
padding: EdgeInsets.all(20),
child: searching
? Text("Please wait")
: Text("Search any location")
//if is searching then show "Please wait"
//else show search peopels text
)
: Container(
child: searching
? showSearchSuggestions()
: Text("Find any location"),
)
// if data is null or not retrived then
// show message, else show suggestion
),
),
);
}
Widget showSearchSuggestions() {
List suggestionlist = List.from(data["data"].map((i) {
return SearchSuggestion.fromJSON(i);
}));
//serilizing json data inside model list.
return Column(
children: suggestionlist.map((suggestion) {
return InkResponse(
// onTap: () {
// //when tapped on suggestion
// print(suggestion.id); //pint student id
// },
child: GestureDetector(
onTap: () {
_sendDataBack(context);
},
child: SizedBox(
width: double.infinity, //make 100% width
child: Card(
child: Container(
decoration: BoxDecoration(color: Styles.background),
padding: EdgeInsets.all(15),
child: Text(
suggestion.name,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
);
}).toList(),
);
}
// get the text in the TextField and send it back to the FirstScreen
void _sendDataBack(BuildContext context) {
String textToSendBack = textFieldController.text;
Navigator.pop(context, textToSendBack);
}
Widget searchField() {
//search input field
return Container(
height: 50,
child: TextField(
controller: _controller,
autofocus: true,
style: Styles.textStyle,
decoration: InputDecoration(
hintStyle: TextStyle(color: Styles.colorDeepGrey),
hintText: "Search Location...",
prefixIcon: Icon(Icons.search),
suffixIcon: _controller.text.length > 0
? IconButton(
onPressed: () {
_controller.clear();
setState(() {});
},
icon: Icon(Icons.cancel, color: Colors.grey))
: null,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Styles.colorLightBlack.withOpacity(0.20),
width: 2,
),
borderRadius: BorderRadius.circular(4),
), //under line border, set OutlineInputBorder() for all side border
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Styles.primaryColor,
width: 1,
),
borderRadius: BorderRadius.circular(4),
), // focused border color
), //decoration for search input field
onChanged: (value) {
query = value; //update the value of query
getSuggestion(); //start to get suggestion
},
),
);
}
}
//serarch suggestion data model to serialize JSON data
class SearchSuggestion {
String id, name;
SearchSuggestion({required this.id, required this.name});
factory SearchSuggestion.fromJSON(Map<String, dynamic> json) {
return SearchSuggestion(
id: json["id"],
name: json["name"],
);
}
}
Sample Code NextPage Screen :
class NextPage extends StatefulWidget {
#override
_NextPageState createState() => _NextPageState();
}
class _NextPageState extends State<NextPage> {
int _currentStep = 0;
StepperType stepperType = StepperType.vertical;
String text = 'Text';
var _controller = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Flutter Stepper Demo'),
centerTitle: true,
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Stepper(
type: stepperType,
physics: ScrollPhysics(),
currentStep: _currentStep,
onStepTapped: (step) => tapped(step),
onStepContinue: continued,
onStepCancel: cancel,
steps: <Step>[
//Form Pengirim
Step(
title: new Text('Location'),
content: Column(
children: <Widget>[
SizedBox(
height: 50,
child: TextField(
onTap: () {
_awaitReturnValueFromSecondScreen(context);
},
controller: _controller,
autofocus: true,
onChanged: (text) {
setState(() {});
},
style: Styles.textStyle,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: 'Location',
contentPadding:
EdgeInsets.only(left: 15, right: 15),
hintStyle: TextStyle(color: Styles.colorDeepGrey),
suffixIcon: _controller.text.length > 0
? IconButton(
onPressed: () {
_controller.clear();
setState(() {});
},
icon: Icon(Icons.cancel,
color: Colors.grey))
: null,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
Styles.colorLightBlack.withOpacity(0.20),
width: 2,
),
borderRadius: BorderRadius.circular(4),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Styles.primaryColor,
width: 1,
),
borderRadius: BorderRadius.circular(4),
),
),
),
),
],
),
isActive: _currentStep >= 0,
state: _currentStep >= 0
? StepState.complete
: StepState.disabled,
),
],
),
),
],
),
),
);
}
void _awaitReturnValueFromSecondScreen(BuildContext context) async {
// start the SecondScreen and wait for it to finish with a result
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DataAlamat(),
));
// after the SecondScreen result comes back update the Text widget with it
setState(() {
text = result;
});
}
tapped(int step) {
setState(() => _currentStep = step);
}
continued() {
_currentStep < 2 ? setState(() => _currentStep += 1) : null;
}
cancel() {
_currentStep > 0 ? setState(() => _currentStep -= 1) : null;
}
}
Pass the tapped item value to the next page via named parameter of other page class.
class ListPage extends StatelessWidget {
const ListPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return NextPage(value: index);
},
));
},
title: Text(index.toString()),
);
},
),
);
}
}
class NextPage extends StatelessWidget {
final int value;
const NextPage({Key? key, required this.value}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(value.toString()),
),
);
}
}
Example in ListView screen, you have a variable called List<String> listLocations. Your ListView widget be like:
ListView.builder(
itemCount: listLocations.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return SecondScreen(listLocations[index]);
},
)),
child: ...
);
}
}
And your SecondScreen is a StatefulWidget (well it is a Form screen, so it would be Stateful, not Stateless, use TextEditingController in Form widget):
import 'package:flutter/material.dart';
class SecondScreen extends StatefulWidget {
final String location;
SecondScreen(this.location, {Key? key}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
var _textEditingController = TextEditingController();
#override
void initState() {
_textEditingController.text = widget.location;
super.initState();
}
#override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
You need to pass the location value in init state, and don't forget to dispose it.

How to use GetX on a value?

I want to make a Password TextField in which the content visibility can be controlled by the suffix icon.
The code may like this:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(TestGetX());
}
class TestGetX extends StatelessWidget {
var eyeClosed = true.obs;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Test GetX"),
),
body: Align(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.all(20),
child: TextFormField(
obscureText: eyeClosed.value,
decoration: InputDecoration(
icon: Icon(
Icons.security,
color: Colors.purple,
),
hintText: "Your Password",
hintStyle: TextStyle(color: Colors.grey),
suffix: Obx(
() => InkWell(
child: eyeClosed.value
? Icon(Icons.visibility_off, color: Colors.grey)
: Icon(Icons.visibility, color: Colors.purple),
onTap: () {
eyeClosed.value = !eyeClosed.value;
},
),
),
),
),
),
),
),
);
}
}
The suffix icon can be controlled by the Obx(), but the obscureText doesn't work. The direct way is to use Obx() on the TextFormField, but I don't think it is the best way.
Here is the result:
You need to wrap Obx() in TextFormField
Obx(() => TextFormField(...))
Create a controller for your login screen
class LoginController extends GetxController {
RxBool hidePassword = true.obs;
final passwordTextController = TextEditingController();
}
Extends your login screen widget from GetWidget
class LoginScreen extends GetWidget<LoginController> {
final LoginController controller = Get.find<LoginController>();
#override
Widget build(BuildContext context) {
return(); //Define your widget
}
}
Wrap your textfield in Obx(()=> )
Obx(() => FormBuilderTextField(
name: 'password',
controller: controller.passwordTextController,
obscureText: controller.hidePassword.value,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: controller.hidePassword.value ? Icon(Icons.visibility_off)
: Icon(Icons.visibility),
onPressed: () {
controller.hidePassword.value = !controller.hidePassword.value;
},
),
),
),
I have tried with your code & works fine with a little bit change
class LoginPage extends GetView<LoginController>
Also wrap the whole textFormField in Obx(()=>)
i extend a controller for taking values & calling methods in Getx.i can share my full code if you need.
You should use StatefulWidget when your state is changing. Plus, you can reach the same result you want, without "Get" package.
I show you an example here:
import 'package:flutter/material.dart';
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
bool hidePassword = true;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
obscureText: hidePassword, // which is true by default
decoration: InputDecoration(
hintText: "Enter Password",
suffixIcon: IconButton(
icon: hidePassword == false
? Icon(
Icons.visibility_rounded,
color: Colors.purple,
)
: Icon(
Icons.visibility_off_rounded,
color: Colors.grey,
),
onPressed: () {
setState(() {
// here we change the value
// if it's false, it gets true
// and if it's true, it gets false
hidePassword = !hidePassword;
});
},
),
),
),
),
),
);
}
}

How to update Pin on keyboard press?

How can I update the PinInput Boxes with input from the on-screen keyboard? From what I understand, whenever there's a state change, the widget will be rebuild. Hence, from below, what I did was updating the text whenever the on-screen keyboard detects tap. Then since the state is changed, I assumed it will rebuild all the widget which include the PinInput widget, and this is true since I tested the text whenever there's changes. I then did _pinPutController.text = text; to change the input of PinInput, however it is not working.
When I hardcode _pinPutController.text = '123', it works. So the problem is that it is not rebuilding. Am I understanding this correctly? How can I achieve what I wanted?
import 'package:flutter/material.dart';
import 'package:numeric_keyboard/numeric_keyboard.dart';
import 'package:pinput/pin_put/pin_put.dart';
import '../../../../constants.dart';
import '../../../../size_config.dart';
class InputForm extends StatefulWidget {
#override
_InputFormState createState() => _InputFormState();
}
class _InputFormState extends State<InputForm> {
String text = '';
#override
Widget build(BuildContext context) {
return Column(
children: [
PinInput(text: text),
NumericKeyboard(
onKeyboardTap: (value) {
setState(() {
text += value;
});
},
textColor: Colors.red,
rightButtonFn: () {
setState(() {
text = text.substring(0, text.length - 1);
});
},
rightIcon: Icon(
Icons.backspace,
color: Colors.red,
),
mainAxisAlignment: MainAxisAlignment.spaceEvenly),
],
);
}
}
class PinInput extends StatelessWidget {
const PinInput({
Key key,
this.text,
}) : super(key: key);
final String text;
#override
Widget build(BuildContext context) {
final size = getProportionateScreenHeight(60);
final TextEditingController _pinPutController = TextEditingController();
final FocusNode _pinPutFocusNode = FocusNode();
_pinPutFocusNode.unfocus();
print(text);
_pinPutController.text = text;
return PinPut(
fieldsCount: 4,
onSubmit: (String pin) => {},
focusNode: _pinPutFocusNode,
controller: _pinPutController,
preFilledWidget: Align(
alignment: Alignment.bottomCenter,
child: Divider(
color: kPrimaryColor,
thickness: 2.5,
indent: 7.5,
endIndent: 7.5,
),
),
textStyle: TextStyle(
fontSize: getProportionateScreenHeight(24),
),
eachFieldPadding: EdgeInsets.all(
getProportionateScreenHeight(10),
),
eachFieldMargin: EdgeInsets.all(
getProportionateScreenWidth(5),
),
eachFieldHeight: size,
eachFieldWidth: size,
submittedFieldDecoration: boxDecoration(),
selectedFieldDecoration: boxDecoration(),
followingFieldDecoration: boxDecoration(),
inputDecoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
counterText: '',
),
withCursor: true,
pinAnimationType: PinAnimationType.scale,
animationDuration: kAnimationDuration,
);
}
BoxDecoration boxDecoration() {
return BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(
getProportionateScreenWidth(10),
),
);
}
}
The problem is that you recreate a new TextEditingController at each rebuild of your PinInput Widget. However, if you check the PinPutState of the pinput package, it keeps a reference to the first TextEditingController you provide in its initState method:
#override
void initState() {
_controller = widget.controller ?? TextEditingController();
[...]
}
So, you have to keep the same TextEditingController all the way.
The easiest way to fix this would be to raise the TextEditingController to the State of InputForm. Instead of a String text, just use a Controller:
class InputForm extends StatefulWidget {
#override
_InputFormState createState() => _InputFormState();
}
class _InputFormState extends State<InputForm> {
TextEditingController _controller = TextEditingController(text: '');
#override
Widget build(BuildContext context) {
return Column(
children: [
PinInput(controller: _controller),
NumericKeyboard(
onKeyboardTap: (value) => _controller.text += value,
textColor: Colors.red,
rightButtonFn: () => _controller.text =
_controller.text.substring(0, _controller.text.length - 1),
rightIcon: Icon(Icons.backspace, color: Colors.red),
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
],
);
}
}
Note: Since you use a TextEditingController instead of a String, you can get rid of all your setState methods.
Full source code:
import 'package:flutter/material.dart';
import 'package:numeric_keyboard/numeric_keyboard.dart';
import 'package:pinput/pin_put/pin_put.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: InputForm()),
);
}
}
class InputForm extends StatefulWidget {
#override
_InputFormState createState() => _InputFormState();
}
class _InputFormState extends State<InputForm> {
TextEditingController _controller = TextEditingController(text: '');
#override
Widget build(BuildContext context) {
return Column(
children: [
PinInput(controller: _controller),
NumericKeyboard(
onKeyboardTap: (value) => _controller.text += value,
textColor: Colors.red,
rightButtonFn: () => _controller.text =
_controller.text.substring(0, _controller.text.length - 1),
rightIcon: Icon(Icons.backspace, color: Colors.red),
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
],
);
}
}
class PinInput extends StatelessWidget {
const PinInput({
Key key,
this.controller,
}) : super(key: key);
final TextEditingController controller;
#override
Widget build(BuildContext context) {
final size = 60.0;
final FocusNode _pinPutFocusNode = FocusNode();
_pinPutFocusNode.unfocus();
return PinPut(
fieldsCount: 4,
onSubmit: (String pin) => {},
focusNode: _pinPutFocusNode,
controller: controller,
preFilledWidget: Align(
alignment: Alignment.bottomCenter,
child: Divider(
color: Theme.of(context).primaryColor,
thickness: 2.5,
indent: 7.5,
endIndent: 7.5,
),
),
textStyle: TextStyle(
fontSize: 24,
),
eachFieldPadding: EdgeInsets.all(
10,
),
eachFieldMargin: EdgeInsets.all(
5,
),
eachFieldHeight: size,
eachFieldWidth: size,
submittedFieldDecoration: boxDecoration(),
selectedFieldDecoration: boxDecoration(),
followingFieldDecoration: boxDecoration(),
inputDecoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
counterText: '',
),
withCursor: true,
pinAnimationType: PinAnimationType.scale,
animationDuration: Duration(milliseconds: 500),
);
}
BoxDecoration boxDecoration() {
return BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(
10,
),
);
}
}
UPDATE: How to hide the Keyboard...
...while keeping the focus blinking cursor
1. disable the focus on your PinPut fields:
For this, I used a class described here:
class AlwaysDisabledFocusNode extends FocusNode {
#override
bool get hasFocus => false;
}
...as the focusNode of the PinPut:
class PinInput extends StatelessWidget {
[...]
#override
Widget build(BuildContext context) {
final size = 60.0;
final FocusNode _pinPutFocusNode = AlwaysDisabledFocusNode();
// _pinPutFocusNode.unfocus();
return PinPut(
[...]
focusNode: _pinPutFocusNode,
[...]
);
}
}
So, now, the PinPut never gets the focus. The Soft Keyboard is not shown but, hey!, we lost the blinking cursor!
No worries, we'll keep it always on programmatically.
2. Keep the blinking cursor always on
For this, though, we'll have to change the code of the pinput package.
Currently, in PinPutState, the blinking cursor is shown on the next field if withCursor is set to true and the PinPut has the focus. Instead, we will always show the blinking cursor if withCursoris set to true:
if (widget.withCursor /* && _focusNode!.hasFocus */ && index == pin.length) {
return _buildCursor();
}
VoilĂ ! Does it work for you?
This has been mentioned on PinPut GitHub Issue Tracker [ref] about disabling the device keyboard when using a custom keyboard.

Change the textformfield decoration when the content is empty

I want to implement a design but did not get a grip on how to do it. Like in the picture when the user select the field it colors changes. I change the color of the text field to verify if the text field is focussed or not. but how can i know that if the text field is empty then change the color of the border also the content unfocus color?
The design is attached.
This is the code
class EditMneomic extends StatefulWidget {
#override
_EditMneomicState createState() => _EditMneomicState();
}
class _EditMneomicState extends State<EditMneomic> {
FocusNode _focusNode;
TextEditingController _controller;
final _formKey = GlobalKey<FormState>();
#override
void dispose() {
super.dispose();
_focusNode.dispose();
}
#override
void initState() {
super.initState();
// if (_formKey.currentState.validate() == null)
_focusNode = new FocusNode();
_focusNode.addListener(_onOnFocusNodeEvent);
}
_onOnFocusNodeEvent() {
setState(() {
// Re-renders
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Changing Colors'),
),
body: new Container(
// height: 50,
// width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(20),
),
color: _getContainerBackgroundColor(),
),
// padding: new EdgeInsets.all(40.0),
child: Form(
key: _formKey,
autovalidate: true,
child: new TextFormField(
// onEditingComplete: () {
// print('true');
// },
// autovalidate: true,
// validator: (value) {
// // if(value.isEmpty){}
// if (value.isEmpty) {
// return 'Please enter some text';
// }
// return null;
// // if (_controller.text == "") {
// // _getContainerBackgroundColor();
// // } else {
// // return null;
// // }
// },
decoration: InputDecoration(
prefixIcon: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: Color.fromRGBO(255, 255, 255, 1),
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
child: Text(
'1',
textAlign: TextAlign.center,
),
),
),
errorBorder: OutlineInputBorder(
// gapPadding: 10,
borderSide: BorderSide(
color: Colors.white,
),
),
filled: true,
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.red),
// borderRadius: BorderRadius.circular(20)),
// border: OutlineInputBorder(
// borderRadius: const BorderRadius.all(
// const Radius.circular(25.0),
// ),
// ),
),
// style: new TextStyle(color: _getInputTextColor()),
focusNode: _focusNode,
),
)),
);
}
Color _getContainerBackgroundColor() {
return _focusNode.hasFocus
? Color.fromRGBO(233, 238, 249, 1)
: Color.fromRGBO(0, 85, 255, 1);
}
// Color _getAppBarBackgroundColor() {
// return _focusNode.hasFocus ? Colors.green : Colors.red;
// }
Color _getInputTextColor() {
return _focusNode.hasFocus ? Colors.white : Colors.pink;
}
}
You could just have a onChanged listener in your TextFormField that listens to the text input and sets a bool accordingly.
Also, and this has nothing to do with the solution, you need to initialize your TextEditingController and hook it up to your TextFormField.
Example:
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
TextEditingController _controller;
// Use this flag in your _getContainerBackgroundColor or directly in your build method
bool textFieldIsEmpty = true;
#override
void initState() {
_controller = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return TextFormField(
controller: _controller,
onChanged: (String text) {
setState(() {
textFieldIsEmpty = text.isEmpty;
});
},
);
}
}