How to pass data from StatefulWidget to StatefulWidget, (error) - flutter

I am totally flutter beginner.
What I want to do is pass the data (by TextController) from StatefulWidget to another one.
Here is my code (passive Widget)
import 'package:flutter/material.dart';
class VocabularyText extends StatefulWidget {
final String text;
// ignore: sort_constructors_first
const VocabularyText ({ Key key, this.text }): super(key: key);
#override
_VocabularyTextState createState() => _VocabularyTextState();
}
class _VocabularyTextState extends State<VocabularyText> {
Offset offset = Offset.zero;
#override
Widget build(BuildContext context) {
return Container(
child: Positioned(
left: offset.dx,
top: offset.dy,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
offset = Offset(
offset.dx + details.delta.dx, offset.dy + details.delta.dy);
});
},
child: const SizedBox(
width: 300,
height: 300,
child: Padding(
padding: EdgeInsets.all(8),
child: Center(
child: Text(
'a',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 28,
color: Colors.red
)
),
),
),
)),
),
);
}
}
The thing is here
child: Text(
//
widget.text,
//
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 28,
color: Colors.red
)
),
According to my research, this should work, but it doesn't. Why did I make a mistake?
Here is references
How to add a draggable "textfield" to add text over images in flutter?
Passing Data to a Stateful Widget
Thank you in advance.
edit

i answered before seeing the image it wasn't there
after seeing the image
what causing the problem is this
widget.text
the correct way to use it in Text widget is like this
Text('${widget.text}'),
i would suggest that you do the following
to send data to Statefull Widget first you use Navigator or any other method to open this widget to the user like so
return GestureDetector(
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
//note here between the () you pass the variable
Categories(companies[index].id, companies[index].name)),
)},
and then to receive them you do like this in my case its Categories Class
class Categories extends StatefulWidget {
//getting company id from home page
final int companyId;
final companyName;
Categories(this.companyId , this.companyName);
#override
_CategoriesState createState() => _CategoriesState();
}
class _CategoriesState extends State<Categories> {
#override
Widget build(BuildContext context) {
...... rest of the code
and now to use the data you can do like this for example
widget.companyId
this was an example from my code now lets jump to your code
to receive the text from the text editing controller you do
class TextReceiver extends StatefulWidget {
//getting company id from home page
final String userInput;
TextReceiver(this.userInput);
#override
TextReceiver createState() => _TextReceiver();
}
//to use it
widget.userInput
now to send it you send it through Material Navigator
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TextReceiver(TextEditingController.text)),
)},
note that you should pass it as TextEditingController.text because the constructor in TextReceiver is specifying the type to String if you passed TextEditingController then the type wouldn't be String it will be TextEditingController type
all of this code is for example and it would't be like your code but it will give you the idea
refer to official docs https://flutter.dev/docs/cookbook/navigation/passing-data
Edit : remove const from this line
child: const SizedBox(
rest of the code
)
to this
child: SizedBox(
rest of the code
)

Related

How to dynamically add a TextFormField with initialization data in the middle of a list of UI TextFormFields?

