I'm totally new to Flutter. I tried to make a login form with an option to remember the credentials.
Since SharedPreferences
is async, it makes a this task bit tricky: I created an async method -started in the constructor- to asynchronously obtain the SharedPreferences
instance, read some values, then call setState()
writing the new state1.
But when setState()
is called, and the new state written, the UI does not update (i.e. the text fields remain blank).
The build()
method is something like:
@override
Widget build(BuildContext context) {
developer.log("build called: rememberCredentials=$rememberCredentials, username=$username, password=$password");
return (... TextFormField(
initialValue: username,
onChanged: (String value) {username = value;}
) ...);
}
I added a couple of developer.log()
calls to see when build()
is called, and if saved preferences are actually read (they do):
[log] build called: rememberCredentials=false, username=, password=
[log] read prefs: rememberCredentials=true, username=john, password=1234
[log] build called: rememberCredentials=true, username=john, password=1234
[log] build called: rememberCredentials=true, username=john, password=1234
This is the relevant code:
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
String username = "";
String password = "";
bool rememberCredentials = false;
_LoginFormState() {
readSavedCredentials();
}
void readSavedCredentials() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
rememberCredentials = prefs.getBool("rememberCredentials") ?? false;
username = prefs.getString("username") ?? "";
password = prefs.getString("password") ?? "";
developer.log("read prefs: rememberCredentials=$rememberCredentials, username=$username, password=$password");
});
}
void login() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool("rememberCredentials", rememberCredentials);
await prefs.setString("username", rememberCredentials ? username : "");
await prefs.setString("password", rememberCredentials ? password : "");
final resp = await http.get(
Uri.parse('https://127.0.0.1:56873/?action=login&user=$username&pass=${md5hex(password)}')
);
if(resp.statusCode == 200) {
Map o = jsonDecode(resp.body);
if(o["status"] == "AUTH_OK") {
developer.log(jsonEncode(o));
Navigator.push(
context,
MaterialPageRoute(builder: (context) => FilesView(o["base_url"], o["files"])),
);
} else {
// TODO: report error
}
} else {
// TODO: report error
}
}
@override
Widget build(BuildContext context) {
developer.log("build called: rememberCredentials=$rememberCredentials, username=$username, password=$password");
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: Text(title())),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.all(15),
child: TextFormField(
initialValue: username,
onChanged: (String value) {
username = value;
},
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Username',
)
)
),
Padding(
padding: EdgeInsets.all(15),
child: TextFormField(
initialValue: password,
onChanged: (String value) {
password = value;
},
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
)
)
),
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: rememberCredentials,
onChanged: (bool? value) {
setState(() {
rememberCredentials = value!;
});
}),
Text('Remember credentials'),
],
),
),
Container(
width: 220,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20),
),
child: TextButton(
onPressed: () {
login();
},
child: Padding(
padding: EdgeInsets.all(10),
child: Text(
'Login',
style: TextStyle(color: Colors.white),
),
),
),
),
]
)
);
}
}
Since I'm totally new to Flutter, I may have made several mistakes. Any advice on the design is very welcome :)
1: another option was to use FutureBuilder
, but it is messier, and it would delay UI creation... I prefer showing the UI soon, and changing fields values later.
CodePudding user response:
Instead of using using the String username to hold your value and onChanged to change it, you can use a text editing controller so
String username = '';
becomes
TextEditingController usernameController = TextEditingController();
Then in your TextFormFields
TextFormField(
initialValue: username,
onChanged: (String value) {
username = value;
},
...
)
becomes
TextFormField(
controller: usernameController,
...
)
Lastly, in your readSavedCredentials() method you can set the text (username) using the text editing controller
void readSavedCredentials() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
...
usernameController.text = prefs.getString("username") ?? "";
...
});
}