Flutter - How to save ThemeMode preference using Shared Preferences and SwitchTile - android

my goal is to save the ThemeMode preference even when the app is closed.
I tried to follow some guides but unsuccessfully, I need to know what I'm doing wrong.
Can someone help me and provide me with the right code?
provider.dart
class ThemeProvider extends ChangeNotifier {
ThemeMode themeMode = ThemeMode.light;
bool get isDarkMode => themeMode == ThemeMode.dark;
void toggleTheme(bool isOn) {
themeMode = isOn ? ThemeMode.dark : ThemeMode.light;
notifyListeners();
}
}
drawer.dart
#override
Widget build(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);
return Drawer(
child: ListView(
physics: const ScrollPhysics(),
padding: EdgeInsets.zero,
children: <Widget>[
SwitchListTile(
secondary: Icon(
themeProvider.isDarkMode ? Icons.dark_mode : Icons.light_mode,
),
title: const Text('Tema'),
value: themeProvider.isDarkMode,
onChanged: (value) {
final provider =
Provider.of<ThemeProvider>(context, listen: false);
provider.toggleTheme(value);
},
),
],
),
);
}

This is what i use for my app, it has three themes but works..
ThemeNotifier(IAppRepository appRepository) {
_appPrefsUseCase = AppPrefsUseCase(appRepository);
_loadFromPrefs();
}
toggleTheme(int index) {
var type = ThemeType.values[index];
_saveToPrefs(type);
notifyListeners();
}
void _loadFromPrefs() async {
final _theme = _appPrefsUseCase?.call<String>(AppKeys.theme);
_userTheme = ThemeType.values
.firstWhere((t) => t.toString() == _theme, orElse: () => _userTheme);
notifyListeners();
}
void _saveToPrefs(ThemeType type) {
_userTheme = type;
_appPrefsUseCase?.savePref(key: AppKeys.theme, value: type.toString());
}
}

Related

How to check for internet connection once for every screen in Flutter?

