flutter shared preference null is returned - flutter

everyone
I saved List in shared preference.
Since List cannot be saved in shared preference, it is saved in json type.
I referenced the post on the stackover.
However, when reading the value stored in the preference, null was returned.
Please help.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:real_lotto/lotto_number.dart';
class DisplayNumber extends StatefulWidget {
#override
_DisplayNumberState createState() => _DisplayNumberState();
}
class _DisplayNumberState extends State<DisplayNumber> {
String _number = '';
//initState called when the widget is mounted
void initState() {
super.initState();
if (_number == '') {
loadLotto().then((String s) => setState((() {
_number = s;
print(s);
})));
}
}
#override
Widget build(BuildContext context) {
print(_number);
return Scaffold();
}
}
Future<void> _saveLotto(number) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('key', json.encode(number));
}
Future<String> loadLotto() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var number = json.decode(prefs.getString('key'));
return (number);
}
List<dynamic> myNumber() {
var number = [];
number.add((List.generate(45, (index) => ++index)..shuffle()).sublist(0, 6));
number.add((List.generate(45, (index) => ++index)..shuffle()).sublist(0, 6));
number.add((List.generate(45, (index) => ++index)..shuffle()).sublist(0, 6));
number.add((List.generate(45, (index) => ++index)..shuffle()).sublist(0, 6));
number.add((List.generate(45, (index) => ++index)..shuffle()).sublist(0, 6));
for (var i = 0; i < number.length; i++) {
number[i].sort();
}
_saveLotto(number);
return number;
}

Related

Why is ChangeNotifier updating endlessly

I have 2 data provider classes that extend ChangeNotifier. Within each, there's a function to fetch data and at the end of them, I use notifyListeners() to notify the screens/listeners that the data changed. However, it seems that the listeners start getting notified endlessly instead of once and that creates a loop of reloading, circle indicators that don't go away, and a frozen screen. I don't get it.
Data providers:
class UsersDataProvider extends ChangeNotifier {
UsersDataProvider() : super();
static Map<int, QueryDocumentSnapshot<Object?>> usersMap = {};
Future<void> fetchUsers() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
final docs = result.docs.asMap();
usersMap = docs;
print(usersMap.length);
notifyListeners();
}
}
class PostsDataProvider extends ChangeNotifier {
PostsDataProvider() : super();
static Map<int, QueryDocumentSnapshot<Object?>> postsMap = {};
Future<void> fetchPosts() async {
UsersDataProvider.usersMap.forEach((index, resultValue) async {
final postsRef = FirebaseFirestore.instance
.collection('users')
.doc(resultValue.id)
.collection('posts');
final QuerySnapshot postsResult = await postsRef.get();
final postDocs = postsResult.docs.asMap();
postsMap = postDocs;
print('Post map: ${postsMap.length}');
notifyListeners();
});
}
}
Add listeners and reload data:
Future<void> fetchUsersAndPosts(bool initial) async {
if (!initial) {
setState(() {
postsLoading = true;
});
usersDataProvider.fetchUsers();
postsDataProvider.fetchPosts();
}
if (initial) {
usersDataProvider.addListener(() {
print('changed');
setState(() {
fetchUsersAndPosts(false);
});
});
}
if (initial) {
postsDataProvider.addListener(() {
setState(() {
fetchUsersAndPosts(false);
});
});
}
UsersDataProvider.usersMap.forEach((index, value) async {
List<Post> posts = [];
PostsDataProvider.postsMap.forEach((index, value) {
final post = Post.fromJson(value.data() as Map<String, dynamic>);
posts.add(post);
setState(() {});
if (posts.length == PostsDataProvider.postsMap.length) {
setState(() {
postsList = posts;
postsList.sort((a, b) {
return b.date.compareTo(a.date);
});
postsLoading = false;
});
}
});
final profileInfo =
ProfileInfoObject.fromJson(value.data() as Map<String, dynamic>);
Profile profile = Profile(profileInfo, postsList.where((p) => p.uid == value.id).toList());
UserSearchResult user = (UserSearchResult(profile, value.id));
if (usersList.where((u) => u.uid == user.uid).toList().isEmpty) {
setState(() {
usersList.add(user);
});
}
});
setState(() {
postsList.sort((a, b) {
return b.date.compareTo(a.date);
});
});
}

