Dark/light theme preferences do not save in the system. Flutter - flutter

I am trying to add dark/light/custom theme modes in my Flutter application. I've used this tutorial, but my theme preferences do not save and do not apply after the application is restarted. I am using the shared_preferences library in my project, which you can access here
I've tried importing theme_manager.dart and other files as a packages and my storage_manager file looks like this now:
import 'package:shared_preferences/shared_preferences.dart';
import 'package:calculator/main.dart';
import 'package:calculator/theme_manager.dart';
import 'package:calculator/settings.dart';
class StorageManager {
static void saveData(String key, dynamic value) async {
final prefs = await SharedPreferences.getInstance();
print(value);
if (value is int) {
prefs.setInt(key, value);
} else if (value is String) {
prefs.setString(key, value);
} else if (value is bool) {
prefs.setBool(key, value);
} else {
print("Invalid Type");
}
}
static Future<dynamic> readData(String key) async {
final prefs = await SharedPreferences.getInstance();
dynamic obj = prefs.get(key);
return obj;
}
static Future<bool> deleteData(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.remove(key);
}
}
And function in theme_manager.dart, that should change theme (at the app start), looks like this:
late ThemeData _themeData;
var _buttonsData;
ThemeData getTheme() => _themeData;
getButtons() => _buttonsData;
ThemeNotifier() {
StorageManager.readData('themeMode').then((value) {
var themeMode = value ?? 'light';
if (themeMode == 'light') {
_themeData = lightTheme;
} else if (themeMode == 'dark') {
_themeData = darkTheme;
} else {
_themeData = customTheme;
}
notifyListeners();
});
StorageManager.readData('buttonsMode').then((value) {
var buttonsMode = value ?? 'circle';
if (buttonsMode == 'circle') {
_buttonsData = circledButtons;
} else if (buttonsMode == 'rounded') {
_buttonsData = roundedButtons;
} else if (buttonsMode == 'box') {
_buttonsData = boxButtons;
}
notifyListeners();
});
}
In the original example - there is no late argument before ThemeData _themeData;, but without it, my whole code does not work. Can this be a problem? Any help would be much appreciated!

Your initialization process isn't working because you are not waiting for it. So you never get the saved theme from shared preferences. You can change your ThemeNotifier to something like that:
ThemeNotifier();
Future<void> init() async {
final themeMode = await StorageManager.readData('themeMode') ?? 'light';
final buttonsMode =
await StorageManager.readData('buttonsMode') ?? 'circle';
if (themeMode == 'light') {
_themeData = lightTheme;
} else if (themeMode == 'dark') {
_themeData = darkTheme;
} else {
_themeData = customTheme;
}
if (buttonsMode == 'circle') {
_buttonsData = circledButtons;
} else if (buttonsMode == 'rounded') {
_buttonsData = roundedButtons;
} else if (buttonsMode == 'box') {
_buttonsData = boxButtons;
}
notifyListeners();
}
Then in your main method you should await the init method
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
final themeNotifier = ThemeNotifier();
await themeNotifier.init();
return runApp(ChangeNotifierProvider<ThemeNotifier>.value(
value: themeNotifier,
child: const MyApp(),
));
}
This way ThemeNotifier will be initialized with your saved value

Related

Unhandled Exception: Bad state: Tried to use PaginationNotifier after `dispose` was called

