Issue
My process is as follows. The screen has just two elements – TextFormField and an ElevatedButton.
- Get email address from user
- User clicks button
- Button validates input, then
- Calls FutureBuilder, which
- Tries to fetch client record from REST API
- Redirects to appropriate route
This is my first Flutter/Dart program FYI, so I might be making a beginner mistake.
Question: The very first line of the FutureBuilder isn’t executed. No error, no messages, nothing. Why does this happen?
The user enters the email address, clicks the button, the fetchClientInfo
function is executed, which returns a Future<ClientInfo>
and that’s that.
Could you help please?
@override
Widget build(BuildContext context) {
final _formKey = GlobalKey<FormState>();
return Scaffold(
appBar: AppBar(
title: Text("Register Profile"),
),
body: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(20),
child: TextFormField(
controller: emailController,
validator: (email) {
if (email.isEmpty) {
return 'Please enter your email address.';
} else if (!EmailValidator.validate(email)) {
return 'Please enter a valid email address.';
}
return null;
},
decoration: InputDecoration(
border: new UnderlineInputBorder(borderSide: new BorderSide(color: Colors.red)),
labelText: 'Email',
hintText: 'Enter your email address',
contentPadding: EdgeInsets.all(20.0),
),
)),
ElevatedButton(
onPressed: () => {
if (_formKey.currentState.validate())
{
FutureBuilder<ClientInfo>(
future: fetchClientInfo(emailController.text),
builder: (BuildContext context, snapshot) {
print("here");
if (snapshot.data.outcome) {
return Text("main screen");
} else if (!snapshot.data.outcome) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RegisterNewUser(emailAddress: emailController.text)));
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// Show a spinner
return CircularProgressIndicator();
},
)
}
},
child: Text(
"Check Email",
))
])));
}
Future<ClientInfo> fetchClientInfo(String emailAddress) async {
var url = Uri.https(APIAccess.baseAPIURL, APIAccess.pathToClientAPI, {
'client_id': '$emailAddress',
'action': 'info',
'key': '${APIAccess.key}'
});
final response = await http.get(url);
if (response.statusCode == 200) {
return ClientInfo.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load album');
}
}
Solution
You’re missing a couple of things:
- return statement (in your lambda you’re creating a
FutureBuilder
but it’s not being used anywhere)
if(true) {
return SizedBox.shrink();
}
- correct lambda syntax (well, technically it’s correct but it’s not doing what you want): https://dart.dev/guides/language/language-tour#anonymous-functions
// that's how compiler sees it
Map<dynamic, dynamic> Function() foo = () => {
};
What you did reminds me of javascript, but in dart lambdas look a bit different
return Button(
onTap: () => doStuff(),
);
return Button(
onTap: () {
doStuff();
}
);
// and if you want to return a value from block lambda
return Builder(
builder: (context) {
return SizedBox.shrink();
}
);
- rendering widget on tap
When handling tap events, it’s best to redirect calls to a component that’s handling business logic, and only listen for current state in the widget.
What you want to read about is state management. The topic is highly opinionated, so you have to choose yourself the solution that’s right for you. https://flutter.dev/docs/development/data-and-backend/state-mgmt
I myself like using a slightly modified version of bloc. You can find the ‘original’ one here: https://pub.dev/packages/flutter_bloc
A new de-facto standard if it comes to state management is Riverpod
If you just want to make your code work, do something like this:
class Demo extends StatefulWidget {
@override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
Future<ClientInfo?> clientInfo = Future.value(null);
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
setState(() {
clientInfo = fetchClientInfo(emailController.text);
});
},
child: _buildButtonContent(),
),
FutureBuilder<ClientInfo>(
initialData: null,
future: clientInfo,
builder: (BuildContext context, snapshot) {
if (snapshot.data == null) {
return SizedBox.shrink();
} else {
return Text(snapshot.data.toString());
}
},
)
],
);
}
}
Answered By – eeqk
Answer Checked By – Robin (FlutterFixes Admin)