There is a signup section in My App with a main page and 3 widgets form inside it. form 1 and form 2 contain some TextFormField and on the last page, I get some pictures from the user. the user, enter data and click the next button on the main page, now should be validate entered data, and if the data was without any problem, finally send a request with the entered data to a server and step++ and go to the next form.
my question is how can I do this job and get child form data on the main page?
You can use callback function. eg void Function(String)
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
YourFormWidget(
onFirstNameChanged: (String firstName) {
print(firstName);
},
onLastNameChanged: (String lastName) {
print(lastName);
},
),
TextButton(
onPressed: () {},
child: Text("Submit"),
)
],
),
);
}
}
class YourFormWidget extends StatelessWidget {
const YourFormWidget({
Key? key,
required this.onFirstNameChanged,
required this.onLastNameChanged,
}) : super(key: key);
final void Function(String) onFirstNameChanged;
final void Function(String) onLastNameChanged;
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(onChanged: onFirstNameChanged),
TextField(onChanged: onFirstNameChanged),
],
);
}
}
This is called state management. https://flutter.dev/docs/development/data-and-backend/state-mgmt/options
I suggest provider. Create the object in the main page and update in one of it's children Widget.
An example with Provider https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
Related
I am currently trying to execute a FutureBuilder future function in an Autorouter - the library (https://pub.dev/packages/auto_route#tab-navigation) - and it works perfectly. However, as I am using a FutureBuilder in the tabs, the future is only executed once - the first time I access the tab - and isn't re-executed again when I leave the tab and come back to it. I would like to be able to execute the future function every time I access the tab since the future is reading data from the database.
I have tried the following:
making the widget stateful and executing setState function to force a rebuild
using the overridden function didChangeDependencies
override the deactivate function of the widget
None of the above seem to work.
And after going through the documentation of the Autoroute library, I haven't come across any explanation on how to force a rebuild of the current tab.
I welcome any suggestions.
Thank you
NB: I'm using Flutter to make a mobile application, the solution doesn't necessarily have to work on a web application.
Tab View
class MyTabView extends StatelessWidget {
MyTabView({Key? key}) : super(key: key);
final tabRoutes = [
TabRoute1(),
TabRoute2(),
];
#override
Widget build(BuildContext context) {
return AutoTabsScaffold(
routes: tabRoutes,
bottomNavigationBuilder: (_, tabRouter) {
return BottomNavigationBar(
currentIndex: tabRouter.activeIndex,
onTap: tabRouter.setActiveIndex,
items: [
BottomNavigationBarItem(
icon: BaseIcon(
svgFileName: 'calendar.svg',
),
label: LocaleKeys.careProfessionalLabelProfile.tr(),
),
BottomNavigationBarItem(
icon: BaseIcon(
svgFileName: 'wallet.svg',
),
label: LocaleKeys.careProfessionalLabelChat.tr(),
),
],
);
},
);
}
}
Tab with child that contains FutureBuilder
class TabRoute2 extends StatefulWidget {
const TabRoute2({Key? key}) : super(key: key);
#override
State<TabRoute2> createState() => _TabRoute2State();
}
class _TabRoute2State extends State<TabRoute2> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// ---- END SPACER
Expanded(
child: ShowFutureData(),
),
],
);
}
}
ShowFutureData
class ShowFutureData extends StatefulWidget {
const ShowFutureData({
super.key,
});
#override
State<ShowFutureData> createState() =>
_ShowFutureDataState();
}
class _ShowFutureDataState extends State<ShowFutureData> {
late FutureDataObjectProvider futureObjectProvider;
#override
initState() {
super.initState();
futureObjectProvider = context.read<FutureDataObjectProvider>();
}
#override
Widget build(BuildContext context) {
retrieved = futureObjectProvider.retrieveAllData();
return FutureBuilder(
future: retrieved, // only executed when the tab is first accessed
initialData: const [],
builder: (context, snapshot) {
// do something with the data
},
);
}
}
You can reassign the future to recall the future.
FutureBuilder(
future: myFuture,
Then reassign it again
myFuture = getData();
I have an app with multiple pages and a filter that is shared on all of them. When the filter is changed it triggers an update on all the pages.
As an example this are the providers that use the same filter (filterProvider):
final provider1 = FutureProvider<List<...>>((ref) async {
final String filter = ref.watch(filterProvider);
return ref.read(apiProvider).getFirstPageData(filter);
});
final provider2 = FutureProvider<List<...>>((ref) async {
final String filter = ref.watch(filterProvider);
return ref.read(apiProvider).getSecondPageData(filter);
});
This is the page that navigates to the second one:
class FirstPage extends StatelessWidget {
const FirstPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final data = ref.watch(provider1);
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
const Filter(), // This widget uses filterProvider
data.when(...),
ElevatedButton(
child: Text('Navigate'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => SecondPage(),
)),
),
],
),
);
}
}
And the second page which is also using the same filter:
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final data = ref.watch(provider2);
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
const Filter(), // This widget uses filterProvider
data.when(...),
]),
);
}
}
When the filter is updated, how can I trigger a change only for the current page (top of the navigator stack)?
I tried to use pushReplacement instead of push (as recommended here) and it works as expected but we lose the reference to the previous page, so we have to manually implement a back button which adds a lot of complexity.
Thanks.
I have a doubt, i am new in Flutter and i want to know if there is a better way to do what i want, please see the next example of code.
This is my screen and i have to classes, one is for when the device height is less than 800 and the other when is higher
class MyPageExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
return Scaffold(
body: SafeArea(
child: screenSize.height > 800
? SignupLarge()
: SignupSmall()
)
);
}
}
This are my two StatefulWidgets
class SignupLarge extends StatefulWidget {
const SignupLarge({ Key? key }) : super(key: key);
#override
_SingupLargeState createState() => _SingupLargeState();
}
class _SingupLargeState extends State<SignupLarge> {
#override
Widget build(BuildContext context) {
return Column(
children: [
// Wome widgets...
ElevatedButton(
onPressed: () => signupToFirebase(),
child: Text('Click me')
)
],
);
}
}
class SignupSmall extends StatefulWidget {
const SignupSmall({ Key? key }) : super(key: key);
#override
_SignupSmallState createState() => _SignupSmallState();
}
class _SignupSmallState extends State<SignupSmall> {
#override
Widget build(BuildContext context) {
return ListView(
children: [
// Wome widgets...
ElevatedButton(
onPressed: () => signupToFirebase(),
child: Text('Click me')
)
],
);
}
}
signupToFirebase(){
// I need to use setState here to show the loading widget
// await Firebase.instance.signupWithEmailAndPassword...
// I need to use setState here to hide the loading widget
}
What i want to do is use the method in my classes using the setState, actually i cant because my method is not inside my classes but if i put my method inside i cant use the same method in both classes.
the above code is just an example of how my code looks like, im tryng to show a loading when a user is signed up.
Someone knows the right way to use the setState for my method using the both classes?
You can give that function another function for it to call.
void signupToFirebase(void Function() whenDone) {
//...
whenDone();
}
...
// caller
signupToFirebase(() {
setState(() {
});
});
...
Or even better, you can have that function return a Future if what you wanted to do is to act once it's done.
Future<void> signupToFirebase() {
await FirebaseAuth.instance... signup() kind of function that returns a Future
}
...
// caller
await signupToFirebase();
setState(() {
});
...
I have 2 instances of the same widget(w1 and w2) with a button and an onPressed function. I know to disable the onPress by setting that up with a null value, so far so good. The issue in hand is to solve how to disable w2 onPress when clicking w1 once and enable it again if it is clicked once again.
Even though I send a variable containing if widget has been pressed, disabling never happens because I can not trigger from outside(Widget containing my 2 instances) the setState of each widget separately.
You have a couple of solutions. You could use a more sophisticated state management system (such as Provider or Bloc), but it might be simpler to instead try "lifting state".
"Lifting state" refers to pulling state out of the children and moving it to the parent.
Instead of having the children be stateful, they can become stateless, and the widget that contains them will be made stateful, and will keep track of which buttons are enabled and which are not.
// example button, actual implementation may be different
class MyButton extends StatelessWidget {
final VoidCallback? onPressed;
final String text;
const MyButton({Key? key, this.onPressed, required this.text,}) : super(key: key);
#override
Widget build(BuildContext context) => ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
class ButtonContainer extends StatefulWidget {
// boilerplate
}
class ButtonContainerState extends State<ButtonContainer> {
bool isSecondButtonEnabled = true;
void toggleSecondButton() => setState(() => isSecondButtonEnabled = !isSecondButtonEnabled);
#override
Widget build(BuildContext context) => Row(
children: [
MyButton(
text: "Button 1",
onPressed: toggleSecondButton,
),
MyButton(
text: "Button 2",
onPressed: isSecondButtonEnabled ? someOtherFunction : null,
)
],
);
}
Lets assume a class "SpecialButton" and its State-Class "SpecialButtonState"
class SpecialButton extends StatefulWidget {
bool active = false;
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
decoration:
BoxDecoration(color: this.widget.active ? COLOR_1 : COLOR_2),
child: null);
}
}
In the parent widget, I manage a couple of these buttons. Therefore, I want to assign a state to them. The solution I tried was to introduce a flag "active" in the SpecialButton class which I can easily set to either true or false from the parent widget. I can then use this in the build function of the state class to colorize the button. Unfortunately, this does not work completely as it does not update the button immediately (it needs some kind of state update e.g. by hovering over the element).
My second idea was to introduce this flag as a propper state of the SpecialButtonState class
class SpecialButton extends StatefulWidget {
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
bool active;
#override
void initState() {
super.initState();
this.active = false;
}
activate() {
this.setState(() {
active = true;
});
}
deactivate() {
this.setState(() {
active = false;
});
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: this.active ? COLOR_1 : COLOR_2),
child: null);
}
}
As far as I understood, this would be the correct way to work with flutter but it seems that I can't access the functions "activate" or "deactivate" from either the SpecialButton Class or the Parent Class containing the widget.
So my question is: How can I (directly or indirectly through functions) modify a State from the corresponding StatefulWidget Class or the Parent Widget containing it?
There are already some similar questions about this on here on Stack Overflow where I could find hints both to use or not to use global keys for such behavior which i found misleading. Also, due to the rapid ongoing development of flutter, they are probably outdated so I ask this (similar) question again in relation to this exact use case.
EDIT: I forgot to mention that it is crucial that this flag will be changed after creation therefore It will be changed multiple times during its livetime. This requires the widget to redraw.
It is not neŃessary to use stateful widget for SpecialButton is you case. You can handle active flag with stateless widget and keys. Example code:
class SomeParent extends StatefulWidget {
const SomeParent({Key key}) : super(key: key);
#override
State<SomeParent> createState() => SomeParentState();
}
class SomeParentState extends State<SomeParent> {
bool _button1IsActive = false;
bool _button2IsActive = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SpecialButton(
key: UniqueKey(),
active: _button1IsActive,
),
SizedBox(height: 8),
SpecialButton(
key: UniqueKey(),
active: _button2IsActive,
),
SizedBox(height: 16),
TextButton(
child: Text('Toggle button 1'),
onPressed: () {
setState(() {
_button1IsActive = !_button1IsActive;
});
},
),
SizedBox(height: 8),
TextButton(
child: Text('Toggle button 2'),
onPressed: () {
setState(() {
_button2IsActive = !_button2IsActive;
});
},
),
],
),
);
}
}
class SpecialButton extends StatelessWidget {
final bool active;
const SpecialButton({Key key, this.active = false}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(color: active ? Colors.red : Colors.blue),
);
}
}
SomeParent is my fantasy, just for example. Don't know what your parent is.
Keys are significant here. They tell widget tree when specific widgets with the same type (such as SpecialButton) should be rebuild.
Please try this approach, it should work.
As nvoigt says, your buttons could even be stateless widget , but their parent should be statefull and you should provide them with the corresponding value. e.g.:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
StateLessButton1(isEnabled: isEnabled),
StateLessButton1(isEnabled: !isEnabled),
FloatingActionButton(onPressed: (){
setState(() {
isEnabled = !isEnabled;
});
})
],
);
}
}
Now it just depends on when you want to change that value. If you want to change it inside your buttons, I would recommend you to use a class with ChangeNotifier and a function inside it that changes the value. Otherwise I would recommend not to separate your tree into multiple files