Flutter Shared Preferences Auth FIle - dart

I'm trying to write an auth file, with a list of finals with shared preferences values in it. I could import that auth file in my other files and i could get like the name or email without importing shared preferences in every file.
It would probably look way smoother and cleaner.
I thought something like this would have worked but it didn't
/// ------------Auth------------ ///
final email = getEmail();
getEmail() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('email');
}
Does anybody have any idea how to do this?
Greetings,
Jente

I assume you want to use the method in multiple files. The problem with your code is that the getEmail method is marked async that means it will have to return a Future. Think about it like this, when you mark a method as async it means it will return something (or finish executing) in the near future. When ? Well you don't know exactly when, so you'll need to get "notified" when the method is "done", that's why you'll use a Future. Something like this:
Future<String> getEmail() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('email');
}
class ThisIsTheClassWhereYouWantToUseTheFunction {
//let's say you have a method in your class called like the one below (it can be any other name)
void _iNeedTheEmailToPrintIt() {
//this is the way you can call the method in your classes, this class is just an example.
getEmail().then((thisIsTheResult){ // here you "register" to get "notifications" when the getEmail method is done.
print("This is the email $thisIsTheResult");
});
}
}

you can define a class Auth or much better a scoped_model.
Here's a class implementation
class Auth {
get email() {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('email');
}
set email(String em) {
final SharedPreferences prefs = await SharedPreferences.getInstance();
pref.setString('email', em);
}
}
and now you can call it in your widgets :)

Try this;
make dart file (Filename and Class Name ShareUtils)
add follow Code
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
class ShareUtils {
static ShareUtils _instance;
SharedPreferences ShareSave;
factory ShareUtils() => _instance ?? new ShareUtils._();
ShareUtils._();
void Instatce() async {
ShareSave = await SharedPreferences.getInstance();
}
Future<bool> set(key, value) async {
return ShareSave.setString(key, value);
}
Future<String> get(key) async {
return ShareSave.getString(key);
}
}
2.Add main.dart
class MyApp extends StatelessWidget {
static ShareUtils shareUtils;
#override
Widget build(BuildContext context) {
ThemeData mainTheme = new ThemeData(
);
shareUtils = new ShareUtils();
shareUtils.Instatce();
MaterialApp mainApp = new MaterialApp(
title: "Your app",
theme: mainTheme,
home: new SplashPage(),
debugShowCheckedModeBanner: true,
routes: <String, WidgetBuilder>{
"RegisterPage": (BuildContext context) => new RegisterPage(),
"HomePage": (BuildContext context) => new HomePage(),
},
);
return mainApp;
}
}
3.SET
void UserInfo(code, token) async{
await MyApp.shareUtils.set("token", token);
await MyApp.shareUtils.set("code", code);
await Navigator.of(context).pushNamed("HomePage");
}
4.GET
Future NextPage() async {
MyApp.shareUtils.get("token").then((token) {
print(token);
if (token == null || token == "") {
Navigator.of(context).popAndPushNamed("RegisterPage");
} else {
Navigator.of(context).popAndPushNamed("HomePage");
}
});
}
Hope to help.

Related

Flutter integrating Hive database with Riverpod

There is very easy way to use Hive key-value database on StatefulWidgets, for example:
class HookDemo extends StatefulWidget {
#override
_HookDemoState createState() => _HookDemoState();
}
class _HookDemoState extends State<HookDemo> {
Box user;
#override
void initState() {
super.initState();
user = Hive.box<User>('user');
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
final _u = User()
..nameFamily = 'myname'
..mobileNumber = '123456789';
_user!.add(_u);
_u.save();
},
child: Icon(Icons.add),
),
...
);
}
}
here we defined Box user property and inside initState we implemented what's user such as user = Hive.box<User>('user');
after that we can use user without any problem and getting already opened error
now in this current application we used HookWidget and when we want to use Hive we get error as box already opened
main.dart:
Future<void> initHiveDriver() async {
final appDocumentDirectory = await path_provider.getApplicationDocumentsDirectory();
await Hive.initFlutter(appDocumentDirectory.path);
await Hive.openBox<UserAdapter>('user');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
///...
initHiveDriver();
runApp(
ProviderScope(observers: [
Logger()
],
child: MyApp()),
);
}
how can i create a provider for Hive with Riverpod and use it into HookWidget?
I am using Hive with Riverpod like this.
I am using a named constructor so I can await the openBox call.
final hiveProvider = FutureProvider<HiveDB>((_) => HiveDB.create());
class HiveDB {
var _userBox;
HiveDB._create() {}
static Future<HiveDB> create() async {
final component = HiveDB._create();
await component._init();
return component;
}
_init() async {
Hive.registerAdapter(UserAdapter());
this._userBox = await Hive.openBox<User>('user');
}
storeUser(User user) {
this._userBox.put('user', user);
}
User getUser() {
return this._userBox.get('user');
}
}
Use in a ConsumerWidget:
class SomeWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(hiveProvider).data?.value;
...
}
}