I have a StateNotifierProvider that calls an async function which loads some images from the internal storage and adds them to the AsyncValue data:
//Provider declaration
final paginationImagesProvider = StateNotifierProvider.autoDispose<PaginationNotifier, AsyncValue<List<Uint8List?>>>((ref) {
return PaginationNotifier(folderId: ref.watch(localStorageSelectedFolderProvider), itemsPerBatch: 100, ref: ref);
});
//Actual class with AsyncValue as State
class PaginationNotifier extends StateNotifier<AsyncValue<List<Uint8List?>>> {
final int itemsPerBatch;
final String folderId;
final Ref ref;
int _numberOfItemsInFolder = 0;
bool _alreadyFetching = false;
bool _hasMoreItems = true;
PaginationNotifier({required this.itemsPerBatch, required this.folderId, required this.ref}) : super(const AsyncValue.loading()) {
log("PaginationNotifier created with folderId: $folderId, itemsPerBatch: $itemsPerBatch");
init();
}
final List<Uint8List?> _items = [];
void init() {
if (_items.isEmpty) {
log("fetchingFirstBatch");
_fetchFirstBatch();
}
}
Future<List<Uint8List?>> _fetchNextItems() async {
List<AssetEntity> images = (await (await PhotoManager.getAssetPathList())
.firstWhere((element) => element.id == folderId)
.getAssetListRange(start: _items.length, end: _items.length + itemsPerBatch));
List<Uint8List?> newItems = [];
for (AssetEntity image in images) {
newItems.add(await image.thumbnailData);
}
return newItems;
}
void _updateData(List<Uint8List?> result) {
if (result.isEmpty) {
state = AsyncValue.data(_items);
} else {
state = AsyncValue.data(_items..addAll(result));
}
_hasMoreItems = _numberOfItemsInFolder > _items.length;
}
Future<void> _fetchFirstBatch() async {
try {
_numberOfItemsInFolder = await (await PhotoManager.getAssetPathList()).firstWhere((element) => element.id == folderId).assetCountAsync;
state = const AsyncValue.loading();
final List<Uint8List?> result = await _fetchNextItems();
_updateData(result);
} catch (e, stk) {
state = AsyncValue.error(e, stk);
}
}
Future<void> fetchNextBatch() async {
if (_alreadyFetching || !_hasMoreItems) return;
_alreadyFetching = true;
log("data updated");
state = AsyncValue.data(_items);
try {
final result = await _fetchNextItems();
_updateData(result);
} catch (e, stk) {
state = AsyncValue.error(e, stk);
log("error catched");
}
_alreadyFetching = false;
}
}
Then I use a scroll controller attached to a CustomScrollView in order to call fetchNextBatch() when the scroll position changes:
#override
void initState() {
if (!controller.hasListeners && !controller.hasClients) {
log("listener added");
controller.addListener(() {
double maxScroll = controller.position.maxScrollExtent;
double position = controller.position.pixels;
if ((position > maxScroll * 0.2 || position == 0) && ref.read(paginationImagesProvider.notifier).mounted) {
ref.read(paginationImagesProvider.notifier).fetchNextBatch();
}
});
}
super.initState();
}
The problem is that when the StateNotifierProvider is fetching more data in the async function fetchNextBatch() and I go back on the navigator (like navigator.pop()), Flutter gives me an error:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Tried to use PaginationNotifier after dispose was called.
Consider checking mounted.
I think that the async function responsible of loading data completes after I've popped the page from the Stack (which triggers a Provider dispose).
I'm probably missing something and I still haven't found a fix for this error, any help is appreciated.

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();
}
}

Flutter Firestore Query snapshot- result is always null