I want to check for internet connection at every screen on my app just like Telegram does and whenever user goes offline, show an Offline banner on the top of the screen.
I have tried using connectivity_plus and internet_connection_checker plugins to check for this but the problem is I have to subscribe to a stream for this and if the device goes offline once then there is no way to subscribe to it again without clicking a button.
getConnectivity() =>
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
setState(() {
constants.offline = true;
print('Constants().offline ${constants.offline}');
isAlertSet = true;
});
}
print('off');
},
);
I'm using this code right now to check this issue but I don't want to replicate this code on each and every screen and even if I do replicate it then there will be a lot of subscriptions that I'll be subscribing to, which will mean that all the subscriptions will be disposed at the same time causing all sorts of issues.
If you have custom Scaffold, then you have to edit it. Otherwise, create a new one and change all Scaffolds to your custom one. This allows you to easily apply changes that should be on all pages.
Then, in the CustomScaffold create a Stack that contains page content and ValueListenableBuilder that listens to connection changes and if there is no internet displays error banner.
class CustomScaffold extends StatefulWidget {
const CustomScaffold({Key? key}) : super(key: key);
#override
State<CustomScaffold> createState() => _CustomScaffoldState();
}
class _CustomScaffoldState extends State<CustomScaffold> with WidgetsBindingObserver {
StreamSubscription? connectivitySubscription;
ValueNotifier<bool> isNetworkDisabled = ValueNotifier(false);
void _checkCurrentNetworkState() {
Connectivity().checkConnectivity().then((connectivityResult) {
isNetworkDisabled.value = connectivityResult == ConnectivityResult.none;
});
}
initStateFunc() {
_checkCurrentNetworkState();
connectivitySubscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) {
isNetworkDisabled.value = result == ConnectivityResult.none;
},
);
}
#override
void initState() {
WidgetsBinding.instance.addObserver(this);
initStateFunc();
super.initState();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
_checkCurrentNetworkState();
}
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
connectivitySubscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
Scaffold(
...
),
ValueListenableBuilder(
valueListenable: isNetworkDisabled,
builder: (_, bool networkDisabled, __) =>
Visibility(
visible: networkDisabled,
child: YourErrorBanner(),
),
),
],
);
}
}
First I created an abstract class called BaseScreenWidget
used bloc state management to listen each time the internet connection changed then show toast or show upper banner with Blocbuilder
abstract class BaseScreenWidget extends StatelessWidget {
const BaseScreenWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
baseBuild(context),
BlocConsumer<InternetConnectionBloc, InternetConnectionState>(
listener: (context, state) {
// if (!state.isConnected) {
// showToast("No Internet Connection");
// }
},
builder: (context, state) {
if (!state.isConnected) {
return const NoInternetWidget();
}
return const SizedBox.shrink();
},
),
],
);
}
Widget baseBuild(BuildContext context);
}
Made each screen only screen widgets contains Scaffold to extends BaseScreenWidget
class MainScreen extends BaseScreenWidget {
const MainScreen({super.key});
#override
Widget baseBuild(BuildContext context) {
return const Scaffold(
body: MainScreenBody(),
);
}
}
it's very helpful to wrap the Column with SafeArea in the build method in BaseScreen.
USE THIS SIMPLE TECHNIQUE only need this package: Internet Connection Checker. If you turn off your network it will tell you
connection_checker.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
class CheckMyConnection {
static bool isConnect = false;
static bool isInit = false;
static hasConnection(
{required void Function() hasConnection,
required void Function() noConnection}) async {
Timer.periodic(const Duration(seconds: 1), (_) async {
isConnect = await InternetConnectionChecker().hasConnection;
if (isInit == false && isConnect == true) {
isInit = true;
hasConnection.call();
} else if (isInit == true && isConnect == false) {
isInit = false;
noConnection.call();
}
});
}
}
base.dart
import 'package:flutter/material.dart';
import 'connection_checker.dart';
class Base extends StatefulWidget {
final String title;
const Base({Key? key, required this.title}) : super(key: key);
#override
State<Base> createState() => _BaseState();
}
class _BaseState extends State<Base> {
final snackBar1 = SnackBar(
content: const Text(
'Internet Connected',
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
);
final snackBar2 = SnackBar(
content: const Text(
'No Internet Connection',
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.red,
);
#override
void initState() {
super.initState();
CheckMyConnection.hasConnection(hasConnection: () {
ScaffoldMessenger.of(navigatorKey.currentContext!)
.showSnackBar(snackBar1);
}, noConnection: () {
ScaffoldMessenger.of(navigatorKey.currentContext!)
.showSnackBar(snackBar2);
});
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
key: navigatorKey,
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: const Text('Tabs Demo'),
),
body: const TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
);
}
}
I myself use connectivity_plus and I have never found the problem you mentioned (if the device goes offline once then there is no way to subscribe to it again without clicking a button), you can use my example.
If the user's internet is disconnected, a modal will appear. If the user is connected again, the modal will be deleted automatically.
Anyway, I put the option to check the internet again in the modal
class CheckConnectionStream extends GetxController {
bool isModalEnable = false;
final loadingCheckConnectivity = false.obs;
ConnectivityResult _connectionStatus = ConnectivityResult.none;
final Connectivity _connectivity = Connectivity();
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
Future<void> initConnectivity() async {
late ConnectivityResult result;
try {
result = await _connectivity.checkConnectivity();
loadingCheckConnectivity.value = false;
} on PlatformException {
return;
}
return _updateConnectionStatus(result);
}
Future<void> _updateConnectionStatus(ConnectivityResult result) async {
_connectionStatus = result;
if (result == ConnectivityResult.none) {
if (isModalEnable != true) {
isModalEnable = true;
showDialogIfNotConnect();
}
} else {
if (isModalEnable) {
Get.back();
}
isModalEnable = false;
}
}
showDialogIfNotConnect() {
Get.defaultDialog(
barrierDismissible: false,
title: "check your network".tr,
onWillPop: () async {
return false;
},
middleText: "Your device is not currently connected to the Internet".tr,
titleStyle: TextStyle(
color: Get.isDarkMode ? Colors.white : Colors.black,
),
middleTextStyle: TextStyle(
color: Get.isDarkMode ? Colors.white : Colors.black,
),
radius: 30,
actions: [
Obx(() => loadingCheckConnectivity.value
? const CustomLoading(
height: 30.0,
radius: 30.0,
)
: ElevatedButton(
onPressed: () async {
loadingCheckConnectivity.value = true;
EasyDebounce.debounce(
'check connectivity',
const Duration(milliseconds: 1000), () async {
await initConnectivity();
});
},
child: Text(
'try again'.tr,
style: const TextStyle(color: Colors.white),
),
))
]);
}
#override
void onInit() {
super.onInit();
initConnectivity();
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
}
#override
void onClose() {
_connectivitySubscription.cancel();
super.onClose();
}
}

