Flutter - How to change and save an integer retrieved from SharedPreferences? - dart

I retrieve an integer from SharedPreferences and I need to sum this value with another integer, but when I run the method I get an infinite loop with the sum.
I am calling the updateScore method to get a value that was saved before in SharedPreferences then I change the value with updateScore method, I save the value in SharedPreferences with saveScore method and I show the value in a Text().
int currentScore;
final int earnedScore;
#override
Widget build(BuildContext context) {
updateScore();
setScore();
return....
}
Future<int> getScore() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getInt(widget.keyNameScore);
}
Future<void> saveScore(int score) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt(widget.keyNameScore, score);
}
void updateScore() async{
getScore().then((value){
currentScore = value;
currentScore = currentScore + earnedScore;
saveScore(currentScore);
});
}
void setScore(){
getScore().then((value){
setState(() {
currentScore = value;
});
});
}

I had to move my methods from the build to initState.

Related

how to make singleton class with some initialization code?

I have tried the answers in here How do you build a Singleton in Dart?
but I can't achieve what I want. so basically I want to make a Shared Preference Service as a singleton class. currently my code is like this. this is just a regular class, not a singleton.
class SharedPreferenceService {
late SharedPreferences _prefs;
SharedPreferenceService() {
SharedPreferences.getInstance().then((value) => _prefs = value);
}
Future<void> setIntroPagesHaveBeenViewed() async {
await _prefs.setBool(SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED, true);
}
Future<bool> checkIfIntroPagesHaveBeenViewed() async {
return _prefs.getBool(SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED) ?? false;
}
}
I need a singleton class, but when the instance is initialize for the first time, I also need to initialize _pref , so then I can access that _pref on the methods
Your problem is that initialization is asynchronous.
That means that the first time the singleton instance is accessed, that access needs to be asynchronous too (and so does any further access which happens before the initialization completes). However, the usage pattern of a singleton like this is such that you don't know which access is the first. So you have to make every access asynchronous.
Example:
class SharedPreferenceService {
static final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<void> setIntroPagesHaveBeenViewed() async {
await (await _prefs).setBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED, true);
}
Future<bool> checkIfIntroPagesHaveBeenViewed() async {
return (await _prefs).getBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED) ?? false;
}
}
If all the methods are asynchronous anyway, that extra delay is not going to be a problem.
If you really, really only want to do that extra await if absolutely necessary,
you can cache the value, like you try to here:
class SharedPreferenceService {
static final Future<SharedPreferences> _prefsFuture = SharedPreferences.getInstance();
static SharedPreferences? _prefs;
Future<void> setIntroPagesHaveBeenViewed() async {
var prefs = _prefs ??= await _prefsFuture;
await _prefs.setBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED, true);
}
Future<bool> checkIfIntroPagesHaveBeenViewed() async {
var prefs = _prefs ??= await _prefsFuture;
return _prefs.getBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED) ?? false;
}
}

How to add two number fetched from SharedPreferences in Flutter

I was trying to add two numbers say point1 and point2. These points are stored in SharedPreferences .
I have fetched the points using a function Future<int> fetchPoints which is in below.
Then I called this from another function
fetchPoints:
Future<int> fetchFromSps(String field) async {
SharedPreferences sp = await SharedPreferences.getInstance();
return sp.getInt(field);
}
GetPoints:
Future<void> setPoints() async{
int _newPoints=await ((await fetchFromSps('point1'))+(await fetchFromSps('point2')));
setState(() {
_totalPoints=_newPoints.toString();
});
}
setInSharedPreference:
void setInSharedPreference() async{
SharedPreferences prefs=await SharedPreferences.getInstance();
prefs.setInt('point1', 0);
prefs.setInt('point2',0);
}
The function setInSharedPreference is in another dart file,which contains main function
I need to add two points which is named 'point1 and 'point2' from shared preference
just call them from any method like this sample:
fetchTwoPoints() async {
final point1 = await fetchFromSps("point1");
final point2 = await fetchFromSps("point2");
setState(() {
_totalPoints= (point1 + point2).toString();
});
}
Update your setInSharedPreference method because you are using async you need to wait to store the data.
Future<void> setInSharedPreference() async{
SharedPreferences prefs=await SharedPreferences.getInstance();
await prefs.setInt('point1', 0);
await prefs.setInt('point2',0);
}

Get stored shared preference list and display in list view

Hi I'm having issue where getting the stored value from Shared Preference and displaying it. It giving Future doesn't contain length instance error. My code below.
Save Shared Preference Value Code
Future<String> saveSearchQuery(String squery) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if(prefs.getStringList("searchhistory") == null){
final List<String> recentSearch = [];
recentSearch.insert(0, squery);
prefs.setStringList("searchhistory", recentSearch);
}else{
final recentSearch = prefs.getStringList("searchhistory");
if(recentSearch.contains(squery)){
recentSearch.forEach((e) => print(e));
}else{
recentSearch.insert(0, squery);
}
prefs.setStringList("searchhistory", recentSearch);
}
return prefs.commit().toString();
}
Get Shared Preference Value Code
Future<dynamic> getSearchHistory() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List searchHistory = prefs.getStringList("searchhistory");
return searchHistory.toList();
}
Please help. Thank you.

