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

enter image description here

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();