Flutter - Firebase Dynamic Link is not caught by onLink but open the app on iOS

Everything works fine on android but on ios when the app is already opened clicking the link takes the app in the foreground but the onLink method is not call.
Link:
https://<url>/?link=<link>&apn=<apn>&ibi=<bundle>&isi=<isi>
Package:
firebase_dynamic_links: ^0.6.3
Code
import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';
import 'package:flutter/material.dart';
class DynamicLinksService {
Future handleDynamicLinks(BuildContext context) async {
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getInitialLink();
await _handleDynamicLink(context, data);
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLinkData) async {
await _handleDynamicLink(context, dynamicLinkData);
}, onError: (OnLinkErrorException e) async {
print('Dynamic link failed: ${e.message}');
});
}
Future _handleDynamicLink(
BuildContext context, PendingDynamicLinkData data) async {
final Uri deepLink = data?.link;
if (deepLink != null) {
print('_handleDeepLink | deepLink $deepLink');
await _doSomething(context, deepLink.toString());
} else {
print('no deepLink');
}
}
}
From my experimentation, onLink is not called on iOS however you can call getInitialLink() and it will contain the link. I'm uncertain if this is by design or a bug, but it seems to be across a few versions.
Example service code:
Future<Uri> retrieveDynamicLink(BuildContext context) async {
try {
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
return deepLink;
} catch (e) {
print(e.toString());
}
return null;
}
Widget snippet
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed){
final _timerLink = Timer(
const Duration(milliseconds: 1000),
() async {
final auth = Provider.of<FirebaseAuthService>(context, listen: false);
final link = await auth.retrieveDynamicLink(context);
_handleLink(link);
},
);
}
}
Make sure to add the WidgetsBindingObserver
class _SignInPageState extends State<SignInPage> with TickerProviderStateMixin, WidgetsBindingObserver{
I'm not sure why this works but you'd first have to call FirebaseMessaging.instance.getInitialMessage() at least once before your onLink callback is activated by Firebase.
Not sure if this is by design or a bug.
Let me know if this works.

Flutter - How to get value from shared preferences in a non-async method

I am trying to get some values saved in the SharedPreferences from a getter method of a class. But SharedPreferences.getInstance() returns a Future. Is there a way to obtain the SharedPreferences object in a non-async getter methods, for example:
import 'package:shared_preferences/shared_preferences.dart';
class MyClass {
get someValue {
return _sharedPreferencesObject.getString("someKey");
}
}
Is there something in Dart that is similar to .Result property in C#, for example getSomethingAsync().Result (https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.result?view=netframework-4.7.2)?
You can use FutureBuilder()
SharedPreferences sharedPrefs;
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getPrefs(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return YourFinalWidget();
}
return CircularProgressIndicator(); // or some other widget
},
);
}
Future<void> _getPrefs() async{
sharedPrefs = await SharedPreferences.getInstance();
}
You can do it in initState() and after this call setState() to update your build() method. Other way is to use FutureBuilder()
SharedPreferences sharedPrefs;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((prefs) {
setState(() => sharedPrefs = prefs);
});
}
I suggest you use GetStorage
https://pub.dev/packages/get_storage
you will be able to get values without awaiting for them
main.dart
await GetStorage.init();
runApp(MyApp());
storage.dart
static bool isFirstOpen() {
final box = GetStorage();
return box.read("isFirstOpen") ?? true;
}
static setFirstOpen(bool isopen) {
final box = GetStorage();
box.write("isFirstOpen", isopen);
}
You can do something like this
Create a separate shared preferences service
Take in the SharedPreferences value in the constructor
You can then do get call without async, await
Example
class SharedPreferencesService {
final SharedPreferences _prefs;
const SharedPreferencesService(this._prefs);
Future<void> setString(String key, String value) async {
await _prefs.setString(key, value);
}
String? getString(String key) {
if (containsKey(key)) {
return _prefs.getString(key);
} else {
return null;
}
}
bool containsKey(String key) {
return _prefs.containsKey(key);
}
}
The only async call in SharedPreferences is to get the initial instance. This can be reused across your application. main can be async. So you can just await the SharedPreferences instance in main:
late SharedPreferences prefs;
main() async {
prefs = await SharedPreferences.getInstance();
runApp(App());
}
Now you can use prefs anywhere without resorting to async code. SharedPreferences will serve as a nonblocking write-through cache, with write operations running asynchronously in the background.
You can call a function on initState() and save SharedPreferences in a variable, so you can use in a non-async method, like this:
SharedPreferences prefs;
Future<void> loadPrefs() async {
prefs = await SharedPreferences.getInstance();
}
#override
void initState(){
super.initState();
loadPrefs();
}