Flutter setState() not updating the view after Invoking Flutter Code From Native Side

I am trying to implement invoking Flutter Code From Native Side using method channel and working as expected. But having issue with rendering the view after trying to set the state. Can any one help to fix the issue?
Actually the SimSlotInfo is calling from the below widget,
List<Step> getSteps() {
return <Step>[
Step(
state: currentStep > 0 ? StepState.complete : StepState.indexed,
isActive: currentStep >= 0,
title: const Text("Send SMS"),
content: Column(
children: [
SimSlotInfo()
],
),
),
];
}
SimSlotInfo dart class
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutterdemo/model/device_slot.dart';
class SimSlotInfo extends StatefulWidget {
//callback function
final void Function(String) callBackFunction;
const SimSlotInfo(this.callBackFunction, {super.key});
//const SimSlotInfo({Key? key}) : super(key: key);
#override
State<SimSlotInfo> createState() => _SimSlotInfoState();
}
class _SimSlotInfoState extends State<SimSlotInfo> {
final platformMethodChannel = const MethodChannel('common_lib_plugin');
List<SimDetails> simDetailsObj = [];
//execute the below code while page loading
#override
void initState() {
super.initState();
platformMethodChannel.setMethodCallHandler(handleNativeMethodCall);
}
Future<void> handleNativeMethodCall(MethodCall call) async {
// do some processing
switch(call.method) {
case "deviceInfo":
var simData = call.arguments;
var arrayObjsText = '[{"slot":0,"simno":"89911017061","deviceid":"3518920","carrierName":"Vodafone"},{"slot":1,"simno":"89101706","deviceid":"3511643","carrierName":"JIO"}]';
List simObjsJson = jsonDecode(arrayObjsText) as List;
simDetailsObj = simObjsJson.map((tagJson) => SimDetails.fromJson(tagJson)).toList();
setState(() {
simDetailsObj = simDetailsObj;
});
}
}
#override
Widget build(BuildContext context) {
return Column(
children:
simDetailsObj.map((data) => RadioListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${data.carrierName}",
style: const TextStyle(color: Colors.black, fontSize: 18),
),
],
),
groupValue: _selectedSim,
value: data.simno,
onChanged: (val) {
},
)).toList()
);
}
}
First, you are trying to assign List to List so your code is getting brake there. to solve that loop the object with SimDetails object. and that will do the trick
ParentWidget
class _ParentWidgetState extends State<ParentWidget> {
#override
Widget build(BuildContext context) {
return ChildWidget( // <---- child widget
callSetState: (list) { // <--- callback Function
print(list);
setState(() {
// <---
});
},
);
}
}
In Child widget
class ChildWidget extends StatefulWidget {
const ChildWidget({Key? key, required this.callSetState}) : super(key: key);
final Function(List<SimDetails>) callSetState; // <-- declare callback function here
#override
State<ChildWidget> createState() => _ChildWidgetState();
}
and replace your setState with widget.callSetState
Future<void> handleNativeMethodCall(MethodCall methodCall) async {
switch (call.method) {
case 'deviceInfo':
var simData = call.arguments;
var arrayObjsText =
'[{"slot":0,"simno":"89911017061","deviceid":"3518920","carrierName":"Vodafone"},{"slot":1,"simno":"89101706","deviceid":"3511643","carrierName":"JIO"}]';
for (var data in jsonDecode(arrayObjsText)) {
simDetailsObj.add(
SimDetails(
slot: data['slot'],
simno: data['simno'],
deviceid: data['deviceid'],
carrierName: data['carrierName'],
),
);
}
/// setState(() {});
widget.callSetState(simDetailsObj);
break;
default:
}}