Flutter : TypeError: Cannot read properties of null (reading 'setString')

I want to make progress tracker like if the user passed level 1 level 1 I will send to the Map level 1 is true (Finished),
I don't want to use database so I tried Shared Preferences Package then I faced the Error That in the title
... if you have a better way to do it please write it
class CheckLvl extends StatelessWidget {
static SharedPreferences sharedPreferences;
Map<String , String> Check = {
'1':'true',
'2':'false',
'3':'false',
'4':'false',
};
String encoded ;
String encodedMap;
Map<String , String> decoded;
CheckLvl(){
encoded = jsonEncode(Check);
sharedPreferences.setString('State', encoded);
}
static init () async
{
sharedPreferences = await SharedPreferences.getInstance();
}
Future<bool> isComplete (String index) async {
encodedMap = sharedPreferences.getString('State');
decoded = jsonDecode(encodedMap);
print(decoded);
if (decoded[index]=='true')
return true;
}
void Done(String index)
{
encodedMap = sharedPreferences.getString('State');
decoded = jsonDecode(encodedMap);
decoded[index]='true';
}
It is possible to get null data while reading , you can do
Future<bool> isComplete (String index) async {
final String? data = sharedPreferences.getString('State');
return data=='true' ;
}
Better using FutureBuilder for future method like
class CheckLvl extends StatefulWidget {
#override
State<CheckLvl> createState() => _CheckLvlState();
}
class _CheckLvlState extends State<CheckLvl> {
SharedPreferences? sharedPreferences;
Map<String, String> Check = {
'1': 'true',
'2': 'false',
'3': 'false',
'4': 'false',
};
Future<void> init() async {
sharedPreferences = await SharedPreferences.getInstance();
}
String? encoded;
String? encodedMap;
Map<String, String>? decoded;
Future<bool> isComplete(String index) async {
encodedMap = sharedPreferences!.getString('State');
decoded = jsonDecode(encodedMap!);
print(decoded);
if (decoded?[index] == 'true') return true;
return false;
}
void Done(String index) async {
encodedMap = sharedPreferences!.getString('State');
decoded = jsonDecode(encodedMap!);
decoded?[index] = 'true';
}
late final prefFuture = init();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: prefFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("got data");
}
return CircularProgressIndicator();
},
);
}
}
class CheckLvl extends StatelessWidget {
static SharedPreferences? sharedPreferences;
Map<String, String> Check = {
'1': 'true',
'2': 'false',
'3': 'false',
'4': 'false',
};
String? encoded;
String? encodedMap;
Map<String, String>? decoded;
static Future<SharedPreferences> init() async {
return await SharedPreferences.getInstance();
}
Future<bool> isComplete(String index) async {
sharedPreferences ??= await init();
encodedMap = sharedPreferences!.getString('State');
decoded = jsonDecode(encodedMap!);
print(decoded);
if (decoded?[index] == 'true') return true;
return false;
}
void Done(String index) async {
sharedPreferences ??= await init();
encodedMap = sharedPreferences!.getString('State');
decoded = jsonDecode(encodedMap!);
decoded?[index] = 'true';
}
#override
Widget build(BuildContext context) {
throw UnimplementedError();
}
}

How to Refresh ListView.Builder when data changes in background?

I have a ListView which gets data from API, the API call is first made from initState() and assigning to List<DataFromApi> dataFromApi = [];. After sometime am doing a background call again to the same API, Am getting the data and assigning to the same list dataFromApi in
setState(() {
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
});
When logging it, the data changes, but UI not changing. How can I update the ListView?
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<DataFromApi> dataFromApi = [];
#override
void initState() {
loadData();
super.initState();
fetchFirstData();
initTimedDataCall();
}
initTimedDataCall() {
Timer.periodic(Duration(minutes: 5), (timer) {
setState(() {
timedCall();
});
});
}
fetchFirstData() async {
final response = await http.get(
Utils.getDataUrl,
);
if (response.statusCode == 200) {
final resData = DataListFromJson(response.body);
if (resData.status == 1) {
print(response.body);
setState(() {
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
});
}
///
///
/// DOING SOME MORE THINGS IN FIRST CALL
///
///
}
}
timedCall() async {
final response = await http.get(
Utils.getDataUrl,
);
if (response.statusCode == 200) {
final resData = DataListFromJson(response.body);
if (resData.status == 1) {
print(response.body);
setState(() {
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
});
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: dataFromApi.length,
itemBuilder: (context, index) {
return Text('${dataFromApi[index].title}');
}
),
);
}
}
Try calling setState after computation is done
timedCall() async {
final response = await http.get(
Utils.getDataUrl,
);
if (response.statusCode == 200) {
final resData = DataListFromJson(response.body);
if (resData.status == 1) {
print(response.body);
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
setState(() {
});
}
}
}
use similar for other methods. This is working at my end