Flutter set startup page based on Shared Preference

I've been trying without success to load different pages according to my Shared Preference settings.
Based on several posts found in stackoverflow, i end up with the following solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:testing/screens/login.dart';
import 'package:testing/screens/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget page = Login();
Future getSharedPrefs() async {
String user = Preferences.local.getString('user');
if (user != null) {
print(user);
this.page = Home();
}
}
#override
void initState() {
super.initState();
this.getSharedPrefs();
}
#override
Widget build(BuildContext context) {
return MaterialApp(home: this.page);
}
}
class Preferences {
static SharedPreferences local;
/// Initializes the Shared Preferences and sets the info towards a global variable
static Future init() async {
local = await SharedPreferences.getInstance();
}
}
The variable user is not null because the print(user) returns a value as expected, but the login screen is always being opened.
Your problem is that your build method returns before your getSharedPrefs future is complete. The getSharedPrefs returns instantly as soon as it's called because it's async and you're treating it as a "Fire and Forget" by not awaiting. Seeing that you can't await in your initState function that makes sense.
This is where you want to use the FutureBuilder widget. Create a Future that returns a boolean (or enum if you want more states) and use a future builder as your home child to return the correct widget.
Create your future
Future<bool> showLoginPage() async {
var sharedPreferences = await SharedPreferences.getInstance();
// sharedPreferences.setString('user', 'hasuser');
String user = sharedPreferences.getString('user');
return user == null;
}
When user is null this will return true. Use this future in a Future builder to listen to the value changes and respond accordingly.
#override
Widget build(BuildContext context) {
return MaterialApp(home: FutureBuilder<bool>(
future: showLoginPage(),
builder: (buildContext, snapshot) {
if(snapshot.hasData) {
if(snapshot.data){
// Return your login here
return Container(color: Colors.blue);
}
// Return your home here
return Container(color: Colors.red);
} else {
// Return loading screen while reading preferences
return Center(child: CircularProgressIndicator());
}
},
));
}
I ran this code and it works fine. You should see a blue screen when login is required and a red screen when there's a user present. Uncomment the line in showLoginPage to test.
There is a much pretty way of doing this.
Assuming that you have some routes and a boolean SharedPreference key called initialized.
You need to use the WidgetsFlutterBinding.ensureInitialized() function before calling runApp() method.
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
The SharedPreference Class Code :
class SharedPreferencesClass {
static Future restore(String key) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
return (sharedPrefs.get(key) ?? false);
}
static save(String key, dynamic value) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
if (value is bool) {
sharedPrefs.setBool(key, value);
} else if (value is String) {
sharedPrefs.setString(key, value);
} else if (value is int) {
sharedPrefs.setInt(key, value);
} else if (value is double) {
sharedPrefs.setDouble(key, value);
} else if (value is List<String>) {
sharedPrefs.setStringList(key, value);
}
}
}

