I am trying to add a circular progress indicator on a login page while the user is logging in. I declared isLoading inside my model class, and set it in the login() function.
class LoginPageModel extends ChangeNotifier {
bool isLoading = false;
login(String datasource, String username, String password,
BuildContext context) async {
isLoading = true;
try {
final result = await Client.auth(
dataSource: datasource,
username: username,
password: password,
);
if (result) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => CustomersPage()));
}
} on ClientException catch (e) {
debugPrint(e.prettyMessage());
} catch (e) {
debugPrint(e.toString());
} finally {
isLoading = false;
notifyListeners();
}
}
}
On the UI side, I have a CircularProgressIndicator set to display depending on the value of isLoading.
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => LoginPageModel(),
child: Consumer<LoginPageModel>(
builder: (context, model, _) {
return Scaffold(
body: Center(
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(60.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
'Log in',
style: Styles.titleTextStyle(),
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
decoration: Styles.textFormFieldDecoration(
labelText: 'Data Source',
),
controller: _dataSourceController,
validator: (value) =>
value.isNotEmpty ? null : 'Required Field',
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
decoration: Styles.textFormFieldDecoration(
labelText: 'Username',
),
controller: _usernameController,
validator: (value) =>
value.isNotEmpty ? null : 'Required Field',
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
obscureText: true,
decoration: Styles.textFormFieldDecoration(
labelText: 'Password',
),
controller: _passwordController,
validator: (value) =>
value.isNotEmpty ? null : 'Required Field',
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
side: BorderSide(
style: BorderStyle.solid,
color: Colors.grey)),
child: Text(model.isLoading
? 'Logging In . . '
: 'Submit'),
onPressed: () async {
if (_formKey.currentState.validate()) {
model.login(
_dataSourceController.text,
_usernameController.text,
_passwordController.text,
context,
);
}
},
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: model.isLoading
? CircularProgressIndicator()
: Container(),
),
],
),
),
),
),
),
);
},
));
}
When I test it, the circular progress indicator does not appear. Do I need to set isLoading somewhere else? Is the state of isLoading being tracked? I would like to keep the UI and logic code separate and keep my widget stateless. Is this possible?
Solution 1: PRATIK PAWAR
Don't use isLoading in Model, In the UI file get the instance of the model class
final loginModel = LoginPageModel();
bool isLoading = false;
create a new function
void login(parameters) async {
setState(() {
isLoading = true;
});
await loginModel.login(parameters);
setState(() {
isLoading = false;
});
}
in the Widget part
...
return isLoading ? CircularProgressIndicator()
: Your Widget()
...
Solution 2: JonnyH
in LoginPageModel, follow
isLoading = true;
with
notifyListeners();
Solution 3: chunhunghan
You can copy paste run full code below
Step 1: You forgot to add notifyListeners
isLoading = true;
notifyListeners();
Step 2: You need SingleChildScrollView
to avoid keyboard issue
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Padding(
working demo
full code
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:provider/provider.dart';
class LoginPageModel extends ChangeNotifier {
bool isLoading = false;
login(String datasource, String username, String password,
BuildContext context) async {
isLoading = true;
notifyListeners();
try {
await Future.delayed(Duration(seconds: 3), () {});
/*
final result = await Client.auth(
dataSource: datasource,
username: username,
password: password,
);
if (result) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => CustomersPage()));
}*/
} on ClientException catch (e) {
debugPrint(e.toString());
} catch (e) {
debugPrint(e.toString());
} finally {
isLoading = false;
notifyListeners();
}
}
}
class Test extends StatelessWidget {
final _formKey = GlobalKey<FormState>();
final _dataSourceController = TextEditingController();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => LoginPageModel(),
child: Consumer<LoginPageModel>(
builder: (context, model, _) {
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(60.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
'Log in',
//style: Styles.titleTextStyle(),
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
decoration: InputDecoration(
labelText: 'Data Source',
),
controller: _dataSourceController,
validator: (value) =>
value.isNotEmpty ? null : 'Required Field',
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
decoration: InputDecoration(
labelText: 'Username',
),
controller: _usernameController,
validator: (value) =>
value.isNotEmpty ? null : 'Required Field',
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
),
controller: _passwordController,
validator: (value) =>
value.isNotEmpty ? null : 'Required Field',
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
side: BorderSide(
style: BorderStyle.solid,
color: Colors.grey)),
child: Text(model.isLoading
? 'Logging In . . '
: 'Submit'),
onPressed: () async {
if (_formKey.currentState.validate()) {
model.login(
_dataSourceController.text,
_usernameController.text,
_passwordController.text,
context,
);
}
},
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: model.isLoading
? CircularProgressIndicator()
: Container(),
),
],
),
),
),
),
),
),
);
},
));
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Test(),
);
}
}
Solution 4: MuhammadOmar
In LoginPageModel model make sure your isLoading should be true and also notify your listener
isLoading = true;
notifyListeners();