Flutter How to make it automatically - (navigate to login) when http status code is 401 - dart

Here is my previous codes.
class mainScreen extends StatefulWidget
{
Api api = new Api();
override
Widget build(BuildContext context) {
return new MaterialButton(
onPressed: () async{
new Future.then(
api.doSthA(),onError: (e) {
Navigator.of(context).pushNamed("/login"); //this is repeat
})
new Future.then(
api.doSthB(),onError: (e) {
Navigator.of(context).pushNamed("/login"); //this is repeat
}
)
}
);
}
}
class Api
{
Future<dynamic> doSthA() async{
return http
.post(
"url"
)
.then((http.Response res) {
if(res.body.statusCode == 401){
throw new Exception("401");
}else{
return _decoder.convert(res);
}
}
}
Future<dynamic> doSthB() async{
similar with doSthA
}
}
I want it simplify to
new MaterialButton(
onPressed: () async{
new Future.then(api.doSthA())...
new Future.then(api.doSthB())...
}
auto execute Navigator to login when api return 401.
Because Navigator must need widget's content.so I have no idea how to let it integrate with Api class.I want make Navigator be part of Api manager.

Try this when you want to navigate to login
Navigator.pushNamedAndRemoveUntil(
context,
'/login', (_) => false);
Put this in build method
Api api = Api(context: context)
Api class
class Api
{
BuildContext context;
Api({this.context});
}
ApiCall Method
if(res.statusCode == 401){
Navigator.pushNamedAndRemoveUntil(
context,
'/login', (_) => false);
return null;
}

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 Shared Preferences Auth FIle

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.

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

How to navigate different page after async method return response

I have a screen App in which i have onGenerateRoute property of MaterialApp. In the routes method i make an api call and once i get the response i want to let user navigate to login screen
I tried calling my widget Login inside .then() function
class App extends StatelessWidget {
Widget build(BuildContext context) {
return AppBlocProvider(
child: LoginBlocProvider(
child: MaterialApp(
onGenerateRoute: routes,
),
),
);
}
Route routes(RouteSettings settings) {
print(settings.name);
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) {
//HERE I AM MAKING API CALL
final appBloc = AppBlocProvider.of(context);
appBloc.verifyUser().then((response) {
//HERE ONCE I GET THE RESPONSE I WANT TO NAVIGATE USER TO
//lOGIN ACTIVITY
print('called');
return Login();
});
return AppBlocProvider(
child: Center(child: CircularProgressIndicator()),
);
});
break;
case '/Login':
return MaterialPageRoute(builder: (BuildContext context) {
return Login();
});
break;
case '/HomeScreen':
return MaterialPageRoute(builder: (BuildContext context) {
return Home();
});
break;
}
return MaterialPageRoute(builder: (context) {
print('returned null');
});
}
api call get successful and even .then() method executes but login screen doesn't appear
The reason return Login(); doesn't do anything was because another return has been executed already: return AppBlocProvider(child: Widget());
Similar to this sample, since a return has been already made, the other return won't do anything. The sample prints 'bar', and 'foo' was never printed using print(bar());.
void main() {
print(bar());
}
Future<String> foo() async{
await Future.delayed(Duration(seconds: 5));
return 'foo';
}
String bar(){
String txt = 'bar';
foo().then((String value){
print('Future finished: $value');
// Since print already got a String return,
// returning this value won't do anything
return value; // 'foo' won't be printed on main()
});
return txt;
}
You may want to consider moving the navigation inside Login and also display CircularProgressIndicator() there.

Resources