only static members can be accessed in initializers

I have an int value passed from the first screen to the second screen and I can have my value without any problem...My problem is that I want to add my recieved value to astring to complete and start working with my api which reqires to add the imported value of the first screen...Iam trapped between the recieved int which I can not change it to a static, and the api string which reqires the added value to be a static
the second Screen:
class CatsNews extends StatefulWidget {
#override
_CatsNewsState createState() => new _CatsNewsState();
}
class _CatsNewsState extends State<CatsNews> {
int _id ;
String root = "http://api.0mr.net";
String url = "/api/GetCategoryById/$_id";
#override
List data;
Future<String> getDrawerItem() async {
var response = await http.get(
Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
var respondbody = json.decode(response.body);
data = respondbody["StoriesCategory"];
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cat Screen'),
),
body: Center()
I get the value through the shared preferences and it works fine if it was inside Widget build(BuildContext context)
#override
void initState() {
getIntPrefrence().then(updatId);
super.initState();
this.getDrawerItem();
}
void updatId(int id) {
setState(() {
this._id = id;
});
}
}
UPDATE:
I have added the Srting url to the initstate() and the code is as foloows:
class _CatsNewsState extends State<CatsNews> {
#override
List data;
int _id ;
var response;
String root = "http://-api.0mr.net";
String url ;
Future<String> getDrawerItem() async {
response = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
var respondbody = json.decode(response.body);
data = respondbody["StoriesCategory"];
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$_id'),
),
body: Center()
}
Future<int> getIntPrefrence() async {
SharedPreferences pref = await SharedPreferences.getInstance();
int id = pref.getInt("id");
return id;
}
#override
void initState() {
super.initState();
getIntPrefrence().then(updatId);
updateurl();
this.getDrawerItem();
//initResponse();
}
updateurl() {
setState(() {
this.url= "$root/api/CategoryId/$_id";
});
}
void updatId(int id) {
setState(() {
this._id = id;
});
}
}
the previous issue was solved by adiing the String to the initstate(), but the updated values does not add to the String and deals witht the imported int id as null or zero,however the int id works fine and shows the imported value in any widget inside the Widget build(BuildContext context)
The offending code is probably
var response = await http.get(...
You can't have arbitrary code in a field initializer which is the part after = in above code.
Dart has a strict order in object creation steps.
Field initializers are executed before the constructor initialization list and before the constructor initialization list of super classes.
Only after all the constructor initialization lists of all super classes are executed, the constructor bodies are executed and only from then on is it allowed to access this, the instance of your class being created.
Your code accesses this (implicitly) before that and at this point object initialization isn't done and therefore access to this prohibited to prevent undefined bahavior.
Static members are safe to access, because they don't depend on object initialization. They are ready to use without an instance entirely.
What the error message is telling you is that your initializer is trying to do things that are not possible at this point and you need to move the code somewhere else.
The field initialization code can be moved to the constructor initialization list. This is usually done if the field is supposed to be final,
or to the constructor body, or to a method - as shown below.
var response;
#override
void initState() {
super.initState();
_initResponse();
}
void _initResponse() async {
response = await http.get(
Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
var respondbody = json.decode(response.body);
data = respondbody["StoriesCategory"];
});
}
Because initState does not allow async we move the code to another method (_initResponse) that we call from initState.
the solution of my problem was adding both the imported value and the String which I want to edit according to it inside the method which will start my api request as follows:
void initState() {
super.initState();
this.getDrawerItem();
}
Future<String> getDrawerItem() async {
int _id ;
String url;
SharedPreferences pref = await
SharedPreferences.getInstance();
_id = (pref.getInt("id")?? 0);
url = "http://gomhuriaonline-api.0mr."
"net/api/GetCategoryById/$_id";
print(url);
response = await http
.get(Uri.encodeFull(url), headers:
{"Accept": "application/json"});
setState(() {
var respondbody = json.decode(response.body);
data = respondbody["StoriesCategory"];
});
}
}

Resources