I have a stateful widget called AuthenticatingScreen
where I'm trying to perform the following flow...
- Output message letting the user know we are logging them in
- Get user oAuth token (calls to service file)
- Update the message to let the user know we are loading their details
- Fetch the users details and redirect them away
The problem is that at step three, I'm rebuilding the state, which is in turn causing the build
method to be fired again and calling the service again, which triggers an exception.
import 'package:flutter/material.dart';
import 'package:testing/services/auth_service.dart';
class AuthenticatingScreen extends StatefulWidget {
final String token;
AuthenticatingScreen(this.token);
@override
State<AuthenticatingScreen> createState() => _AuthenticatingScreenState();
}
class _AuthenticatingScreenState extends State<AuthenticatingScreen> {
// step 1) our default message
String _message = 'Please wait while we log you in...';
Future<void> _fetchUserDetails() {
return Future.delayed(const Duration(seconds: 3), () {
// ToDo: fetch user details from the server
});
}
@override
Widget build(BuildContext context) {
// step 2) get our oAuth token
AuthService.handleCallback(widget.token).then((accessCode) async {
// step 3) update our message
setState(() => _message = 'We\'re just getting your details');
// step 4) retrieve our user details and redirect away
_fetchUserDetails().then((_) {
Navigator.of(context).pushNamedAndRemoveUntil(
'/home',
(Route<dynamic> route) => false,
);
});
});
/// output our authenticating screen.
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Padding(
padding: EdgeInsets.only(bottom: 20.0),
child: CircularProgressIndicator(),
),
Text(_message),
],
),
),
);
}
}
My question being: How can I work around this / extract this logic to only fire when the widget is created, while still having access to the build context for navigation?
I've tried making the widget itself stateless and extracting the message and spinner into a separate widget, but changing the input argument alone still doesn't force a rebuild.
CodePudding user response:
Ok, so I have figured out the solution. It seems making service calls within the build()
method is a bad idea.
Moving my service calls into a void function which can then be called within the initState()
method seems to be the way to go.
import 'package:flutter/material.dart';
import 'package:testing/screens/home.dart';
import 'package:testing/services/auth_service.dart';
class AuthenticatingScreen extends StatefulWidget {
final String token;
AuthenticatingScreen(this.token);
@override
State<AuthenticatingScreen> createState() => _AuthenticatingScreenState();
}
class _AuthenticatingScreenState extends State<AuthenticatingScreen> {
/// the default message to display to the user.
String _message = 'Please wait while we log you in...';
void _authenticateUser(String token) {
AuthService.handleCallback(widget.token).then((accessCode) async {
// we've got the users token, now we need to fetch the user details
setState(() => _message = 'We\'re just getting your details');
// after fetching the user details, push them to the home screen
_fetchUserDetails().then((_) {
Navigator.of(context).pushNamedAndRemoveUntil(
HomeScreen.name,
(Route<dynamic> route) => false,
);
});
});
}
Future<void> _fetchUserDetails() {
return Future.delayed(const Duration(seconds: 3), () {
// ToDo: fetch user details from the server
});
}
@override
void initState() {
super.initState();
_authenticateUser(widget.token);
}
@override
Widget build(BuildContext context) {
/// output our authenticating screen.
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Padding(
padding: EdgeInsets.only(bottom: 20.0),
child: CircularProgressIndicator(),
),
Text(_message),
],
),
),
);
}
}
And this way when the build()
method is called again for the rebuild, very little details have to be redrawn.
CodePudding user response:
you can do it this way, i usually use getx & controller to achieve this.
- separate the UI class & service class preferably in a controller
- make the UI class statefull
- call the API in onInit() method,as it called only once it will trigger the service class
- in API method when you get the result 200, initiate the UI transition