Shared Preferences Flutter - Save depending on Book Title

I'm working on a PDF viewer, where I'm trying to store the last page a user read to Shared Preferences as the Book_Title id (tid). When the user resumes reading he's redirected to where he previously left off depending on each book.
The problem I'm facing is the shared Preferences is not being saved to each Book_title id. It's saving the last page to all the books
setInt(Book_title ID, setPage)
var book;
int bookPage = 1;
int setPage = 1;
#override
void initState() {
book = '${widget.tid}';
_pdfViewerController = PdfViewerController();
getPref();
super.initState();
}
getPref() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt('${book}', setPage);
bookPage = prefs.getInt('${book}')!;
print(book);
}
Full code
class libraryPDF extends StatefulWidget {
final String? sid;
final String? tid;
final String? page;
const libraryPDF({Key? key, this.sid, this.page, this.tid}) : super(key: key);
#override
State<libraryPDF> createState() => _libraryPDFState();
}
var book;
int bookPage = 1;
int setPage = 1;
class _libraryPDFState extends State<libraryPDF> {
late PdfViewerController _pdfViewerController;
final GlobalKey<SfPdfViewerState> _pdfViewerStateKey = GlobalKey();
#override
void initState() {
book = '${widget.tid}';
_pdfViewerController = PdfViewerController();
getPref();
super.initState();
}
getPref() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setInt('${book}', setPage);
bookPage = await prefs.getInt('${book}')!;
print(book);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
elevation: 0,
title: const Text("CanadianHS"),
backgroundColor: Colors.blue[900],
actions: <Widget>[
IconButton(
onPressed: () {
_pdfViewerStateKey.currentState!.openBookmarkView();
},
icon: Icon(
Icons.bookmark,
color: Colors.white,
)),
IconButton(
icon: Icon(
Icons.keyboard_arrow_left,
color: Colors.white,
),
onPressed: () {
_pdfViewerController.previousPage();
},
),
IconButton(
icon: Icon(
Icons.keyboard_arrow_right,
color: Colors.white,
),
onPressed: () {
_pdfViewerController.nextPage();
},
)
],
),
body: SfPdfViewer.network(
'https://url/${widget.sid}',
controller: _pdfViewerController,
onDocumentLoaded: (PdfDocumentLoadedDetails details) {
_pdfViewerController.jumpToPage(bookPage);
print(details.document.pages.count);
},
key: _pdfViewerStateKey,
onPageChanged: (PdfPageChangedDetails details) {
setPage = details.newPageNumber;
},
pageLayoutMode: PdfPageLayoutMode.single),
);
}
}
Well, in your getPrefs before you read the page of the current book, you set it. You don't want to set the preferences in a method called getPrefs.
Just remove the line prefs.setInt('${book}', setPage); from your getPrefs method. There are a lot of other bugs in there, but that should get you going.
I suggest turning on and listening to your linter. It will catch a lot of what is wrong and give you good hints on how to improve your code.
You can try this by adding any dynamic values with the prefixed keys.
Such as,
prefs.setInt("pageNo${bookId}", value);

Display and edit text into TextFormField using voice (Flutter Speech to text)