Correct way to call an api by provider in fflutter?

I have been trying to make a app in flutter where an api is called and data is updated in TextField
Used provider for state management, here is the code for it.
class ProfileProvider with ChangeNotifier {
var profileData;
String _url = "http://10.0.2.2:3000/api/v1/user/loggedin_user";
void getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var token = prefs.getString('token');
var data = await http.get(
_url,
headers: {
"accept": "application/json",
"content-type": "application/json",
'Token': token,
},
);
var infoOfPerson = json.decode(data.body);
profileData = new ProfileObject(
name: infoOfPerson['name'],
mobile: infoOfPerson['mobile'],
email: infoOfPerson['email'],
role: infoOfPerson['role'],
);
notifyListeners();
}
ProfileObject get profileInfo {
return profileData;
}
}
I am getting the data fine, now i have to show it in the UI, but sometime data is populated, sometime its not. Can someone please point me the right direction why this is happening.
Here is the code for UI.
class Profile extends StatefulWidget {
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
final emailController = TextEditingController(text: '');
final nameController = TextEditingController(text: '');
final mobileController = TextEditingController(text: '');
var _isInit = true;
#override
void didChangeDependencies() {
if (_isInit) {
final profileData = Provider.of<ProfileProvider>(context);
profileData.getData();
if (profileData.profileInfo != null) {
emailController.text = profileData.profileInfo.name;
nameController.text = profileData.profileInfo.email;
mobileController.text = profileData.profileInfo.mobile;
}
_isInit = false;
super.didChangeDependencies();
}
}
#override
Widget build(BuildContext context) {
final profileData = Provider.of<ProfileProvider>(context);
return Scaffold(
drawer: NavigationDrawer(),
body: profileData.profileInfo == null
? Center(
child: CircularProgressIndicator(),
)
: Builder(
builder: (context) => SingleChildScrollView(
child: Padding(.....
Below the padding, there is normal TextField, can someone tell me why the data is being populated sometime and sometime its coming empty, even I wrapped it with CircularProgressIndicator() and a check the notifyListeners(); is not working there. The loader is not being shown and data is not being loaded.
Thanks
for StatelessWidget.
Inside the build method use:
Future.microtask(() async {
context.read<SomeProvider>().fetchSomething();
});
For StatefulWidgets if you want to call it once. Do this inside the initState() or didChangeDependencies (better if the latter). This will be called at the end of the frame which means after the build or rendering finishes..
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SomeProvider>().fetchSomething();
});
}
EDIT: WidgetsBinding will also work on build. I forgot on why I used microtask lol
i've created a function which called nextTick, i call it in initState and it works for now, but want to see others method
void nextTick(Function callback, [int milliseconds = 0]) {
Future.delayed(Duration(milliseconds: 0)).then((_) {
callback();
});
}
then use it like below
#override
void initState() {
super.initState();
nextTick((){
ProfileProvider profileProvider = Provider.of<ProfileProvider>(context);
profileProvider.getProfile();
});
}
Edit: i store couple of variables to manage them on ui, like isLoading, hasError and errorMessage. Here is my provider class
class ProfileProvider extends ChangeNotifier {
bool _hasError = false;
bool _isLoading = true;
String _errorMsg = '';
Profile _profileResponse;
bool get hasError => _hasError;
bool get isLoading => _isLoading;
String get errorMsg => _errorMsg;
Profile get profileResponse => _profileResponse;
Future<void> getProfile() async {
this.setLoading = true;
this.setError = false;
this.setErrorMsg = '';
try {
await dio.post('$api/p/view', data: {}).then((res) {
print(res.data);
_profileResponse = Profile.fromJson(jsonDecode(res.data));
print(_profileResponse.data);
notifyListeners();
}).whenComplete(() {
this.setLoading = false;
});
} catch (e) {
this.setError = true;
this.setErrorMsg = '$e';
}
this.setLoading = false;
}
set setError(bool val) {
if (val != _hasError) {
_hasError = val;
notifyListeners();
}
}
set setErrorMsg(String val) {
if (val != null && val != '') {
_errorMsg = val;
notifyListeners();
}
}
set setLoading(bool val) {
_isLoading = val;
notifyListeners();
}
}

How to save max duration with sharedpref in flutter?

I tried to save duration with sharedpreference only if my last duration is superior to the current duration and so show the MAX duration. my problem is that I don't know how to compare two string duration.
here is the code thanks (#ZeRj)
load_lastPressString()async{
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
final lastPressString = prefs.getString("lastButtonPress");
_lastButtonPress = lastPressString!=null ? DateTime.parse(lastPressString) : DateTime.now();
_updateTimer();
_ticker = Timer.periodic(Duration(seconds:1),(_)=>_updateTimer());
});
}
save_lastPressString()async{
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
prefs.setString('_lastButtonPress', _lastButtonPress.toIso8601String());
});
}
void _updateTimer() {
final duration = DateTime.now().difference(_lastButtonPress);
final newDuration = _formatDuration(duration);
setState(() {
_pressDuration = newDuration;
});
}
String _formatDuration(Duration duration) {
String twoDigits(int n) {
if (n >= 10) return "$n";
return "0$n";
}
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${twoDigits(duration.inDays)}:${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
}
I tried the code bellow, but I can't use > with string and tried to parse int but not possible with date, and I tried to extract each decimal with regex but it's to complicated and I have errors I havn't understand.
save_max_lastPress()async{
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
if(_lastButtonPress>Max_lastButtonPress)
{
prefs.setString('_max_lastPress', Max_lastButtonPress) ;
}
}
);
}
For that you have to save the maximal as the duration.
Use prefs.setInt("maxDuration",maxDuration.toSeconds()) to save it as a int in shared preferences and load it with
Duration(seconds: prefs.getInt("maxDuration")
You can simply compare two instances of Duration.
I edited the last example to implement this feature:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutterfly/SharedPrefs.dart';
import 'package:flutterfly/SharedPrefs.dart' as prefix0;
class TestWidget extends StatefulWidget {
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
DateTime _lastButtonPress;
String _pressDuration;
Timer _ticker;
Duration _maxDuration;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Time since button pressed"),
Text(_pressDuration),
Text("Maximal Duration"),
Text(_formatDuration(_maxDuration)),
RaisedButton(
child: Text("Press me"),
onPressed: () {
_lastButtonPress = DateTime.now();
_updateTimer();
sharedPreferences.setString("lastButtonPress",_lastButtonPress.toIso8601String());
},
)
],
),
);
}
#override
void initState() {
super.initState();
//load max duration, if there is none start with 0
_maxDuration = Duration(seconds:sharedPreferences.getInt("maxDuration")??0);
final lastPressString = sharedPreferences.getString("lastButtonPress");
_lastButtonPress = lastPressString!=null ? DateTime.parse(lastPressString) : DateTime.now();
_updateTimer();
_ticker = Timer.periodic(Duration(seconds:1),(_)=>_updateTimer());
}
#override
void dispose() {
_ticker.cancel();
super.dispose();
}
void _updateTimer() {
final duration = DateTime.now().difference(_lastButtonPress);
//check for new max duration here
Duration newMaxDuration = _maxDuration;
if(duration> _maxDuration) {
//save when current duration is a new max
newMaxDuration = duration;
sharedPreferences.setInt("maxDuration",newMaxDuration.inSeconds);
}
final newDuration =_formatDuration(duration);
setState(() {
_maxDuration = newMaxDuration;
_pressDuration = newDuration;
});
}
String _formatDuration(Duration duration) {
String twoDigits(int n) {
if (n >= 10) return "$n";
return "0$n";
}
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
}
}