I have a simple flutter code to retrieve some data from Firestore. the data is retireved correctly, however passing the data from the future function making the result always null. can you advise how to adapt the code to return the list?
that is the class where the actual query is happening:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
print(businessprofileslist[0]);
});
});
} catch (e) {
print(e.toString());
return null;
}
}
}
here is the page where I am calling the function: (however the result is always null)
class _ProfilesListPageState extends State<ProfilesListPage> {
List businessprofileslist = [];
#override
void initState() {
super.initState();
fetchBusinessProfilesList();
}
fetchBusinessProfilesList() async {
dynamic result = await DatabaseManager().GetBusinessProfilesCollection();
print(result.toString());
if (result == null) {
print('enable to retieve');
} else {
print('success');
setState(() {
businessprofileslist = result;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
You're not returning anything from GetBusinessProfilesCollection but null, so the result seems somewhat expected.
I guess you want to do:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
var QuerySnapshot = await BusinessProfilesCollection.get();
querySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
});
return businessprofileslist;
} catch (e) {
print(e.toString());
return null;
}
}
}
Btw: returning null when the load fails, is just going to lead to a null pointer exception when you then do print(result.toString());. So I recommend not catching the error and just letting it bubble up. With that your code can be simplified to:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
var QuerySnapshot = await BusinessProfilesCollection.get();
return querySnapshot.docs.map((element) => element.data());
}
}
You just need to return the list
return businessprofileslist;
CODE :
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
List businessprofileslist = [];
try {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.forEach((element) {
businessprofileslist.add(element.data());
print(businessprofileslist[0]);
});
// you just need to return the list here after filling it up
return businessprofileslist;
});
} catch (e) {
print(e.toString());
return null;
}
}
}
Code with a little improvement:
class DatabaseManager {
final CollectionReference BusinessProfilesCollection =
FirebaseFirestore.instance.collection("BusinessProfilesCollection");
Future GetBusinessProfilesCollection() async {
await BusinessProfilesCollection.get().then((QuerySnapshot) {
QuerySnapshot.docs.map((doc) => doc.data()).toList();
});
}
}
Try that with calling the function in feching
fetchBusinessProfilesList()
async {
dynamic result ;
await DatabaseManager().GetBusinessProfilesCollection().then((value){
result=value;
print(result.toString());
if (result == null) {
print('enable to retieve');
} else {
print('success');
setState(() {
businessprofileslist = result;
});
}
});
}

sharedp references Getting Stored Data in Another Flutter Page