I have a list of dynamic forms where I need to add and remove form fields between two fields dynamically. I am able to add/remove form fields from the bottom of the list properly.
However, when I try to add a form field in between two form fields the data for the field does not update correctly.
How can I correctly add a field in between the two fields and populate the data correctly?
import 'package:flutter/material.dart';
class DynamicFormWidget extends StatefulWidget {
const DynamicFormWidget({Key? key}) : super(key: key);
#override
State<DynamicFormWidget> createState() => _DynamicFormWidgetState();
}
class _DynamicFormWidgetState extends State<DynamicFormWidget> {
List<String?> names = [null];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dynamic Forms'),
),
body: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
itemBuilder: (builderContext, index) => Row(
children: [
Flexible(
child: TextFormField(
initialValue: names[index],
onChanged: (name) {
names[index] = name;
debugPrint(names.toString());
},
decoration: InputDecoration(
hintText: 'Enter your name',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8))),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: () {
setState(() {
if(index + 1 == names.length){
names.add( null); debugPrint('Added: $names');
} else {
names.insert(index + 1, null); debugPrint('Added [${index+1}]: $names');
}
});
},
color: Colors.green,
iconSize: 32,
icon: const Icon(Icons.add_circle)),
),
Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: (index == 0&& names.length == 1)
? null
: () {
setState(() {
names.removeAt(index);
});
debugPrint('Removed [$index]: $names');
},
color: Colors.red,
iconSize: 32,
icon: const Icon(Icons.remove_circle)),
),
],
),
separatorBuilder: (separatorContext, index) => const SizedBox(
height: 16,
),
itemCount: names.length,
),
);
}
}
Basically the problem is that Flutter is confused about who is who in your TextFormField list.
To fix this issue simply add a key to your TextFormField, so that it can be uniquely identified by Flutter:
...
child: TextFormField(
initialValue: names[index],
key: UniqueKey(), // add this line
onChanged: (name) {
...
If you want to learn more about keys and its correct use take a look at this.
The widget AnimatedList solves this problem, it keep track of the widgets as a list would do and uses a build function so it is really easy to sync elements with another list. If you end up having a wide range of forms you can make use of the InheritedWidget to simplify the code.
In this sample i'm making use of the TextEditingController to abstract from the form code part and to initialize with value (the widget inherits from the ChangeNotifier so changing the value will update the text in the form widget), for simplicity it only adds (with the generic text) and removes at an index.
To make every CustomLineForm react the others (as in: disable remove if it only remains one) use a StreamBuilder or a ListModel to notify changes and make each entry evaluate if needs to update instead of rebuilding everything.
class App extends StatelessWidget {
final print_all = ChangeNotifier();
App({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FormList(print_notifier: print_all),
floatingActionButton: IconButton(
onPressed: print_all.notifyListeners,
icon: Icon(Icons.checklist),
),
),
);
}
}
class FormList extends StatefulWidget {
final ChangeNotifier print_notifier;
FormList({required this.print_notifier, super.key});
#override
_FormList createState() => _FormList();
}
class _FormList extends State<FormList> {
final _controllers = <TextEditingController>[];
final _list_key = GlobalKey<AnimatedListState>();
void print_all() {
for (var controller in _controllers) print(controller.text);
}
#override
void initState() {
super.initState();
widget.print_notifier.addListener(print_all);
_controllers.add(TextEditingController(text: 'Inital entrie'));
}
#override
void dispose() {
widget.print_notifier.removeListener(print_all);
for (var controller in _controllers) controller.dispose();
super.dispose();
}
void _insert(int index) {
final int at = index.clamp(0, _controllers.length - 1);
_controllers.insert(at, TextEditingController(text: 'Insert at $at'));
// AnimatedList will take what is placed in [at] so the controller
// needs to exist before adding the widget
_list_key.currentState!.insertItem(at);
}
void _remove(int index) {
final int at = index.clamp(0, _controllers.length - 1);
// The widget is replacing the original, it is used to animate the
// disposal of the widget, ex: size.y -= delta * amount
_list_key.currentState!.removeItem(at, (_, __) => Container());
_controllers[at].dispose();
_controllers.removeAt(at);
}
#override
Widget build(BuildContext context) {
return AnimatedList(
key: _list_key,
initialItemCount: _controllers.length,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
itemBuilder: (ctx, index, _) {
return CustomLineForm(
index: index,
controler: _controllers[index],
on_insert: _insert,
on_remove: _remove,
);
},
);
}
}
class CustomLineForm extends StatelessWidget {
final int index;
final void Function(int) on_insert;
final void Function(int) on_remove;
final TextEditingController controler;
const CustomLineForm({
super.key,
required this.index,
required this.controler,
required this.on_insert,
required this.on_remove,
});
#override
Widget build(BuildContext context) {
return Row(
children: [
Flexible(
child: TextFormField(
controller: controler,
),
),
IconButton(
icon: Icon(Icons.add_circle),
onPressed: () => on_insert(index),
),
IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () => on_remove(index),
)
],
);
}
}

I can't get a function to work from another file

