kamranbekirovyz
kamranbekirovyz

Reputation: 1321

What is the proper way of using SharedPreferences with Provider in Flutter?

I am newbie to state management using provider in flutter.

I've created a model named as Counter:

import 'package:flutter/foundation.dart';

class Counter with ChangeNotifier {
  int value = 0;

  void increment() {
    value++;
    notifyListeners();
  }

  void decrement() {
    value--;
    notifyListeners();
  }
}

Now when value changes I can save it locally using SharedPreferences in order to start from that value next time.

But, I do not know what would be a proper way of loading data from local and set value in Counter class.

Should I load saved data in main.dart file when app is initalized and then setValue to that data?

Or are there any solutions, for example, loading data directly in my Counter class?

Upvotes: 5

Views: 7891

Answers (4)

Ronya
Ronya

Reputation: 19

Try to use the future builder and then set it to the provider and be able to use SharedPreferences everywhere in the app:

  @override
Widget build(BuildContext context) {
  return FutureBuilder<SharedPreferences>(
    future: SharedPreferences.getInstance(),
    builder: (context, snapshot) {
      if (snapshot.hasData && snapshot.data != null) {
        return MultiProvider(providers: [
          Provider<SharedPreferences>(
            create: (context) => snapshot.data!,
          ),
        ],
        );
      }
    },
  );
}

And you can use context.read() everywhere.

Upvotes: 1

Thapelo Radebe
Thapelo Radebe

Reputation: 627

create a SharedPreferencesProvider

import 'package:shared_preferences/shared_preferences.dart';

class SharedPreferencesProvider {
  final Future<SharedPreferences> sharedPreferences;

  SharedPreferencesProvider(this.sharedPreferences);

  Stream<SharedPreferences> get prefsState => sharedPreferences.asStream();
}

then create a Provider and with a StreamProvider as shown below

