How can i used the multi future in FutureBuilder? - flutter

Begins an asynchronous calls the function and multiple.
However, the response to the second call is always null.
I want to update it to the screen when I get all the results.
sometimes invoked Error msg like this :
type 'List' is not a subtype of type 'Map<dynamic, dynamic>'
class _HomePageState extends State<Home> {
final _formKey = GlobalKey<FormState>();
final _formKey2 = GlobalKey<FormState>();
aboutRes about; // delete
var _callStack = [Gateway.Instance.about(), Wifi.Instance.ssidInfo(0)];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body : Padding(
padding: const EdgeInsets.all(15.0),
child: Column(children: [
Expanded(
child: FutureBuilder(
future : Future.wait(_callStack),
key : _formKey,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Invoked Error is ${snapshot.error}');
} else if (snapshot.hasData) {
return ListView (
padding: const EdgeInsets.all(0.8),
children: [
Container(
child : Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
InformationItem(about: snapshot.data[0]),
WirelessItem(ssid: snapshot.data[1]),
],
),
)
]
);
} else {
return CircularProgressIndicator();
}
}
)
),
]),
)
);
}
}
Future<aboutRes> about() async {
aboutRes res;
ApiResponse response = await get(GATEWAY_ABOUT);
// for jsonSerialization
Map resMap = jsonDecode(response.body);
res = aboutRes.fromJson(resMap);
return res;
}

Related

Display data from Firebase in async - Flutter