I am working on Flutter speech to text. The scenery is user can give their comment using voice. While they give input for comment by their voice they may edit the comment field and enter some text(given by their voice) into any position. Like, I give input using my voice and want to enter some words on the first or middle or any place where I think I've to add some words for completing my comments.
I am using speech_to_text: ^5.2.0. Following are the code sample. Here, I just came to display the text into TextFormField. The value remove when I re-enter my voice. But what I need the value have to be in TextFormField and I can edit it any position of the given word that I think I've to input here.
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String text = 'Hi';
bool isListening = false;
final _textEditingController = TextEditingController();
late stt.SpeechToText _speech;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
TextFormField(
controller: _textEditingController,
),
Text(text),
],
)
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: AvatarGlow(
animate: isListening,
endRadius: 150,
glowColor: Theme.of(context).primaryColor,
child: FloatingActionButton(
onPressed: toggleRecording,
child: Icon(isLintening ? Icons.mic : Icons.mic_none),
),
),
),
));
}
Future toggleRecording() => SpechApi.toggleRecording(
onResult: (text) => setState(() {
this.text = text;
_textEditingController.text = text;
}),
onListening: (isLintening) {
setState(() => this.isListening = isListening);
if(!isListening) {
Future.delayed(Duration(seconds: 1), () {
// Utils.scanText(text);
_textEditingController.text = text;
});
}
});
void onListen() async {
bool available = await _speech.initialize(
onStatus: (val) => print('onStatus: $val'),
onError: (val) => print('onError: $val'));
if (!isListening) {
if (available) {
setState(() {
isLintening = false;
_speech.listen(
onResult: (val) => setState(() {
_textEditingController.text = text;
}),
);
});
}
} else {
setState(() {
isLintening = false;
_speech.stop();
});
}
}
}

Checkbox with many options

I created two checkboxes but after clicking on one of them both are marked, as in the picture below, could someone help me solve this problem?
only one can be marked,
my code:
class _LanguageSelectorState extends State<LanguageSelector> {
static final List<String> languagesList = application.supportedLanguages;
static final List<String> languageCodesList =
application.supportedLanguagesCodes;
final Map<dynamic, dynamic> languagesMap = {
languagesList[0]: languageCodesList[0],
languagesList[1]: languageCodesList[1],
};
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.black),
title: Text(AppTranslations.of(context).text("settings_language"), style: TextStyle(color: Colors.black, letterSpacing: 1)),
elevation: 0.0,
centerTitle: true,
bottom: PreferredSize(child: Container(color: Colors.black, height: 0.1), preferredSize: Size.fromHeight(0.1),),
),
body: _buildLanguagesList()
);
}
String selectedLanguage = '';
_buildLanguagesList() {
return ListView.builder(
itemCount: languagesList.length,
itemBuilder: (context, index) {
return _buildLanguageItem(languagesList[index]);
},
);
}
bool _value = false;
_buildLanguageItem(String language) {
return CheckboxListTile(
title: Text(language),
value: _value,
onChanged: (value) {
setState(() {
_value = value;
application.onLocaleChanged(Locale(languagesMap[language]));
});
},
controlAffinity: ListTileControlAffinity.trailing,
);
}
}
thanks for any help :)
////////////////////////////////////////////////////////////////////
Take a look at this example.. Hope that will answer your question how to use checkboxes in listView
List<Map<String, dynamic>> languagesList = [
{'value': false},
{'value': false}
];
ListView.builder(
itemCount: languagesList.length,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text(languagesList[index]['value'].toString()),
value: languagesList[index]['value'],
onChanged: (value) {
setState(() {
languagesList[index]['value'] = value;
});
},
controlAffinity: ListTileControlAffinity.trailing,
);
}),
The reason your approach didn't work was because you have assigned one variable to all your checkboxes so no wander your checkboxes were updated together
Because all the widgets created by the ListView has the same value _value, if one of the widget gets checked, the value for all of the widgets change as the all depend on the same variable.
Here is a demonstration of how you can do it. it may contain errors.
import 'package:flutter/material.dart';
class LanguageItem extends StatefulWidget {
Key key;
bool isSelected = false;
YOURCLASS application;
String language;
LanguageItem({#required language, #required this.application, this.key
}):super(key:key);
#override
_LanguageItemState createState() => _LanguageItemState();
}
class _LanguageItemState extends State<LanguageItem> {
#override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(widget.language),
value: widget.isSelected,
onChanged: (value) {
setState(() {
widget.isSelected = value;
widget.application.onLocaleChanged(Locale(languagesMap[language]));
});
},
controlAffinity: ListTileControlAffinity.trailing,
);
}
}

Categories

Resources