Flutter Await Callback Not Give Any Response

I am still a beginner on dart flutter, now I am trying to retrieve data from the REST API and socket.IO. at this time I have a confusing problem, I have tried searching on the internet for 3 days, but there is no solution. I have async and await scripts, but the function I added await doesn't give any response and still pause.
it is assumed that I have two different files, the first is the main file and the second is the helper file.
main.dart
Future<List<ChatTile>> fetchChat(socketutil,id) async {
socketutil.join(id); //STACK IN HERE
SharedPreferences prefs = await SharedPreferences.getInstance();
String messagePrefs = prefs.getString('messagePrefs');
print("DUA");
return await compute(parseListChat, messagePrefs);
}
helper.dart
Future<void> join(String id_room) async {
String jsonData ='{"room_id" : "$id_room","user_id" : "5a91687811138e74009839c9","user_name" : "Denis Muhammad Ramdan","user_photo" : "photo.jpg","user_status" : "1"}';
socketIO.sendMessage("join", jsonData, null);
//subscribe event
return await socketIO.subscribe("updateMessageList", (result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('messagePrefs', result);
print('SATU');
return await result;
});
}
my question is there something wrong with my code, and how is the best way?
many thanks,
I suggest you to add await_only_futures to your analyzer config
analysis_options.yaml
lint:
rules:
- await_only_futures
You also don't need to do return await something since your function already return a future, this is redondant.
And from what I see of the socketio subscribe method, it does not return the result like you expect but use a callback and does not return it (https://pub.dartlang.org/documentation/flutter_socket_io/latest/flutter_socket_io/SocketIO/subscribe.html)
to handle this you should use a Completer
final completer = Completer<String>()
socketIO.subscribe("updateMessageList", (result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('messagePrefs', result);
socketIO.unSubscribe("updateMessageList");
completer.complete(result);
});
return completer.future;
you probably want to handle error when there is using completer.completeError(error)
Update
You can alos convert the subscription to a Dart Stream to handle more case.
StreamController<String> controller;
Stream<String> get onUpdateMessageList {
if (controller != null) return controller.stream;
constroller = StreamController<String>.broadcast(
onCancel: () => socketIO.unSubscribe("updateMessageList"),
);
socketIO.subscribe("updateMessageList", constroller.add);
return controller.stream;
}
Future<StreamSubscription> join(String id_room) async {
...
return onUpdateMessageList.listen((result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('messagePrefs', result);
});
}

Is there a way to load async data on InitState method?

I'm a looking for a way to load async data on InitState method, I need some data before build method runs. I'm using a GoogleAuth code, and I need to execute build method 'till a Stream runs.
My initState method is:
#override
void initState () {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
});
_googleSignIn.signInSilently();
}
I will appreciate any feedback.
You can create an async method and call it inside your initState
#override
void initState () {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
_asyncMethod();
});
}
_asyncMethod() async {
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
});
_googleSignIn.signInSilently();
}
As of now using .then notation seems to work:
// ...
#override
initState() {
super.initState();
myAsyncFunction
// as suggested in the comment
// .whenComplete() {
// or
.then((result) {
print("result: $result");
setState(() {});
});
}
//...
Method 1 : You can use StreamBuilder to do this. This will run the builder method whenever the data in stream changes.
Below is a code snippet from one of my sample projects:
StreamBuilder<List<Content>> _getContentsList(BuildContext context) {
final BlocProvider blocProvider = BlocProvider.of(context);
int page = 1;
return StreamBuilder<List<Content>>(
stream: blocProvider.contentBloc.contents,
initialData: [],
builder: (context, snapshot) {
if (snapshot.data.isNotEmpty) {
return ListView.builder(itemBuilder: (context, index) {
if (index < snapshot.data.length) {
return ContentBox(content: snapshot.data.elementAt(index));
} else if (index / 5 == page) {
page++;
blocProvider.contentBloc.index.add(index);
}
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
});
}
In the above code StreamBuilder listens for any change in contents, initially its an empty array and shows the CircularProgressIndicator. Once I make API call the data fetched is added to contents array, which will run the builder method.
When the user scrolls down, more content is fetched and added to contents array which will again run builder method.
In your case only initial loading will be required. But this provides you an option to display something else on the screen till the data is fetched.
Hope this is helpful.
EDIT:
In your case I am guessing it will look something like shown below:
StreamBuilder<List<Content>>(
stream: account, // stream data to listen for change
builder: (context, snapshot) {
if(account != null) {
return _googleSignIn.signInSilently();
} else {
// show loader or animation
}
});
Method 2: Another method would be to create an async method and call it from you initState() method like shown below:
#override
void initState() {
super.initState();
asyncMethod();
}
void asyncMethod() async {
await asyncCall1();
await asyncCall2();
// ....
}
Create anonymous function inside initState like this:
#override
void initState() {
super.initState();
// Create anonymous function:
() async {
await _performYourTask();
setState(() {
// Update your UI with the desired changes.
});
} ();
}
#override
void initState() {
super.initState();
asyncInitState(); // async is not allowed on initState() directly
}
void asyncInitState() async {
await yourAsyncCalls();
}
Previous Answer!!
You can set a Boolean value like loaded and set it to true in your listen function and make your build function return your data when loaded is set to true otherwise just throw a CircularProgressIndicator
Edited --
I would not suggest calling setState in a method you call in initState. If the widget is not mounted while the setState is called (as the async operation completes) an error will be reported. I suggest you use a package after_layout
Take a look at this answer for better understanding setState in initState : https://stackoverflow.com/a/53373017/9206337
This post will give you an idea to know when the app finishes the build method. So that you can wait for your async method to setState after widget is mounted : https://stackoverflow.com/a/51273797/9206337
You can create an async method and call it inside your initState
#override
void initState() {
super.initState();
asyncMethod(); ///initiate your method here
}
Future<void> asyncMethod async{
await ///write your method body here
}
Per documentation at https://pub.dev/packages/provider
initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>(context).fetchSomething(someValue);
);
}
Sample code:
#override
void initState() {
super.initState();
asyncOperation().then((val) {
setState(() {});
print("success");
}).catchError((error, stackTrace) {
print("outer: $error");
});
//or
asyncOperation().whenComplete(() {
setState(() {});
print("success");
}).catchError((error, stackTrace) {
print("outer: $error");
});
}
Future<void> asyncOperation() async {
await ... ;
}
As loading or waiting for initial state is a (generally) aone off event FutureBuilder would seem to be a good option as it blocks once on an async method; where the async method could be the loading of json config, login etc. There is an post on it [here] in stack.(Flutter StreamBuilder vs FutureBuilder)
How about this?
#override
void initState() {
//you are not allowed to add async modifier to initState
Future.delayed(Duration.zero,() async {
//your async 'await' codes goes here
});
super.initState();
}
initState() and build cannot be async; but in these, you can call a function that is async without waiting for that function.
#override
void initState() {
super.initState();
_userStorage.getCurrentUser().then((user) {
setState(() {
if (user.isAuthenticated) {
Timer.run(() {
redirectTo();
});
}
});
});
}
void redirectTo() {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) => new ShopOrders()));
}
I would strongly suggest using a FutureBuilder. It takes care of executing the async function and building the widget according to the result!
Here's a link to a short intro video and the documentation.
Code Example:
Future<void> initControllers() async {
for (var filePath in widget.videoFilePaths) {
var val = VideoPlayerController.file(File(filePath));
await val.initialize();
controllers.add(val);
}
}
#override
Widget build(BuildContext context) {
FutureBuilder(
future: initControllers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return YourWidget();
} else {
return const ProgressIndicator();
}
},
));}
Tried all suggestions, none would keep my build from starting after the async method that I need in initState() finish, except one: the trick of having a a bool variable in the State class (let's call it _isDataLoaded) that is initialized to false upon definition, set to true inside a setState() that is invoked when the async function finishes inside initState(). In the build, condition a CircleProcessIndicator() or your Widget depending on the value of this variable.
I know it's dirty because it could break the build, but honestly nothing else that would make more sense -such as running super.initState() upon completion of the async function- has worked for me.
I came here because I needed to fetch some files from FTP on program start. My project is a flutter desktop application. The main thread download the last file added to the FTP server, decrypts it and displays the encrypted content, this method is called from initState(). I wanted to have all the other files downloaded in background after the GUI shows up.
None of the above mentioned methods worked. Constructing an Isolate is relatively complex.
The easy way was to use the "compute" method:
move the method downloading all files from the FTP out of the class.
make it an int function with an int parameter (I do not use the int parameter or the result)
call it from the initState() method
In that way, the GUI shows and the program downloads the files in background.
void initState() {
super.initState();
_retrieveFileList(); // this gets the first file and displays it
compute(_backgroundDownloader, 0); // this gets all the other files so that they are available in the local directory
}
int _backgroundDownloader(int value) {
var i = 0;
new Directory('data').createSync();
FTPClient ftpClient = FTPClient('www.guckguck.de',
user: 'maxmusterman', pass: 'maxmusterpasswort');
try {
ftpClient.connect();
var directoryContent = ftpClient.listDirectoryContent();
// .. here, fileNames list is reconstructed from the directoryContent
for (i = 0; i < fileNames.length; i++) {
var dirName = "";
if (Platform.isLinux)
dirName = 'data/';
else
dirName = r'data\';
var filePath = dirName + fileNames[i];
var myDataFile = new File(filePath);
if (!myDataFile.existsSync())
ftpClient.downloadFile(fileNames[i], File(filePath));
}
} catch (err) {
throw (err);
} finally {
ftpClient.disconnect();
}
return i;
I have used timer in initState
Timer timer;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 1), (Timer timer) async {
await this.getUserVerificationInfo();
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
getUserVerificationInfo() async {
await someAsyncFunc();
timer.cancle();
}

Resources