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?
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>();
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.
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();
}
}
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-
Related
my project about Build music App using flutter-provider and packages name (on_audio_query) so when I get all songs from storage and want to modified them, like remove or add item the widget didn't rebuild automatically the update happened in console only unless press hot-reload to all project and the list has update and can review my code below.
main.dart
import 'package:flutter/material.dart';
import 'package:music_app_v4/test_folder/home_screen.dart';
import 'package:music_app_v4/test_folder/home_model.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider<HomeModel>(create: (context) => HomeModel()),
],
child: const MyApp(),));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter List Songs'),
);
}
}
HomeScreen.dart
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:music_app_v4/test_folder/home_model.dart';
import 'package:on_audio_query/on_audio_query.dart';
import 'package:provider/provider.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final OnAudioQuery _onAudioQuery = OnAudioQuery();
#override
void initState() {
requestStoragePermission();
super.initState();
}
void requestStoragePermission() async {
try {
if (!kIsWeb) {
bool status = await _onAudioQuery.permissionsStatus();
if (!status) {
await _onAudioQuery.permissionsRequest().then((value) => HomeModel());
}
setState(() {});
}
} catch (e) {
const SocketException.closed();
rethrow;
}
}
#override
Widget build(BuildContext context) {
//var notify = Provider.of<HomeModel>(context,listen: false);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Consumer<HomeModel>(builder: (context,notify,child){
return FutureBuilder<List<SongModel>>(
future: _onAudioQuery.querySongs(ignoreCase: false),
builder: (context,AsyncSnapshot item) {
if (item.data == null) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (item.data!.isEmpty) {
return const Center(
child: Text('No found Data'),
);
}
List<SongModel> songs = item.data!;
return ListView.builder(
itemCount: songs.length,
itemBuilder: (context, index) {
return Row(
children: [
Expanded(
child: ListTile(
title: Text(songs[index].title),
),
),
GestureDetector(
onTap: () {
// setState(() {
// _audioEdit.deleteAudio(songs[index].data);
// });
notify.removeAudio(songs[index].data);
print('button pressed');
//_audioEdit.deleteAudio(songs[index].data);
},
child: Icon(Icons.remove_circle))
],
);
},
);
},
);
},),
);
}
}
HomeModel.dart
import 'package:flutter/material.dart';
import 'package:on_audio_edit/on_audio_edit.dart';
import 'package:on_audio_query/on_audio_query.dart';
class HomeModel extends ChangeNotifier{
final OnAudioEdit _audioEdit = OnAudioEdit();
final OnAudioQuery onAudioQuery = OnAudioQuery();
List<SongModel>? songs;
void removeAudio(dynamic data){
_audioEdit.deleteAudio(data);
notifyListeners();
}
}
I am making an app in a flutter in which I can select the contacts from phone book and saving them in shared preferences. No problem in data saving and retrieving but i m struggling with showing the updated list on my UI. It is showing the contacts list but every time I click on Load button it duplicates the list and showing 2 lists , 1 previous and other updated .
how can i show just updated list on UI ?
here is my code:
import 'package:contacts_test/select_contacts.dart';
import 'package:contacts_test/shared_pref.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'contact_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
SharedPref sharedPref = SharedPref();
ContactModel modelLoad = ContactModel(displayName: 'saniya' , phoneNumber: '324235 ');
List _list = [];
#override
initState() {
super.initState();
// Add listeners to this clas
// loadSharedPrefs();
}
loadSharedPrefs() async {
try {
print('in load shared pref-- getting keys ');
final prefs = await SharedPreferences.getInstance();
final keys = prefs.getKeys();
print('now load shared pref ');
for (String key in keys) {
ContactModel user = ContactModel.fromJson(await sharedPref.read(key));
_list.add(user);
}
print('done load shared pref ');
}
catch (Excepetion) {
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("contacts "),
),
body: Builder(
builder: (context) {
return Column(children: [
RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Plugin1()));
},
child: const Text('fluttercontactpicker - plugin1'),
),
RaisedButton(
onPressed: () async {
await loadSharedPrefs();
},
child: Text('Load', style: TextStyle(fontSize: 20)),
),
Expanded(
child: _list.isNotEmpty ?
ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, position) {
return ListTile(
leading: Icon(Icons.contacts),
title: Text(
_list[position].displayName.toString()
),
trailing: Icon(Icons.delete));
},
) : Center(child: Text('No list items to show')),
),
]);
}
),
);
}
}
Your loadSharedPrefs(); function adds each contact to the list you show. Every time you press the button, the same elements are added again to the list. There are multiple ways to avoid that. You can: empty the list before filling it, you can write a for loop to loop over the length of the incoming contacts and for each to add it to the list by always starting from index 0. In case you use some kind of replacement or removing method, make sure you call setState(()=> { });
Base on the answer, here is a possible solution:
import 'package:contacts_test/select_contacts.dart';
import 'package:contacts_test/shared_pref.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'contact_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
SharedPref sharedPref = SharedPref();
ContactModel modelLoad = ContactModel(displayName: 'saniya' , phoneNumber: '324235 ');
List _list = [];
#override
initState() {
super.initState();
// Add listeners to this clas
// loadSharedPrefs();
}
loadSharedPrefs() async {
try {
print('in load shared pref-- getting keys ');
final prefs = await SharedPreferences.getInstance();
final keys = prefs.getKeys();
print('now load shared pref ');
var newList = [];
for (String key in keys) {
ContactModel user = ContactModel.fromJson(await sharedPref.read(key));
newList.add(user);
}
setState(()=> { _list = newList; });
print('done load shared pref ');
}
catch (Excepetion) {
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("contacts "),
),
body: Builder(
builder: (context) {
return Column(children: [
RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Plugin1()));
},
child: const Text('fluttercontactpicker - plugin1'),
),
RaisedButton(
onPressed: () async {
await loadSharedPrefs();
},
child: Text('Load', style: TextStyle(fontSize: 20)),
),
Expanded(
child: _list.isNotEmpty ?
ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, position) {
return ListTile(
leading: Icon(Icons.contacts),
title: Text(
_list[position].displayName.toString()
),
trailing: Icon(Icons.delete));
},
) : Center(child: Text('No list items to show')),
),
]);
}
),
);
}
}
I need to show a progress bar like below :
I have implemented it this way but the value inside the progress bar is not updated :
use upload() function to simulate file upload by submitting a test POST request
use StatefulBuilder() for convert my dialog from stateless to statefull
my code :
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(const MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String status = "loading ...";
double uploadedPercent = 0.0;
Future<void> upload() async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/albums'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': "test",
}),
);
if (response.statusCode == 201) {
// If the server did return a 201 CREATED response,
// then parse the JSON.
setState(() {
status = "Uploaded";
uploadedPercent = 1.0;
});
debugPrint(status);
} else {
throw Exception('Failed to create album.');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Upload File'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: ((BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
title: Text(status),
content: LinearProgressIndicator(
value: uploadedPercent,
backgroundColor: Colors.grey,
color: Colors.green,
minHeight: 10,
),
);
});
}),
);
upload();
},
child: const Text('Upload'),
),
],
),
),
// buildFutureBuilder(),
);
}
}
my
status variable
and
uplaodPercent variable
not be update in alert dialog and the my LinearProgressBar() stay in this state :
I conducted an experiment, its essence was to put the setState (() {}); method inside the StatefulBuilder and periodically call it to redraw the AlertBox widget, everything worked out for me, here is an example:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("App bar"),
),
body: HelpSO(count: 0.1),
));
}
}
class HelpSO extends StatelessWidget {
double count;
HelpSO({Key? key, required this.count}) : super(key: key);
#override
Widget build(BuildContext context) {
return StatefulBuilder(builder:
(BuildContext context, void Function(void Function()) setState) {
Timer.periodic(const Duration(seconds: 2), (Timer t) {
setState(() {
count += 0.1;
print(count);
});
});
return AlertDialog(
key: ValueKey(count),
title: const Text("Loading..."),
content: LinearProgressIndicator(
value: count,
backgroundColor: Colors.grey,
color: Colors.green,
minHeight: 10,
),
);
});
}
}
So your problem is that everything works for you, but the widget is not redrawn, so try to put the setState(() {}); in StatefulBuilder and call it at the moment when your widget needs to be updated.
This is the code of the count up app with change notifier and shared preferences.
I want it to save the counter in the model and show the last counter when it is opened, but it does not work.
When it is opened, it shows 0. Once I tap the add button, it shows the last counter plus 1.
Please tell me why it does not work and how can I fix it.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(
ChangeNotifierProvider<MyHomePageModel>(
create: (_) => MyHomePageModel(),
child: MyApp(),
)
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<MyHomePageModel>(builder: (context, model, child) {
int _counter = model.getCounter();
return Scaffold(
appBar: AppBar(
title: Text('FlutteR Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: Button());
});
}
}
class Button extends StatelessWidget {
#override
Widget build(BuildContext context) {
final model = Provider.of<MyHomePageModel>(context, listen: false);
return FloatingActionButton(
onPressed: () => model.addCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
}
class MyHomePageModel extends ChangeNotifier {
int _counter = 0;
void _setPrefItems() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt('counter', _counter);
}
void _getPrefItems() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('counter') ?? 0;
}
void addCounter() {
_counter++;
_setPrefItems();
notifyListeners();
}
int getCounter() {
_getPrefItems();
return _counter;
}
}
2 things I see missing here.
There is no getter for your counter in your HomePageModel. You're calling the getCounter() method in your widget, but the method returns void and not the counter value.
You have a ChangeNotifier, but notifyListeners is never called.
I would recommend setting a getter for your counter and calling notifyListeners in your method.
void _getPrefItems() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('counter') ?? 0;
notifyListeners();
}
int get counter => _counter
void _getPrefItems() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('counter') ?? 0;
}
int getCounter() {
_getPrefItems();
return _counter;
}
_getPrefItems is async function, and when you call getCounter the _counter hasn't been reassigned yet, so it return the default value of _counter which is 0.
So all you have to do is change the getCounter function to async function
Future<int> getCounter() async {
await _getPrefItems();
return _counter;
}
I recommend you to put SharePreferences out of your model, then using FutureProvider, look at the full code, I didn't test it, but you get the idea.
import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return FutureProvider(
create: (_) => SharedPreferences.getInstance(),
lazy: false,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider(
create: (_) => MyHomePageModel(prefs: Provider.of(context)),
child: Scaffold(
appBar: AppBar(
title: Text('FlutteR Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<MyHomePageModel>(
builder: (context, model, child) => Text(
'${model.getCounter()}',
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton: Button(),
),
);
}
}
class Button extends StatelessWidget {
#override
Widget build(BuildContext context) {
final model = Provider.of<MyHomePageModel>(context, listen: false);
return FloatingActionButton(
onPressed: () => model.addCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
}
class MyHomePageModel extends ChangeNotifier {
final SharedPreferences prefs;
int _counter = 0;
MyHomePageModel({#required this.prefs});
void _setPrefItems() {
prefs.setInt('counter', _counter);
}
void _getPrefItems() {
_counter = prefs.getInt('counter') ?? 0;
}
void addCounter() {
_counter++;
_setPrefItems();
notifyListeners();
}
int getCounter() {
_getPrefItems();
return _counter;
}
}
I want to fetch data from an API and set those data to the central state(provider) after creating a screen.( similar scenario of react useEfect function)
class MyApp2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Counter>(
child: MyHomePage(title: 'Flutter Demo Home Page'),
create: (BuildContext context) => Counter());
}
}
class _MyHomePageState extends State<MyHomePage> {
void _incrementCounter(dynamic count) {
count.incrementCounter();
}
int fetchData() {
//api request code
return data; // return fetched data
}
#override
Widget build(BuildContext context) {
final count = Provider.of<Counter>(context);
count.setCounter(fetchData());
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${count.counter}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _incrementCounter(count),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class Counter with ChangeNotifier{
int counter= 0;
void setCounter(int x){
counter =x;
notifyListeners();
}
void clearCounter(){
counter =0;
notifyListeners();
}
void incrementCounter(){
counter++;
notifyListeners();
}
}
It throws and exception and it doesn't work.
setState() or markNeedsBuild() called during build.
If I remove the notifyListeners() function, the app runs without any exceptions but the widget what I want to rebuild isn't rebuilt.
void setCounter(int x){
counter =x;
// notifyListeners();
}
What is the best way to do that?
I am also new to Provider. So this may not be a good solution.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp2(),
);
}
}
class MyApp2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Counter>(
child: MyHomePage(title: 'Flutter Demo Home Page'),
create: (BuildContext context) => Counter(),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({Key key, this.title}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Consumer<Counter>(
builder: (context, counter, _) {
if (counter.waiting)
return CircularProgressIndicator();
else
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<Counter>(
builder: (context, counter, _) {
return Text(
'${counter.counter}',
style: Theme.of(context).textTheme.display1,
);
},
),
],
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: Provider.of<Counter>(context).incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class Counter with ChangeNotifier {
int _counter;
bool _waiting;
Counter(){
_waiting = true;
_fetchCounterFromApi();
}
Future<void>_fetchCounterFromApi() async{
_counter = await Future<int>.delayed(Duration(seconds: 2),() => 4);//Do Api request;
_waiting = false;
notifyListeners();
}
int get counter => _counter;
bool get waiting => _waiting;
void incrementCounter() {
_counter++;
notifyListeners();
}
}