return MultiProvider(
          providers: [
            Provider<SharedPreferencesProvider>(create: (_) => SharedPreferencesProvider(SharedPreferences.getInstance())),
            StreamProvider(create: (context) => context.read<SharedPreferencesProvider>().prefsState, initialData: null)

then consume the state within a Widget build with a context.watch

  @override
  Widget build(BuildContext context) {
    sharedPrefs = context.watch<SharedPreferences>();

Upvotes: 6

Stephen C
Stephen C

Reputation: 2036

The question is leaning toward opinion. I'm also new to flutter -- the below may not be the best way, but it does work, so maybe it will help someone.


If it's the top level app, you can initialize the counter before actually using it, displaying a loading page during the load time (imperceptible in this case). You must include the first runApp however, otherwise shared_preferences will not be able to correctly access the file containing these preferences on the device.

A similar thing can be done with with FutureBuilder, but you must await a delay prior to attempting to read from shared_preferences.

(I don't think the loading page or delay are necessary if you aren't using the widget as your top level widget, which would probably be better anyway. In that case, probably FutureBuilder would be the correct solution. (?))

To note:

  • I added an async "constructor" to the Counter class that initializes from the shared_preferences.
  • I access the Counter via provider library in _MyHomePageState.build with context.watch<Counter>(), which causes this to rebuild on changes (without requiring calls to setState.
  • I've added async Counter._updatePreferences which is called in Counter.increment and Counter.decrement, which saves the current value of the Counter to the shared_preferences.

Imports and main for first method

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

Future<void> main() async {
  // run "loading" app while awaiting counter, then run app again
  runApp(
    const MaterialApp(
      home: Center(
        child: Text('Loading'),
      ),
    )
  );

  final Counter counter = await Counter.fromPreferences();

  runApp(
    ChangeNotifierProvider<Counter>.value(
      value: counter,
      child: const MyApp(),
    )
  );
}

Imports and main (with FutureBuilder)

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  // Get counter in future builder
  runApp(
    FutureBuilder<Counter>(
      future: Counter.fromPreferences(),
      builder: (BuildContext context, AsyncSnapshot<Counter> snapshot) {
        Widget returnWidget = const MaterialApp(
          home: Center(
            child: Text('Loading'),
          ),
        );

        if (snapshot.connectionState == ConnectionState.waiting) {
        } else if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasError) {
            print(snapshot.error);
          } else if (snapshot.hasData) {
            final Counter counter = snapshot.data!;
            returnWidget = ChangeNotifierProvider<Counter>.value(
              value: counter,
              child: const MyApp(),
            );
          } else {
            print('No data');
          }
        } else if (snapshot.connectionState == ConnectionState.none) {
          print('null future');
        } else {
          print(snapshot.connectionState);
        }
        return returnWidget;
      },
    ),
  );
}

MyApp and MyHomePage

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Counter App',
      home: MyHomePage(title: 'Counter App Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    final Counter counter = context.watch<Counter>();
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <FloatingActionButton>[
          FloatingActionButton(
            onPressed: counter.increment,
            child: const Icon(Icons.add),
          ),
          FloatingActionButton(
            onPressed: counter.decrement,
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

Counter Class (ChangeNotifier)

class Counter extends ChangeNotifier {
  int value = 0;

  static Future<Counter> fromPreferences() async {
    final Counter counter = Counter();
    // Must be included if using the FutureBuilder
    // await Future<void>.delayed(Duration.zero, () {});
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int value = prefs.getInt('counterValue') ?? 0;
    counter.value = value;
    return counter;
  }

  Future<void> _updatePreferences() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setInt('counterValue', value);
  }

  void increment() {
    value++;
    notifyListeners();
    _updatePreferences();
  }

  void decrement() {
    value--;
    notifyListeners();
    _updatePreferences();
  }
}


Complete Example

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

Future<void> main() async {
  // run "loading" app while awaiting counter, then run app again
  runApp(
    const MaterialApp(
      home: Center(
        child: Text('Loading'),
      ),
    )
  );

  final Counter counter = await Counter.fromPreferences();

  runApp(
    ChangeNotifierProvider<Counter>.value(
      value: counter,
      child: const MyApp(),
    )
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Counter App',
      home: MyHomePage(title: 'Counter App Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    final Counter counter = context.watch<Counter>();
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <FloatingActionButton>[
          FloatingActionButton(
            onPressed: counter.increment,
            child: const Icon(Icons.add),
          ),
          FloatingActionButton(
            onPressed: counter.decrement,
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

class Counter extends ChangeNotifier {
  int value = 0;

  static Future<Counter> fromPreferences() async {
    final Counter counter = Counter();
    // Must be included if using the FutureBuilder
    // await Future<void>.delayed(Duration.zero, () {});
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final int value = prefs.getInt('counterValue') ?? 0;
    counter.value = value;
    return counter;
  }

  Future<void> _updatePreferences() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setInt('counterValue', value);
  }

  void increment() {
    value++;
    notifyListeners();
    _updatePreferences();
  }

  void decrement() {
    value--;
    notifyListeners();
    _updatePreferences();
  }
}

Upvotes: 0

Ananta Prasad
Ananta Prasad

Reputation: 3859

Use the shared_preferences plugin

enter link description here

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SharedPreferences Demo',
      home: SharedPreferencesDemo(),
    );
  }
}

class SharedPreferencesDemo extends StatefulWidget {
  SharedPreferencesDemo({Key key}) : super(key: key);

  @override
  SharedPreferencesDemoState createState() => SharedPreferencesDemoState();
}

class SharedPreferencesDemoState extends State<SharedPreferencesDemo> {
  Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  Future<int> _counter;

  Future<void> _incrementCounter() async {
    final SharedPreferences prefs = await _prefs;
    final int counter = (prefs.getInt('counter') ?? 0) + 1;

    setState(() {
      _counter = prefs.setInt("counter", counter).then((bool success) {
        return counter;
      });
    });
  }

  @override
  void initState() {
    super.initState();
    _counter = _prefs.then((SharedPreferences prefs) {
      return (prefs.getInt('counter') ?? 0);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("SharedPreferences Demo"),
      ),
      body: Center(
          child: FutureBuilder<int>(
              future: _counter,
              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                switch (snapshot.connectionState) {
                  case ConnectionState.waiting:
                    return const CircularProgressIndicator();
                  default:
                    if (snapshot.hasError) {
                      return Text('Error: ${snapshot.error}');
                    } else {
                      return Text(
                        'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n'
                        'This should persist across restarts.',
                      );
                    }
                }
              })),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Reference site : https://pub.dev/packages/shared_preferences#-example-tab-

Upvotes: -2

Related Questions