I want to get user details such as id, email, username in my HomePage when the user login into the login page. I was able to get the data via this
SPUtil.putString('user', user);
print(user);
Now, the issue is how should I pick it up in another page for usage or display those data. I have a file called sputils.dart where all the code that I used to get the data.
class AuthService {
Future<String> login({
required String username,
required String password,
}) async {
try {
final body = {
'username': username,
'password': password,
};
final response = await http.post(
Uri.parse('$BASE_URL/login'),
headers: {'Content-Type': 'application/json; charset=UTF-8'},
body: jsonEncode(body),
);
if (response.statusCode != 200) {
throw LoginError.unexpected;
}
Map<String, dynamic> data = jsonDecode(response.body);
User loggedInUser = User.fromJson(data['user']);
String user = jsonEncode(loggedInUser);
SPUtil.putString('user', user);
print(user);
return jsonDecode(response.body)['token'];
} on LoginError {
print('login error');
rethrow;
} catch (e) {
print(e);
throw LoginError.unexpected;
}
}
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
// SharedPreferences
class SPUtil {
static SPUtil? _singleton;
static SharedPreferences? _prefs;
static final Lock _lock = Lock();
static Future<SPUtil> getInstance() async {
if (_singleton == null) {
await _lock.synchronized(() async {
if (_singleton == null) {
// keep local instance till it is fully initialized.
final singleton = SPUtil._();
await singleton._init();
_singleton = singleton;
}
});
}
return _singleton!;
}
SPUtil._();
Future _init() async {
_prefs = await SharedPreferences.getInstance();
}
// put object
static Future<bool> putObject(String key, Object value) {
return _prefs!.setString(key, json.encode(value));
}
// get string
static String getString(String key, {String defValue = ''}) {
if (_prefs == null) return defValue;
return _prefs!.getString(key) ?? defValue;
}
// put string
static Future<bool> putString(String key, String value) async {
return _prefs!.setString(key, value);
}
// get bool
static bool getBool(String key, {bool defValue = false}) {
if (_prefs == null) return defValue;
return _prefs!.getBool(key) ?? defValue;
}
// put bool
static Future<bool> putBool(String key, {bool value = false}) {
return _prefs!.setBool(key, value);
}
// get int
static int getInt(String key, {int defValue = 0}) {
if (_prefs == null) return defValue;
return _prefs!.getInt(key) ?? defValue;
}
// put int.
static Future<bool> putInt(String key, int value) {
return _prefs!.setInt(key, value);
}
// get double
static double getDouble(String key, {double defValue = 0.0}) {
if (_prefs == null) return defValue;
return _prefs!.getDouble(key) ?? defValue;
}
// put double
static Future<bool> putDouble(String key, double value) {
return _prefs!.setDouble(key, value);
}
// get string list
static List<String> getStringList(String key,
{List<String> defValue = const []}) {
if (_prefs == null) return defValue;
return _prefs!.getStringList(key) ?? defValue;
}
// put string list
static Future<bool> putStringList(String key, List<String> value) {
return _prefs!.setStringList(key, value);
}
// clear
static Future<bool> clear() {
return _prefs!.clear();
}
// clear a string
static Future<bool> clearString(String key) {
return _prefs!.remove(key);
}
//Sp is initialized
static bool isInitialized() {
return _prefs != null;
}
}
To use saved data from another page,
make your data instance to global(static)
save the data inside of static class
If your application is simple and just test something, go to No.1.
Or else, go to No.2.
make class static : https://dev.to/lucianojung/global-variable-access-in-flutter-3ijm
Using getController is easier to make it global : https://pub.dev/packages/get

Flutter how to check Internet connection is available or not

I am trying to use this plugin https://pub.dev/packages/connectivity/example Issue is its not showing or print internet is connected or not.
This is my code
class _HomePageState extends State<HomePage> {
String _connectionStatus = 'Unknown';
final Connectivity _connectivity = Connectivity();
StreamSubscription<ConnectivityResult> _connectivitySubscription;
#override
void initState() {
super.initState();
initConnectivity();
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (mounted) {
if (userManager.getCurrentDriver() != null &&
userManager.getCurrentDriver().isNotEmpty) {
FirebaseFirestore.instance
.collection(FIREBASE_PATH_TRIP)
.doc(userManager.getCurrentDriver())
.get()
.then((event) {
if (event != null) {
var trip =
DriverModel.fromMap(Map<String, dynamic>.from(event.data()));
Provider.of<TripState>(context, listen: false).driver = trip;
Provider.of<BottomSheetSelector>(context, listen: false)
.changeSheet(SheetType.Profile);
} else {
userManager.saveCurrentDriver('');
}
});
}
if (Theme.of(context).platform == TargetPlatform.android) {
checkForAndroidUpdate(context);
}
}
});
}
#override
void dispose() {
_connectivitySubscription.cancel();
super.dispose();
}
Future<void> initConnectivity() async {
ConnectivityResult result;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
result = await _connectivity.checkConnectivity();
} on PlatformException catch (e) {
print(e.toString());
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) {
return Future.value(null);
}
return _updateConnectionStatus(result);
}
#override
Widget build(BuildContext context) {
final _drawerKey = GlobalKey<ScaffoldState>();
ScreenUtil.init(context);
return SafeArea(
child: WillPopScope(
child: Scaffold(
key: _drawerKey,
backgroundColor: Colors.black,
resizeToAvoidBottomInset: false,
drawer: ViteDrawer(),
body: null,
),
));
}
Future<void> _updateConnectionStatus(ConnectivityResult result) async {
switch (result) {
case ConnectivityResult.wifi:
case ConnectivityResult.mobile:
case ConnectivityResult.none:
setState(() => _connectionStatus = result.toString());
break;
default:
setState(() => _connectionStatus = 'Failed to get connectivity.');
break;
}
}
}
What i need to do is simple print if internet is connected or not. I want to show alert but print is ok so ill manage it. But dont know why its not printing anything
You can try with this
Future<bool> check() async {
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
print("Connected}");
return true;
} else if (connectivityResult == ConnectivityResult.wifi) {
print("Connected}");
return true;
}
print("not Connected}");
// return You can add your dialog for notify user to your connectivity is off
}
you can use below code to check the connectivity
Future<bool> checkInternetConnectivity() async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
return true;
} else {
return false;
}
} on SocketException catch (_) {
return false;
}
}
simple
Future<bool> isConnected() async {
var result = await Connectivity().checkConnectivity();
return result != ConnectivityResult.none;
}