I want to create a profil page where I just display informations from the user, but I have trouble to reach the data. When I want to use my variable user it display 'Instance of Future<Map<String, dynamic>>'
If I put the 'Widget build' in async I have an error message who told me : ProfileScreen.build' ('Future Function(BuildContext)') isn't a valid override of 'StatelessWidget.build' ('Widget Function(BuildContext)').
class ProfileScreen extends StatelessWidget {
ProfileScreen({super.key});
#override
Widget build(BuildContext context) {
final user = displayUser();
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: Align(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.topLeft,
child: Column(children: [
Text('Prénom :${user}\nNom :\nEmail :',
textWidthBasis: TextWidthBasis.longestLine),
]),
)
]),
),
persistentFooterButtons: [
SignOutButton(),
BottomNavBar(),
]);
}
// Get user informations
Future<Map<String, dynamic>> displayUser() async {
final User? currentUser = FirebaseAuth.instance.currentUser;
late final userUid = currentUser?.uid;
late final ref = FirebaseDatabase.instance.ref();
final resSnapshot = await ref.child('/utilisateur/' + userUid!).get();
final Map<String, dynamic> user = {};
if (resSnapshot.exists) {
user['id'] = userUid;
for (var value in resSnapshot.children) {
String key = value.key as String;
var val = value.value;
user[key] = val;
}
} else {
print('No data available.');
}
print(user); // This print display exactly the informations I want.
return user;
}
}
Thanks for your help.
Your displayUser is async function and you can't call it inside build method, you need to use FutureBuilder like this:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: FutureBuilder<Map<String, dynamic>>(
future: displayUser(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
Map<String, dynamic> user = snapshot.data ?? {};
return Align(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.topLeft,
child: Column(
children: [
Text(
'Prénom :${user}\nNom :\nEmail :',
textWidthBasis: TextWidthBasis.longestLine,
),
],
),
)
],
),
);
}
}
},
),
persistentFooterButtons: [
SignOutButton(),
BottomNavBar(),
],
);
}
You can customize loading and error state to what you want.
You can load the user in the initstate and then set user using setstate
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
#override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
Map<String, dynamic>? user;
#override
void initState() {
final User? currentUser = FirebaseAuth.instance.currentUser;
late final userUid = currentUser?.uid;
late final ref = FirebaseDatabase.instance.ref();
final resSnapshot = await ref.child('/utilisateur/' + userUid!).get();
Map<String, dynamic> temp = {};
if (resSnapshot.exists) {
temp['id'] = userUid;
for (var value in resSnapshot.children) {
String key = value.key as String;
var val = value.value;
temp[key] = val;
}
} else {
print('No data available.');
}
print(temp);
setState((){
user =temp
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
user != {} ? Text(user.toString()!) : const CircularProgressIndicator()),
);
}
}
change StatelessWidget to StatefulWidget because userInteract on profileScreen,
UserInteraction changes will show on firebase.
class ProfileScreen extends StatefulWidget{
ProfileScreen({super.key});

Setting login cookies in the rest of headers requests Flutter

i'm trying to set the login cookies to the rest of the get requests.
so i use http pachakge and store the login cookies with sharedPreferences and use it in the get request by adding an update function
but i have a problem that when i go to the page i get 400 response just refreshing the page and i get my data and response 200
is there any other solution for setting cookies in the others get requests headers ?
or is there a solution for my bug ?
codes images : [https://ibb.co/kD3dDc9]
[https://ibb.co/25p5fZr]
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:valomnia_reports/Screens/Superviseur%20Screens/SideBar.dart';
import 'user_model.dart';
class SellersPage extends StatefulWidget {
const SellersPage({Key? key}) : super(key: key);
#override
_SellersPage createState() => _SellersPage();
}
class _SellersPage extends State<SellersPage> {
String? finalEmail;
Future? _futureData;
String? rawCookie;
// ignore: must_call_super
void initState() {
getValidationData();
super.initState();
_futureData = getUserApi();
}
Future getValidationData() async {
final SharedPreferences sharedPreferences2 =
await SharedPreferences.getInstance();
var obtainedEmail2 = sharedPreferences2.getString("rawCookie");
setState(() {
rawCookie = obtainedEmail2;
print(rawCookie);
});
}
List<UserModel> userList = [];
Map<String, String> headers = {};
Future<List<UserModel>> getUserApi() async {
http.Response response = await http.get(
Uri.parse('https://valomnia.herokuapp.com/superviseur/getAllVendeurs'),
headers: headers);
response.headers['set-cookie'] = rawCookie!;
updateCookie(response);
var data = jsonDecode(response.body.toString());
String? cookies = response.headers['set-cookie'];
if (response.statusCode == 200) {
for (Map i in data) {
userList.add(UserModel.fromJson(i));
}
print("Cookie : $cookies");
print("200");
return userList;
} else {
print("400");
print(rawCookie);
print(cookies);
return userList;
}
}
void updateCookie(http.Response response) {
String? rawCookie2 = response.headers['set-cookie'];
if (rawCookie2 != null) {
int index = rawCookie2.indexOf(';');
headers['cookie'] =
(index == -1) ? rawCookie2 : rawCookie2.substring(0, index);
}
}
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
Future<Null> _refresh() {
return getUserApi().then((userList) {
setState(() => userList = userList);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavigationDrawerWidget(),
appBar: AppBar(
title: Text(
'Sellers list',
),
centerTitle: true,
backgroundColor: Colors.green,
),
body: Column(
children: [
Expanded(
child: FutureBuilder(
future: _futureData,
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
return RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _refresh,
child: ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
ReusbaleRow(
title: 'Id',
value:
snapshot.data![index].id.toString()),
ReusbaleRow(
title: 'Name',
value: snapshot.data![index].name
.toString()),
ReusbaleRow(
title: 'Username',
value: snapshot.data![index].username
.toString()),
ReusbaleRow(
title: 'DateCreated',
value: snapshot.data![index].email
.toString()),
],
),
),
);
}),
);
}
},
),
)
],
),
);
}
}
// ignore: must_be_immutable
class ReusbaleRow extends StatelessWidget {
String title, value;
ReusbaleRow({Key? key, required this.title, required this.value})
: super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title),
Text(value),
],
),
);
}
} ```

Flutter Execute Http request on startup using FutureBuilder not working

This is the homepage code of the app I'm currently developing and I have to get all data from API. Therefore I've come up _getLatest that gets the data from the URL which is a list of maps and the data goes into _latest. And I implemented the future builder based on Flutter: Execute a function on Startup - wait for HTTP response parser to extract image URL but things are not quite done.
First of all there are two blue underlines: 1. Future<String> _getLatest() async { says
This function has a return type of 'FutureOr', but doesn't end with a return statement. 2. builder: (BuildContext context, AsyncSnapshot<String> snapshot) { says This function has a return type of 'Widget', but doesn't end with a return statement.
And the main problem is the homepage is that snapshot.connectionState doesn't change to done state so it's loading data eternally. And I'm pretty sure it's because of the code not the URL, the API works fine.
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:kzstats/common/AppBar.dart';
import 'package:kzstats/common/Drawer.dart';
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
var _latest = [];
Future<String> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
var httpClient = new HttpClient();
var result;
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
var json = await response.transform(utf8.decoder).join();
var data = jsonDecode(json);
result = data;
} else {}
} catch (exception) {}
setState(() {
_latest = result;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HomepageAppBar(currentPage),
drawer: HomepageDrawer(),
body: FutureBuilder<String>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${_latest[index]}'),
);
},
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
);
}
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () {
_getLatest();
});
}),
);
}
}
First of all, you have done some wrong things,
if you are using FutureBuilder you can use a snapshot inside it so no need for _latest variable.
you can also use http package for easily requesting data.
inside your _getLatest() function you didn't returned the value and also it was not String.
also you can use Model class for easily assessing data after fetching json.
For your Problem my solution is
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:http/http.dart' as http;
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
Future<List<KzTimer>> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
List<KzTimer> result;
try {
var response = await http.get(Uri.parse(url));
if (response.statusCode == HttpStatus.ok) {
result = kzTimerFromJson(response.body);
} else {
print('Something went wrong!');
}
} catch (exception) {}
return result;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<KzTimer>>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<List<KzTimer>> snapshot) {
return snapshot.hasData ?
ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${snapshot.data[index].playerName}'),
//_latest[index].playerName
);
},
) :
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
) ;
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () {
_getLatest();
});
}),
);
}
}
Method to convert the response data to your model class data
List<KzTimer> kzTimerFromJson(String str) => List<KzTimer>.from(json.decode(str).map((x) => KzTimer.fromJson(x)));
Model class
class KzTimer {
KzTimer({
this.id,
this.steamid64,
this.playerName,
this.steamId,
this.serverId,
this.mapId,
this.stage,
this.mode,
this.tickrate,
this.time,
this.teleports,
this.createdOn,
this.updatedOn,
this.updatedBy,
this.place,
this.top100,
this.top100Overall,
this.serverName,
this.mapName,
this.points,
this.recordFilterId,
this.replayId,
});
int id;
String steamid64;
String playerName;
String steamId;
int serverId;
int mapId;
int stage;
String mode;
int tickrate;
double time;
int teleports;
DateTime createdOn;
DateTime updatedOn;
int updatedBy;
int place;
int top100;
int top100Overall;
String serverName;
String mapName;
int points;
int recordFilterId;
int replayId;
factory KzTimer.fromJson(Map<String, dynamic> json) => KzTimer(
id: json["id"],
steamid64: json["steamid64"],
playerName: json["player_name"],
steamId: json["steam_id"],
serverId: json["server_id"],
mapId: json["map_id"],
stage: json["stage"],
mode: json["mode"],
tickrate: json["tickrate"],
time: json["time"].toDouble(),
teleports: json["teleports"],
createdOn: DateTime.parse(json["created_on"]),
updatedOn: DateTime.parse(json["updated_on"]),
updatedBy: json["updated_by"],
place: json["place"],
top100: json["top_100"],
top100Overall: json["top_100_overall"],
serverName: json["server_name"],
mapName: json["map_name"],
points: json["points"],
recordFilterId: json["record_filter_id"],
replayId: json["replay_id"],
);
}
Change return type by: Future<void>. You are returning nothing.
You have an if and an else if but you still need the default case when both conditions are false.
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:kzstats/common/AppBar.dart';
import 'package:kzstats/common/Drawer.dart';
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
var _latest = [];
Future<String> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
var httpClient = new HttpClient();
var result;
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
var json = await response.transform(utf8.decoder).join();
var data = jsonDecode(json);
result = data;
} else {}
} catch (exception) {}
setState(() {
_latest = result;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HomepageAppBar(currentPage),
drawer: HomepageDrawer(),
body: FutureBuilder<String>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${_latest[index]}'),
);
},
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
);
}
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () =>
_getLatest;
}),
);
}
}
The Correct Version of Your Code

How to get value from an object which in the state (flutter_bloc)

in builder method I reach the value of state like
return BlocBuilder<UsersOwnProfileBloc, UsersOwnProfileState>(
cubit: widget.bloc,
builder: (context, state) {
if (state is FetchedUserSettingState) {
bool account = state.userSettings.publicAccount
}
But I need to get the values from initState. I need to set the values of the widget. I tried something like this but I got error
#override
void initState() {
super.initState();
UsersOwnProfileState state = BlocProvider.of<UsersOwnProfileBloc>(context).state;
if (state is FetchedUserSettingState) {
publicAccount = state.userSettings.publicAccount;
}
}
Can anyone show me how to get state value in initState?
class UserSettingPage extends StatefulWidget {
final UsersOwnProfileBloc bloc;
const UserSettingPage({Key key, this.bloc}) : super(key: key);
#override
_UserSettingPageState createState() => _UserSettingPageState();
}
class _UserSettingPageState extends State<UserSettingPage> {
bool newListingAlert;
bool listingForSearchAlert;
bool searchForListingAlert;
bool followAlert;
bool publicAccount;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final state = BlocProvider.of<UsersOwnProfileBloc>(context).state;
if (state is FetchedUserSettingState) {
publicAccount = state.userSettings.publicAccount;
}
});
}
#override
Widget build(BuildContext context) {
return BlocBuilder<UsersOwnProfileBloc, UsersOwnProfileState>(
cubit: widget.bloc,
builder: (context, state) {
if (state is FetchedUserSettingState) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(25.h),
child: ListingEditAppBar(
onCancel: () {
widget.bloc.add(FetchUserEvent(userId: CurrentUser.currentUser.id));
Navigator.pop(context);
},
),
),
body: Column(
children: [
PageTitle(title: "Preferences"),
Expanded(
child: ListView(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Profilim herkese açık"),
Switch(
onChanged: (value) {
setState(() {
publicAccount = value;
});
},
value: publicAccount,
)
],
),
)
],
),
)
],
),
);
}
return MyProgressIndicator();
},
);
}
}
I have added the whole code. I am getting the following error.
Failed assertion: boolean expression must not be null
The relevant error-causing widget was
Switch
If you would like to access the state within initState you will need to use WidgetsBinding to access this. However, using this ensures that your widget is built and then triggers the method to get the value. It will be faster to just use the BlocBuilder, Watch, or Select to get the value you are looking for.
But to answer your question, you can do the following
WidgetsBinding.instance.addPostFrameCallback((_) {
final state = BlocProvider.of<UsersOwnProfileBloc>(context).state;
if (state is FetchedUserSettingState) {
publicAccount = state.userSettings.publicAccount;
}
});

Got NoSuchMethodError (NoSuchMethodError: The method 'call' was called on null. error after setState in flutter

I have Form screen that contains a form widget.
After changing state (now with bloc but I tested with setState, no different) I got following error:
The following NoSuchMethodError was thrown while handling a gesture:
The method 'call' was called on null.
Receiver: null
Tried calling: call()
This only happened when I change state (if I don't yield new state, or setState it works without error).
but after changing state and probably rebuilding widget I got error:
This is main screen:
class _AuthScreenState extends State<AuthScreen> {
final AuthRepository repository = AuthRepository();
PageController controller;
Bloc _bloc;
#override
void initState() {
controller = PageController(initialPage: widget.page);
super.initState();
}
void changePage(int page) {
controller.animateToPage(
page,
curve: Curves.ease,
duration: Duration(milliseconds: 300),
);
}
void onSubmit(AuthType authType, AuthReq req) {
if (authType == AuthType.LOGIN) {
_bloc.add(LoginEvent(req: req));
} else {
_bloc.add(RegisterEvent(req: req));
}
}
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (ctx) => AuthBloc(repository: repository),
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
_bloc = context.bloc<AuthBloc>();
return ScreenContainer(
loading: state is LoadingState,
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
children: [
Expanded(
child: PageView.builder(
controller: controller,
physics: NeverScrollableScrollPhysics(),
itemCount: 2,
itemBuilder: (context, position) {
return position == 0
? LoginPage(
onPageChange: () => changePage(1),
onSubmit: (req) => onSubmit(AuthType.LOGIN, req),
)
: RegisterPage(
onPageChange: () => changePage(0),
onSubmit: (req) => onSubmit(AuthType.REGISTER, req),
);
},
),
),
],
),
),
);
},
),
);
}
}
class LoginPage extends StatelessWidget {
final VoidCallback onPageChange;
final void Function(AuthReq req) onSubmit;
final FormController controller = FormController();
LoginPage({
#required this.onPageChange,
#required this.onSubmit,
});
void submit() {
var values = controller?.submit();
if (values.isNull) {
return;
}
onSubmit(AuthReq(password: values['password'], username: values['email']));
}
#override
Widget build(BuildContext context) {
var authType = AuthType.LOGIN;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: hP),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FormWrapper(
inputs: loginFields,
controller: controller,
),
submitButton(context, authType, submit),
],
),
),
],
),
),
),
],
);
}
}
class FormController {
Map Function() submit;
}
class FormWrapper extends StatefulWidget {
final List<InputProps> inputs;
final FormController controller;
FormWrapper({
#required this.inputs,
this.controller,
});
#override
_FormWrapperState createState() => _FormWrapperState(controller);
}
class _FormWrapperState extends State<FormWrapper> {
final _formKey = GlobalKey<FormState>();
_FormWrapperState(FormController _controller) {
_controller.submit = submit;
}
bool _autoValidation = false;
Map values = {};
void setValue(String key, dynamic value) {
values[key] = value;
}
Map submit() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
return values;
} else {
setState(() {
_autoValidation = true;
});
return null;
}
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Form(
autovalidate: _autoValidation,
key: _formKey,
child: Column(
children: widget.inputs
.map(
(e) => Container(
margin: EdgeInsets.only(bottom: e.isLast ? 0 : 24.0 - 7.0),
child: RoundedInput(
inputProps: e,
onChange: (value) => setValue(e.label, value),
),
),
)
.toList(),
),
),
);
}
}
I found solution, I post answer instead of deleting question for may help others in future :)
I override didUpdateWidget to reinitialize variable in _FormWrapperState:
#override
void didUpdateWidget(oldWidget) {
widget.controller.submit = submit;
super.didUpdateWidget(oldWidget);
}