Search code examples
androidflutterprovider

How to change the state of a Switch in a Stateful Widget, retrieving data from a Provider?


I have a Switch on a screen and I need it to use the value that is in a Provider. I've tried to infer this value using the provider's value, but the Switch is immobile, it doesn't change visually(but the value is changed in the DB), it only works as it should when I remove the provider's inferences.

My Provider: (It is being called when I start the application)

class DailyDatabase with ChangeNotifier {
  
  bool notificationActive = false;

void loadDailyData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    notificationActive = prefs.getBool('notificationActive') ?? false;}

Variable:

@override
  Widget build(BuildContext context) {
    final provider = Provider.of<DailyDatabase>(context);
    _notificationActive = provider.notificationActive;

Switch:

Switch(
          value: _notificationActive,
          onChanged: (value) {
            _notificationActive = value;
            provider.setNotification(value);
          },
        ),

Solution

  • Stateful Version - Provider only

    Here's a very basic example of Provider with a Switch and using StatefulWidget and its setState to refresh the widget (instead of using ChangeNotifierProvider and Consumer to "listen" and "localize" the widget rebuild to just the Switch and the Text label, which is perhaps a more typical use):

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    class SwitchProviderPage extends StatefulWidget {
      @override
      _SwitchProviderPageState createState() => _SwitchProviderPageState();
    }
    
    class Database {
      bool active = false;
    
      void setActive(bool value) {
        active = value;
      }
    }
    
    class _SwitchProviderPageState extends State<SwitchProviderPage> {
      @override
      Widget build(BuildContext context) {
        return Provider(
            create: (context) => Database(),
          child: Builder(
            builder: (context) {
              Database db = Provider.of<Database>(context, listen: false);
              return Scaffold(
                appBar: AppBar(
                  title: Text('Switch Field'),
                ),
                body: SafeArea(
                  child: Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text('Active? ${db.active}'),
                        Switch(
                          onChanged: (val) { // ← remember to use val (bool)
                            print('Switch value: $val');
                            setState(() {
                              db.setActive(val);
                              // this sets the Switch setting on/off
                            });
    
                          },
                          value: db.active,
                        )
                      ],
                    ),
                  ),
                ),
              );
            },
          ),
        );
      }
    }
    

    Note:

    The use of Builder in above is only to make Scaffold be a child of Provider. Otherwise, Scaffold would be a sibling, not a child, and Provider will not work. Since you wrap your entire app in your ChangeNotifierProvider, you don't need to do this. I needed to do this to get a self-contained example.

    Stateless Version - ChangeNotifierProvider + Consumer

    Here's a complete app example (copy paste into main.dart, replacing everything on page) using a StatelessWidget and the typical/common ChangeNotifierProvider & Consumer.

    This version uses a mocked long duration async call when flipping Switch.

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    void main() {
      runApp(ChangeNotifierProvider<DatabaseListenable>(
          create: (context) => DatabaseListenable(),
          child: MyApp())
      );
    }
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Provider Demo App',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: StatelessSwitchProviderPage(),
        );
      }
    }
    
    class DatabaseListenable with ChangeNotifier {
      bool active = false;
    
      Future<void> setActive(bool value) async {
        // Mock slow database call ↓
        await Future.delayed(Duration(milliseconds: 500), () {
          active = value;
          print('Async DB call DONE.');
        });
        notifyListeners(); // ← causes Consumer to rebuild
      }
    }
    
    class StatelessSwitchProviderPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Switch Provider Stateless'),
          ),
          body: SafeArea(
            child: Center(
              child: Consumer<DatabaseListenable>(
                builder: (context, db, child) =>  Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text('Active? ${db.active}'),
                    Switch(
                      onChanged: (val) {
                        print('Switch value: $val');
                        db.setActive(val);
                      },
                      value: db.active,
                    )
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }