I have a TextFormField where the user should enter a string in the following format:
XX-XX-XX
Is there anyway to automatically add the "-" as the user is typing?
Thanks
This should work for you.
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller = new TextEditingController();
#override
void initState() {
// TODO: implement initState
super.initState();
// you can have different listner functions if you wish
_controller.addListener(onChange);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Center(
child: TextFormField(
controller: _controller,
),
),
),
);
}
String newText = '';
void onChange(){
String text = _controller.text;
if(text.length < newText.length) { // handling backspace in keyboard
newText = text;
}else if (text.isNotEmpty && text != newText) { // handling typing new characters.
String tempText = text.replaceAll("-", "");
if(tempText.length % 2 == 0){
//do your text transforming
newText = '$text-';
_controller.text = newText;
_controller.selection = new TextSelection(
baseOffset: newText.length,
extentOffset: newText.length
);
}
}
}
}
we can use MaskedTextController form extended_masked_text 2.3.1 then the condition is achievable MaskedTextController numberController = MaskedTextController(mask: '0000-0000-0000-000'); //masking type here
Related
I want to use BottomNavigationBar of Flutter so for that I have created a class called BaseWidget which will be changed as the user taps the item.
class BaseWidget extends StatefulWidget {
final String title;
BaseWidget(this.title);
_BaseWidgetState createState() => _BaseWidgetState(this.title);
}
class _BaseWidgetState extends State<BaseWidget> {
final String title;
_BaseWidgetState(this.title);
#override
Widget build(BuildContext context) {
return Center(child: Text(title));
}
}
In the above class am returning the Center widget with child as Text widget.
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int pageIndex = 0;
final _home = BaseWidget('Home');
final _business = BaseWidget('Business');
final _school = BaseWidget('School');
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: choosePager(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(
icon: Icon(Icons.school), title: Text('School')),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
this.pageIndex = index;
});
}
Widget choosePager() {
switch (pageIndex) {
case 0:
return _home;
break;
case 1:
return _business;
break;
case 2:
return _school;
break;
default:
return Text('Unknown');
break;
}
}
}
Problem 1:
Whenever user taps on the BottomNavigationBarItem the text should change to the respected string passed in the BaseWidget's constructor. But it only shows Home and the rest 2 are ignored.
Problem 2:
I am planning to replace Center widget with the ListView widget to populate the list of Schools and Businesses which will be fetched from the network API in paginated way. So I don't want to reinitialise the classes again when BottomNavigationBarItem is tapped as that would result in loss of data which is already fetched. To prevent data lose I am declaring _home, _business & _school property and using these property in choosePager() method.
There are several issues with your code:
1- The real problem is that you never rebuild the BaseWidget. You construct 3 new BaseWidgets, but you only ever call the build of the _home widget, because it's the first one returned by choosePager(). Since you don't create _home, _business, _school in the HomeWidget build, no other BaseWidget can ever get built.
2- When you don't need to store any state/variables for a widget, use a Stateless widget.
3- Don't do anything in the constructor of your State. Use initState https://docs.flutter.io/flutter/widgets/State/initState.html for that instead.
4- Create widgets using const constructors when possible.
5- Widget constructor take named parameters. One of those should be the key. Use super to call the base constructor.
With that in mind, this is what the code should look like:
class BaseWidget extends StatelessWidget {
final String title;
const BaseWidget({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Text(title),
);
}
}
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int pageIndex = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: choosePager(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text('School'),
),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
pageIndex = index;
});
}
Widget choosePager() {
Widget result;
switch (pageIndex) {
case 0:
result = BaseWidget(title: 'Home');
break;
case 1:
result = BaseWidget(title: 'Business');
break;
case 2:
result = BaseWidget(title: 'School');
break;
default:
result = Text('Unknown');
break;
}
return result;
}
}
Edit: For your example, you may want to fetch some data from the network and only use the widget to display it. In that case, create a new class (not a Widget) to fetch & hold on to the data, and use the Widget only for displaying the data.
Some sample code:
/// Silly class to fetch data
class DataClass {
static int _nextDatum = 0;
int _data;
DataClass();
Future<int> fetchData() async {
await Future.delayed(Duration(
milliseconds: 2000,
));
_data = _nextDatum++;
return _data;
}
int getData() {
return _data;
}
}
class BaseClass extends StatefulWidget {
final String title;
final DataClass data;
const BaseClass({Key key, this.title, this.data}) : super(key: key);
#override
_BaseClassState createState() => _BaseClassState();
}
class _BaseClassState extends State<BaseClass> {
String title;
DataClass data;
#override
Widget build(BuildContext context) {
String dataStr = data == null ? ' - ' : '${data.getData()}';
return Center(
child: Text(
'$title: $dataStr',
),
);
}
#override
void initState() {
super.initState();
// initState gets called only ONCE
title = widget.title;
data = widget.data;
}
#override
void didUpdateWidget(BaseClass oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.data != oldWidget.data) {
data = widget.data;
}
if (widget.title != oldWidget.title) {
title = widget.title;
}
}
}
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int pageIndex = 0;
Map<String, DataClass> _dataMap = <String, DataClass>{};
#override
void initState() {
super.initState();
_init().then((result) {
// Since we need to rebuild the widget with the resulting data,
// make sure to use `setState`
setState(() {
_dataMap = result;
});
});
}
Future<Map<String, DataClass>> _init() async {
// this fetches the data only once
return <String, DataClass>{
'home': DataClass()..fetchData(),
'business': DataClass()..fetchData(),
'school': DataClass()..fetchData(),
};
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: choosePager(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text('School'),
),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
pageIndex = index;
});
}
Widget choosePager() {
Widget result;
switch (pageIndex) {
// it doesn't matter if you create a new BaseClass() a hundred times, flutter is optimized enough to not care. The `initState()` gets called only once. You're fetching the data only once.
case 0:
result = BaseClass(
title: 'Home',
data: _dataMap['home'],
);
break;
case 1:
result = BaseClass(
title: 'Business',
data: _dataMap['business'],
);
break;
case 2:
result = BaseClass(
title: 'School',
data: _dataMap['school'],
);
break;
default:
result = Text('Unknown');
break;
}
return result;
}
}
After lot of RND I solved my problem using IndexedStack. It shows the single Child from the list of children based on the index.
It will initialise all the children when the Widget build(BuildContext context) method of the _HomeWidgetState is called. So any time you switch the tabs, the object won't be reinitialised.
Here is my full code
class BaseWidget extends StatefulWidget {
final String title;
BaseWidget(this.title);
_BaseWidgetState createState() => _BaseWidgetState();
}
class _BaseWidgetState extends State<BaseWidget> {
#override
Widget build(BuildContext context) {
return Center(child: Text(widget.title));
}
}
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int _pageIndex = 0;
List<Widget> _children;
#override
void initState() {
super.initState();
_children = [
BaseWidget('Home'),
BaseWidget('Business'),
BaseWidget('School')
];
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: IndexedStack(
children: _children,
index: _pageIndex,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(
icon: Icon(Icons.school), title: Text('School')),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
_pageIndex = index;
});
}
}
Please try this code, I have edited the BaseWidget class
class BaseWidget extends StatefulWidget {
final String title;
BaseWidget(this.title);
_BaseWidgetState createState() => _BaseWidgetState();
}
class _BaseWidgetState extends State<BaseWidget> {
#override
Widget build(BuildContext context) {
return Center(child: Text(widget.title));
}
}
I am currently trying to retrieve data (tags) from a REST API and use the data to populate a dropdown menu which I can successfully do but upon selection of the item, I get the following error which according to this would mean that the "selected value is not member of the values list":
items == null || value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1': is not true.
This occurs after the dropdown menu shows my selected item. However, this is error should not be occurring as I've done the necessary logging to check that the data is indeed assigned to the list in question. Could anyone help me resolve this issue? I have isolated it to down to it originating in the setState() method in onChanged of DropdownButton but can't seem to understand why that should be causing an issue. Any help would be deeply appreciated!
My code is as follows:
class _TodosByTagsHomePageState extends State<TodosByTagsHomePage> {
Tag selectedTag;
final Logger log = new Logger('TodosByTags');
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: ListView(
children: <Widget>[
FutureBuilder<List<Tag>> (
future: fetchTags(),
builder: (context, snapshot) {
if (snapshot.hasData) {
log.info("Tags are present");
_tagsList = snapshot.data;
return DropdownButton<Tag>(
value: selectedTag,
items: _tagsList.map((value) {
return new DropdownMenuItem<Tag>(
value: value,
child: Text(value.tagName),
);
}).toList(),
hint: Text("Select tag"),
onChanged: (Tag chosenTag) {
setState(() {
log.info("In set state");
selectedTag = chosenTag;
Scaffold.of(context).showSnackBar(new SnackBar(content: Text(selectedTag.tagName)));
});
},
) ;
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Container(width: 0.0, height: 0.0);
}),
])
);
}
// Async method to retrieve data from REST API
Future<List<Tag>> fetchTags() async {
final response =
await http.get(REST_API_URL);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
var result = compute(parseData, response.body);
return result;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
static List<Tag> parseData(String response) {
final parsed = json.decode(response);
return (parsed["data"] as List).map<Tag>((json) =>
new Tag.fromJson(json)).toList();
}
List<Tag> _tagsList = new List<Tag>();
}
// Model for Tag
class Tag {
final String tagName;
final String id;
final int v;
Tag({this.id, this.tagName, this.v});
factory Tag.fromJson(Map<String, dynamic> json) {
return new Tag(
id: json['_id'],
tagName: json['tagName'],
v: json['__v'],
);
}
}
update your code like this
I think issues that when calling setState in FutureBuilder that call fetchTags() move fetchTags() to initState() for once call
class _TodosByTagsHomePageState extends State<TodosByTagsHomePage> {
Tag selectedTag;
Future<List<Tag>> _tags;
#override
void initState() {
_tags = fetchTags();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: ListView(children: <Widget>[
FutureBuilder<List<Tag>>(
future: _tags,
builder: (context, snapshot) {
if (snapshot.hasData) {
return DropdownButton<Tag>(
value: selectedTag,
items: snapshot.data.map((value) {
print(value);
return DropdownMenuItem<Tag>(
value: value,
child: Text(value.tagName),
);
}).toList(),
hint: Text("Select tag"),
onChanged: (Tag chosenTag) {
setState(() {
selectedTag = chosenTag;
});
},
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Container(width: 0.0, height: 0.0);
}),
]));
}
I got the below geolocation.dart file, that works perfectly as stand alone widget:
import 'package:flutter/material.dart';
import 'package:location/location.dart';
import 'package:flutter/services.dart';
import 'package:simple_permissions/simple_permissions.dart';
class LocationField extends StatefulWidget {
const LocationField({
this.fieldKey,
this.onSaved,
});
final Key fieldKey;
final FormFieldSetter<String> onSaved;
#override
_LocationFieldState createState() => _LocationFieldState();
}
class _LocationFieldState extends State<LocationField> {
Location _location = new Location();
final lat = TextEditingController();
final lon = TextEditingController();
// #override
// void initState() {
// super.initState();
// lat.addListener(_addLatValue);
// lon.addListener(_addLonValue);
//}
#override
Widget build(BuildContext context) {
return Row(
textDirection: TextDirection.rtl,
children: <Widget>[
FlatButton(
child: const Icon(Icons.my_location),
onPressed: () => _getLocation(),
),
Expanded(child: Column(
textDirection: TextDirection.ltr,
children: <Widget>[
TextFormField(
controller: lat,
decoration: InputDecoration(
prefixIcon: Text("Latittude: ")
),
),
TextFormField(
controller: lon,
decoration: InputDecoration(
prefixIcon: Text("Longitude: ")
),
)
])
)
],
);
}
_getLocation() async {
Map<String, double> location;
var error = null;
try {
await SimplePermissions.requestPermission(Permission.AccessFineLocation);
location = await _location.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
error = 'Permission denied';
} else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {
error =
'Permission denied - please ask the user to enable it from the app settings';
}
location = null;
}
print("error $error");
setState(() {
lat.text = ('${location["latitude"]}');
lon.text = ('${location["longitude"]}');
});
}
}
And display the below, at which the location coordinate appear upon clicking the location icon, as below:
I can also insert it as a widget in my main app, as:
class _SignUpPageState extends State<SignUpPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
LocationField(),
RaisedButton(
onPressed: signUp,
child: Text('Sign up'),
),
],
)
),
);
}
void signUp() {
// what to write here to get the geolocation points lat/lon printed?
}
My question, is: How can I get the geolocation points lat/lon printed upon clicking the signup button, how can I get the value of the 2 fields from the sub-widget?
In Flutter, passing state down the widget tree is quite easy using InheritedWidget & co., while passing data upwards actually involves some thinking.
Similar to the TextEditingControllers you're using, you could create a LocationController that holds the location data:
class LocationController {
Location _location = Location();
get location => _location;
set location(Location val) {
_location = val;
if (onChanged != null) _onChanged(val);
}
VoidCallback _onChanged;
}
This controller can then be passed to the LocationField like this:
class LocationField extends StatefulWidget {
LocationField({
this.fieldKey,
#required this.controller,
this.onSaved,
});
final Key fieldKey;
final LocationController controller;
final FormFieldSetter<String> onSaved;
#override
_LocationFieldState createState() => _LocationFieldState();
}
class _LocationFieldState extends State<LocationField> {
final lat = TextEditingController();
final lon = TextEditingController();
#override
void initState() {
super.initState();
widget.controller._onChanged = (location) => setState(() {
lat.text = ('${location["latitude"]}');
lon.text = ('${location["longitude"]}');
});
lat.addListener(_addLatValue);
lon.addListener(_addLonValue);
}
#override
Widget build(BuildContext context) { ... }
_getLocation() async {
String error;
try {
await SimplePermissions.requestPermission(Permission.AccessFineLocation);
widget.controller.location = await _location.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
error = 'Permission denied';
} else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {
error =
'Permission denied - please ask the user to enable it from the app settings';
}
location = null;
}
print("error $error");
}
}
Then, in your widget up the tree, you can access the controller to retrieve the location:
class _SignUpPageState extends State<SignUpPage> {
LocationController controller = LocationController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
LocationField(controller: controller),
RaisedButton(onPressed: signUp, child: Text('Sign up')),
],
)
),
);
}
void signUp() {
final location = controller.location;
}
}
An added benefit is that you could set the controller's location from the widget up in the tree and the LocationField will automatically rebuild to reflect that change.
I would like to have a different fillColor for the TextField when it is in the focused state and a different one in the normal state. How to achieve this kind of behavior?
Similarly, is it possible to customize other styles as well based on the state of the TextField?
Edit:
I was aiming for a color transition something in lines of this for mobile with flutter.
You can pass your own FocusNode object to your text field's focusNode attribute. FocusNode has addListener method in which you can call setState and thus re-render your widget.
class _ChangingColorsExampleState extends State<ChangingColorsPage> {
FocusNode _focusNode;
#override
void dispose() {
super.dispose();
_focusNode.dispose();
}
#override
void initState() {
super.initState();
_focusNode = new FocusNode();
_focusNode.addListener(_onOnFocusNodeEvent);
}
_onOnFocusNodeEvent() {
setState(() {
// Re-renders
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor: _getAppBarBackgroundColor(),
title: new Text('Changing Colors'),
),
body: new Container(
color: _getContainerBackgroundColor(),
padding: new EdgeInsets.all(40.0),
child: new TextField(
style: new TextStyle(color: _getInputTextColor()),
focusNode: _focusNode,
)
),
);
}
Color _getContainerBackgroundColor() {
return _focusNode.hasFocus ? Colors.blueGrey : Colors.white;
}
Color _getAppBarBackgroundColor() {
return _focusNode.hasFocus ? Colors.green : Colors.red;
}
Color _getInputTextColor() {
return _focusNode.hasFocus ? Colors.white : Colors.pink;
}
}
How to get widget's absolute coordinates on a screen in Flutter?
Or its offset in a parent
Example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Simple app',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SimpleScreen(
title: 'Simple screen',
),
);
}
}
class SimpleScreen extends StatefulWidget {
final String title;
SimpleScreen({Key key, this.title}) : super(key: key);
#override
_SimpleScreenState createState() => new _SimpleScreenState();
}
class _SimpleScreenState extends State<SimpleScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
width: 48.0,
height: 48.0,
color: Colors.blue,
),
),
);
}
}
Goal is to get Container's offset in parent. If Center's size is 48.0, then Container's offset in Center's parent/root
Condition: you don't know the wrapper layout (it's a library widget), should be flexible, without hardcoded values
Thanks
You can use this extension I wrote (requires Dart 2.6):
extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
final translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
final offset = Offset(translation.x, translation.y);
return renderObject!.paintBounds.shift(offset);
} else {
return null;
}
}
}
Example how to use it:
final containerKey = GlobalKey();
Container(
key: containerKey,
width: 100,
height: 50,
)
void printWidgetPosition() {
print('absolute coordinates on screen: ${containerKey.globalPaintBounds}');
}
Also you can see similar solution from Daniel, but using BuildContext (using GlobalKey is relatively expensive).
I changed #vovahost extension function, because it's not working in transformed widgets(for example widgets inside RotatedBox). This is working on all widgets.
extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
final matrix = renderObject?.getTransformTo(null);
if (matrix != null && renderObject?.paintBounds != null) {
final rect = MatrixUtils.transformRect(matrix, renderObject!.paintBounds);
return rect;
} else {
return null;
}
}
}
According to documentation using GlobalKey is relatively expensive. U can use just an Element(BuildContext) to calculate it's global bounds. I improved solution above from #vovahost:
extension GlobalPaintBounds on BuildContext {
Rect? get globalPaintBounds {
final renderObject = findRenderObject();
final translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
final offset = Offset(translation.x, translation.y);
return renderObject!.paintBounds.shift(offset);
} else {
return null;
}
}
}
Maybe this simple widget is what you want :
rect_getter 0.0.1
This is a widget provide a simple way to get child's rectangle information after rendered.