I have a function start timer that requires a stateful widget and I need it to be called in another Class which is also a stateful widget class, I have tried making an object of the class PhoneAuthState phone = PhoneAuthState() and taping it into the function but the timer wouldn't start counting down after pressing the send button,
Here is the first class
`class PhoneAuth extends StatefulWidget {
const PhoneAuth({Key? key}) : super(key: key);
#override
State<PhoneAuth> createState() => PhoneAuthState();
}
class PhoneAuthState extends State<PhoneAuth> {
int start = 30;
#override
Widget build(BuildContext context) {
return Scaffold(
ReusableTField(),
RichText(
text: TextSpan(children: [
TextSpan(
text: "Send OTP again in",
style: TextStyle(color: Colors.orange, fontSize: 17),
),
TextSpan(
text: " 00:$start",
style: TextStyle(color: Colors.redAccent, fontSize: 17),
),
TextSpan(
text: "sec",
style: TextStyle(color: Colors.orange, fontSize: 17),
),
]),
),
**Here is the function below**
void startTimer() {
const onsec = Duration(seconds: 1);
Timer timer = Timer.periodic(onsec, (timer) {
if (start == 0) {
setState(() {
timer.cancel();
});
} else {
setState(() {
start--;
});
}
});
`
Then this is the class(in another file) that needs the function to start counting down when the send button is tapped nothing happens on the emulator, even after the startTimer contains setState
class ReusableTField extends StatefulWidget {
#override
State<ReusableTField> createState() => _ReusableTFieldState();
}
class _ReusableTFieldState extends State<ReusableTField> {
#override
Widget build(BuildContext context) {
return Container(
suffixIcon: InkWell(
onTap: () {} // Here is where I called the function with PhoneAuthState phone =
// PhoneAuthState()... phone.startTimer() but the code does not work,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 14),
child: Text(),
),
)
if you want to use the same code on multiple classes define a static class and write this method as a static method and call it from anywhere.
I think you function is at wrong place keep it out of build method and then call.

Slider not updating itself

I saw another StackOverflow post, none of that worked for me.
I am a flutter newbie.
I am showing modal bottom sheet from a stateful widget called 'Home'
showMaterialModalBottomSheet(
context: context,
bounce: true,
duration: const Duration(milliseconds: 600),
builder: (context) => SingleChildScrollView(
controller: ModalScrollController.of(context),
child: bookmark(id: books[index]['_id'], totalPages: int.parse(books[index]['total']), completedPages: int.parse(books[index]['done']),);
//another 'Stateful widget'
),
);
Another stateful widget name 'bookmark' looks like this
int toggleIndex = 0;
double dragPercentage = 0;
double dragUpdate = 0;
//variables common to both main widget and this widget
class bookmark extends StatefulWidget {
final int totalPages;
final int completedPages;
final int id;
bookmark({#required this.completedPages, #required this.totalPages, #required this.id});
#override
State<bookmark> createState() => _bookmarkState();
}
class _bookmarkState extends State<bookmark> {
#override
Widget build(BuildContext context) {
return
Container(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: SizedBox(
width: double.maxFinite,
child: CupertinoSlider(
value: dragUpdate,
max: 100,
min: 0,
onChanged: (double dragUpdate) {
setState(() {
dragUpdate = dragUpdate;
dragPercentage = dragUpdate * widget.totalPages;
pages.text = (dragPercentage / 100.0).toStringAsFixed(0);
});
},
),
),
),
],
)
);
}
}
The slider is updating the value but is not updating itself.
to be clear, there are 2 stateful widgets named home and bookmark. I am calling showMaterialModalBottomSheet from home and building a bookmark.
what I tried?
I already tried to wrap my slider with a statefulbuilder and used its state to update the values.

I would like to be able to change the Fontfamily when I press the ElevatedButton in flutter

I want the Textfield and the fontFamily in the picture at the top to change when the ElevatedButton is pressed. What should I do? I want to try GetX.
home_page.dart
ListView(
scrollDirection: Axis.horizontal,
children: [
FontFamilyWidget(
fontFamily: 'Cafe24SsurroundAir',
buttonName: '✈️ 카페24 써라운드 에어'),
const SizedBox(width: 5),
FontFamilyWidget(
fontFamily: 'RIDIBatang', buttonName: '📚 리디바탕'),
const SizedBox(width: 5),
FontFamilyWidget(
fontFamily: 'InkLipquid', buttonName: ' 잉크립퀴드체'),
const SizedBox(width: 5),
.
.
.
fontfamily_widget.dart
part of 'font_controller.dart'
String select = 'RIDIBatang';
void onClickChangeFont(String changedFontFamily) {
select = changedFontFamily;
update();
}
part of 'fontfamily_widget.dart'
class FontFamilyWidget extends StatefulWidget {
final String fontFamily;
final String buttonName;
final String changedFontFamily;
const FontFamilyWidget({
Key? key,
required this.fontFamily,
required this.buttonName,
required this.changedFontFamily,
}) : super(key: key);
#override
_FontFamilyWidgetState createState() => _FontFamilyWidgetState();
}
class _FontFamilyWidgetState extends State<FontFamilyWidget> {
FontController c = Get.find();
#override
Widget build(BuildContext context) {
return ElevatedButton(
//텍스트필드와 상단 텍스트의 fontfamily를 변경해주는 함수
onPressed: () {
c.onClickChangeFont(widget.changedFontFamily);
},
child: Text(
widget.buttonName,
style: TextStyle(fontFamily: widget.fontFamily),
),
);
}
}
After that, FontFamilyWidget was applied to home_page.dart.

Passing data between Widgets without using Navigator?

I've some struggling with flutter.
I've two widget Details screen so in the first Details, I have a list and I want to send it to the second Screen without using navigator.
So if there is anyone who can help me I will be very thankful.
Details :
List<String> _instructions = [];
class Details extends StatefulWidget {
#override
_DetailsState createState() => new _DetailsState();
}
class _DetailsState extends State<Details> {
Expanded(
child: Container(
margin: EdgeInsets.all(3),
child: OutlineButton(
highlightElevation: 21,
color: Colors.white,
shape: StadiumBorder(),
textColor: Colors.lightBlue,
child: Text(
'ENVOYER',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
color: Colors.lightBlue,
),
),
borderSide: BorderSide(
color: Colors.lightBlue,
style: BorderStyle.solid,
width: 1),
onPressed: () {
},
),
),
),
}}
so what I want is that,when I press the button I will send my list to the next widget without using navigator.
you can use the sharedpreferance which is the simple xml that belongs to the application. And this is how you can set it
Future<bool> setStringList(String key, List<String> value) =>
_setValue('StringList', key, value);
Future<bool> setStringList (
String key,
List<String> value
)
For more info here is a link
And you can get your List by
List<String> getStringList(String key) {
List<Object> list = _preferenceCache[key];
if (list != null && list is! List<String>) {
list = list.cast<String>().toList();
_preferenceCache[key] = list;
}
return list;
}
Also you can use sqflite
Suppose you have two pages, namely 'page1.dart' and 'page2.dart', both want to access the same list:
Create another dart file 'GlobalVariables.dart', inside this file, create a class gv.
Inside this class gv, create a static list by using:
static List <String> listAnyList = [];
import 'GlobalVariables.dart' in the 2 pages that need to access this list.
Now, in page1.dart and page2.dart,
you can use gv.listAnyList to access the 'Global List'.
Use 'Global Static Variables' if a variable is needed in many dart files, e.g. the 'User ID', then you can simply use gv.strUserID to access it in any pages you want.
I think a more appropriate approach should be similar to how android fragments connect to each other using bloc pattern or master-details flow.
I created an example repository to show the complete concept.
In short, the idea is to create a class with StreamController inside. Both widgets will have a reference to bloc instance. When the first widget wants to send data to the second it adds a new item to Stream. The second listen to the stream and updates its content accordingly.
Inside bloc:
StreamController<String> _selectedItemController = new BehaviorSubject();
Stream<String> get selectedItem => _selectedItemController.stream;
void setSelected(String item) {
_selectedItemController.add(item);
}
First fragment:
class FragmentList extends StatelessWidget {
final Bloc bloc;
const FragmentList(
this.bloc, {
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.selectedItem,
initialData: "",
builder: (BuildContext context, AsyncSnapshot<String> screenType) {
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
selected: bloc.items[index] == screenType.data,
title: Text(bloc.items[index]),
onTap: () {
bloc.setSelected(bloc.items[index]);
},
);
},
itemCount: bloc.items.length,
);
},
);
}
}
The second fragment:
class FragmentDetails extends StatelessWidget {
final Bloc bloc;
const FragmentDetails(
this.bloc, {
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: StreamBuilder(
initialData: "Nothing selected",
stream: bloc.selectedItem,
builder: (BuildContext context, AsyncSnapshot<String> screenType) {
final info = screenType.data;
return Text(info);
},
),
);
}
}