How to use WebView flutter to restrict Navigation to some pages - flutter

I',m trying to use webview in my flutter app to take me back to the app once they navigate to a restricted url
I already created the controller and set it in my init state
super.initState();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
SmartDialog.showLoading();
},
onPageStarted: (String url) {},
onPageFinished: (String url) {
SmartDialog.dismiss();
},
onWebResourceError: (WebResourceError error) {
SmartDialog.showToast('Something went wrong');
Navigator.pushNamedAndRemoveUntil(
context, '/landingpage', (Route<dynamic> route) => false);
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
return NavigationDecision.prevent;
} else if (request.url.endsWith('page5')) {
Navigator.pushNamedAndRemoveUntil(
context, '/landingpage', (Route<dynamic> route) => false);
} else if (request.url.startsWith(
'https://www.facebook.com/')) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) =>
verifyquickbuy(price: int.parse(widget.price))),
(Route<dynamic> route) => false);
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse('https://google.com'));
}
I tried putting a print in the onNavigationRequest to know if it print all url I visit but it only works just when the webview is activated
getcontroller();
return SafeArea(
child: WebViewWidget(
controller: controller,
//gestureRecognizers: GestureDetector,
));
}
}

Related

Error handling Flutter Web x go_router x FirebaseAuth (EmailLink)

I am trying to load make a dashboard and now developing the login site. It works that the user gets and email but when I click on the link provided in the email, the "FirebaseAuth.instance.isSignInWithEmailLink($link)" returns false, because $link is "localhost:8080/login" (the current page) instead of the link that has been sent via email.
Here is the FirebaseAuthService code:
class FirebaseAuthService implements AuthService {
FirebaseAuthService() {
_initialize();
}
Future<void> _initialize() async {
/// Set auth persistance for web so user stays signed in
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
print('debug// window.location.href: ' + window.location.href);
print('debug// Uri.base.toString(): ' + Uri.base.toString());
print('debug2// window.localStorage[email]: ' + window.localStorage['email'].toString());
/// idk man...
FirebaseAuth.instance.authStateChanges().listen((User? firebaseUser) {
if (firebaseUser == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
/// Checks if the incoming link is the OTP email link.
// if (FirebaseAuth.instance.isSignInWithEmailLink(Uri.base.toString())) {
if (FirebaseAuth.instance.isSignInWithEmailLink(window.location.href)) {
print('in method debug2// window.location.href: ' + window.location.href);
print('in method debug2// html.window.document.referrer: ' + (window.document as HtmlDocument).referrer);
print('in method debug// Uri.base.toString(): ' + Uri.base.toString());
print('in method debug2// window.localStorage[email]: ' + window.localStorage['email'].toString());
if (kDebugMode) print('Trying to sign in the user with OTP');
try {
await FirebaseAuth.instance
.signInWithEmailLink(
email: window.localStorage['email'] ?? '',
emailLink: window.location.href,
)
.timeout(const Duration(seconds: 10))
.then((value) => print('value: ${value.toString()}'));
} catch (_) {
print('Exceptino.... $_');
}
window.localStorage.remove('email');
if (kDebugMode) print('Successfully signed in the user with OTP');
}
}
#override
bool get isSignedIn => FirebaseAuth.instance.currentUser != null;
#override
Future<void> signOut() async {
await FirebaseAuth.instance.signOut().timeout(const Duration(seconds: 10));
}
}
And here is my main class where FirebaseAuthService is provided (with the provider package):
class VamosEventsDashboard extends StatelessWidget {
VamosEventsDashboard();
final GoRouter _vamosRouter = GoRouter(
debugLogDiagnostics: true,
initialLocation: EventsPage.route,
errorBuilder: (_, __) => const ErrorPage(),
routes: [
GoRoute(path: EventsPage.route, builder: (_, __) => const EventsPage()), // events
GoRoute(path: LoginPage.route, builder: (_, __) => const LoginPage()), // login
],
redirect: (BuildContext context, GoRouterState state) {
return context.watch<AuthService>().isSignedIn ? EventsPage.route : LoginPage.route; // todo change back to events
},
);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Data sources and services
Provider<OrganizationDataSource>(create: (_) => const FirestoreDataSource()),
Provider<AuthService>(create: (_) => FirebaseAuthService()),
],
child: MultiProvider(
providers: [
// View models
ChangeNotifierProvider(
create: (context) => OrganizationViewModel(organizationDataSource: context.read<OrganizationDataSource>()),
),
ChangeNotifierProvider(create: (_) => LoginViewModel()),
],
child: MaterialApp.router(
theme: vamosTheme,
routerConfig: _vamosRouter,
title: 'vamos! Events Dashboard',
),
),
);
}
}

How to redirect to the next page without pressing a button with flutter?

I want to move to the next page without clicking on button with flutter.
Juste after changing the value of a button, I have to redirect to the next page after some delay without any self interaction.
this is the code :
initialData: BluetoothDeviceState.disconnected,
builder: (c, snapshot) {
if (snapshot.data == BluetoothDeviceState.connected) {
return ElevatedButton(
child: const Text('CONNECTED'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MainScreen())),
);
}
You can try this:
if (snapshot.data == BluetoothDeviceState.connected) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => MainScreen()));
});
return Text('CONNECTED');
}
While the connection is depend on bool, you can do
if (snapshot.data == BluetoothDeviceState.connected) {
// a frame delay
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => MainScreen()));
});
}

How I can exit from next line code in flutter

I have a function:
onTap: () {
FunctionsClass.checkToken(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttackDetailScreen(
idAttack:
data[index]['id'].toString())));
},
Checktoken function:
static Future<http.Response> checkToken(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var url = kUrlAPI + 'checkToken/';
var response = await http.get(Uri.encodeFull(url),
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + prefs.getString('token'),
});
var convertDataToJson = jsonDecode(response.body);
if(convertDataToJson['code'] == 401){
Loader.hide();
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) => CustomDialog(
pressedAction: () {
Navigator.of(context).pop();
},
type: 'w',
title: kWarningTitle,
description: kGenericError,
buttonText: kCloseText,
),
);
//exit for next function
}
}
I want if convertDataToJson['code'] == 401, show dialog and not execute Navigator.
Something to not execute the next method
Return a value from your checkToken function that indicates whether the navigator should push or not. For example you can return null in case of an error or simply return boolean where true means push or false means don't push (or you could return the error string and check it inside onTap if you prefer that).
Let's assume you choose to return null if the navigator shouldn't push the page, then you can do this:
onTap: () {
FunctionsClass.checkToken(context).then((value) {
if(value == null) {
return; // don't do anything
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttackDetailScreen(idAttack: data[index]['id'].toString())));
}
}
}
Try using try/catch for unexpected error too. And 'throw' a custom error.
This is consider a good practice.
static Future<http.Response> checkToken(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var url = kUrlAPI + 'checkToken/';
try {
var response = await http.get(Uri.encodeFull(url),
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + prefs.getString('token'),
});
var convertDataToJson = jsonDecode(response.body);
if(convertDataToJson['code'] == 401){
//exit for next function
throw Exception('some error');
}
} catch (error) {
throw error;
}
}
And use ".then" and '.catchError' while calling a Future function.
onTap: () {
FunctionsClass.checkToken(context).then((_) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AttackDetailScreen(
idAttack:
data[index]['id'].toString(),
),
),
);
}).catchError((onEroor){
Loader.hide();
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) => CustomDialog(
pressedAction: () {
Navigator.of(context).pop();
},
type: 'w',
title: kWarningTitle,
description: kGenericError,
buttonText: kCloseText,
),
);
};
},

BlocProvider.of() called with a context that does not contain a Bloc of type PhoneAuthenticationBloc. Flutter

I create blocs in a MultiBlocProvider, its child is a BlocBuilder that returns a MultiBlocListener but when sending an event
BlocProvider.of<PhoneAuthenticationBloc>(context).add(VerifyPhoneNumberEvent(phoneNumber: controller.text.replaceAll(' ', '')));
I get the BlocProvider.of() called with a context that does not contain a Bloc of type PhoneAuthenticationBloc, while other blocs work fine.
Can you spot what's wrong with PhoneAuthenticationBloc()?
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<PhoneAuthenticationBloc>(
create: (context) => PhoneAuthenticationBloc(userRepository: UserRepository()),
),
// BlocProvider<AuthenticationBloc>(
// create: (context) => AuthenticationBloc(userRepository: UserRepository()),
// lazy: false,
// ),
// BlocProvider<UserBloc>(
// create: (context) => UserBloc(),
// lazy: false,
// ),
BlocProvider<BookingBloc>(
create: (context) => BookingBloc(user: widget.user),
),
BlocProvider<OrderBloc>(
create: (context) => OrderBloc(user: widget.user),
),
BlocProvider<PaymentBloc>(
create: (context) => PaymentBloc(user: widget.user),
lazy: false,
),
BlocProvider<CartBloc>(
create: (context) => CartBloc()..add(LoadCart()),
),
],
child: BlocBuilder<PaymentBloc, PaymentState>(
builder: (context, state) {
if (state is InitialStatePayment) {
return MultiBlocListener(
listeners: [
BlocListener<PhoneAuthenticationBloc, AuthenticationState>(
listener: (BuildContext context, AuthenticationState state){
...
FlatButton.icon(
color: Colors.orange,
onPressed: () {
print('pay pressed');
print(
'bookingStart is ${widget.bookingStart}, selected shop is ${widget.selectedShop}');
if (isVerified == true){
...
}
else{
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return SingleChildScrollView(
child: ValidatePhoneDialog(
controller: controller,
onPressed: (){
if (controller.text.length >= 9){
Navigator.pop(context);
showDialog(
context:context,
barrierDismissible: false,
builder: (BuildContext context){
return VerifyingDialog();
}
);
BlocProvider.of<PhoneAuthenticationBloc>(context).add(VerifyPhoneNumberEvent(phoneNumber: controller.text.replaceAll(' ', '')));
} else {
scaffoldKey.currentState.showSnackBar(SnackBar(
backgroundColor: Colors.redAccent,
content: Text(
AppLocalizations.instance
.text('Wrong number'),
style: TextStyle(color: Colors.white))));
}
}
),
);
}
);
}
},
icon: Icon(
Icons.payment,
color: Colors.white,
),
label: Text(
AppLocalizations.instance.text('Pay'),
style: TextStyle(
color: Colors.white, fontSize: 20),
)),
class PhoneAuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository _userRepository;
PhoneAuthenticationBloc({#required UserRepository userRepository})
: assert(userRepository != null),
_userRepository = userRepository;
String verificationId = "";
#override
AuthenticationState get initialState => Uninitialized();
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event) async* {
// phone verification
if (event is VerifyPhoneNumberEvent) {
print('VerifyPhoneNumberEvent received');
yield* _mapVerifyPhoneNumberToState(event);
} else if (event is PhoneCodeSentEvent) {
print('PhoneCodeSentEvent received');
yield OtpSentState();
} else if (event is VerificationCompletedEvent) {
print('VerificationCompletedEvent received');
yield VerificationCompleteState(firebaseUser: event.firebaseUser, isVerified: event.isVerified);
} else if (event is VerificationExceptionEvent) {
print('VerificationExceptionEvent received');
yield VerificationExceptionState(message: event.message);
} else if (event is VerifySmsCodeEvent) {
print('VerifySmsCodeEvent received');
yield VerifyingState();
try {
AuthResult result =
await _userRepository.verifyAndLinkAuthCredentials(verificationId: verificationId, smsCode: event.smsCode);
if (result.user != null) {
yield VerificationCompleteState(firebaseUser: result.user, isVerified: true);
} else {
yield OtpExceptionState(message: "Invalid otp!",verificationId: verificationId);
}
} catch (e) {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
print(e);
}
} else if ( event is PhoneCodeAutoRetrievalTimeoutEvent){
yield PhoneCodeAutoRetrievalTimeoutState(verificationId: event.verificationId);
}
if(event is SendVerificationCodeEvent) {
yield*_mapVerificationCodeToState(event);
}
}
Stream<AuthenticationState> _mapVerifyPhoneNumberToState(VerifyPhoneNumberEvent event) async* {
print('_mapVerifyPhoneNumberToState V2 started');
yield VerifyingState();
final phoneVerificationCompleted = (AuthCredential authCredential) {
print('_mapVerifyPhoneNumberToState PhoneVerificationCompleted');
// _userRepository.getUser();
_userRepository.getCurrentUser().catchError((onError) {
print(onError);
}).then((user) {
add(VerificationCompletedEvent(firebaseUser: user, isVerified: true));
});
};
final phoneVerificationFailed = (AuthException authException) {
print('_mapVerifyPhoneNumberToState PhoneVerificationFailed');
print(authException.message);
add(VerificationExceptionEvent(onError.toString()));
};
final phoneCodeSent = (String verificationId, [int forceResent]) {
print('_mapVerifyPhoneNumberToState PhoneCodeSent');
this.verificationId = verificationId;
add(PhoneCodeSentEvent());
};
final phoneCodeAutoRetrievalTimeout = (String verificationId) {
// after this print Bloc error is Bad state: Cannot add new events after calling close
print('_mapVerifyPhoneNumberToState PhoneCodeAutoRetrievalTimeout');
this.verificationId = verificationId;
add(PhoneCodeAutoRetrievalTimeoutEvent(verificationId: verificationId));
};
await _userRepository.verifyPhone(
phoneNumber: event.phoneNumber,
timeOut: Duration(seconds: 0),
phoneVerificationFailed: phoneVerificationFailed,
phoneVerificationCompleted: phoneVerificationCompleted,
phoneCodeSent: phoneCodeSent,
autoRetrievalTimeout: phoneCodeAutoRetrievalTimeout);
}
Stream<AuthenticationState> _mapVerificationCodeToState(SendVerificationCodeEvent event) async* {
print('_mapVerificationCodeToState started');
yield VerifyingState();
try {
AuthResult result =
await _userRepository.verifyAndLinkAuthCredentials(verificationId: verificationId, smsCode: event.smsCode);
if (result.user != null) {
yield VerificationCompleteState(firebaseUser: result.user, isVerified: true);
} else {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
}
} catch (e) {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
print(e);
}
}
}
You are using the wrong context when adding the event.
When showing the dialog the widget will be placed in an overlay which is above the bloc provider, so by using the context of the dialog you cannot find the bloc since there is no provider above it.
To fix this name the context of the dialog something else (ie. dialogContext) such that when doing BlocProvider.of(context) the context refers to the context of the widget showing the dialog instead of the context of the dialog itself.

Flutter qr_code_scanner on successfully scanned data update navigation?

This is a widget, that have function that triggers each time we scan qr code.
import 'package:qr_code_scanner/qr_code_scanner.dart';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
flex: 4,
child: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: 300,
),
),
),
In function I want to navigate to next screen.
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
});
}
Problem here is that that listen event trigger many times, is it possible to stop this function after first successfully scan data? I try with
controller.scannedDataStream.first;
But that return empty string and not triggers when real data are scanned.
I need to click 40 times to go back from Second Route to return to QR scanner widget.
Thanks!
for future reference i found pausing the camera works better :)
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
qrText = scanData;
SecondPageRoute();
});
}
SecondPageRoute() async {
controller?.pauseCamera();
var value = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return SecondPage(qrText);
})).then((value) => controller.resumeCamera());
}
If someone still has this problem, try this solution:) It works well for me!
onQRViewCreated: (QRViewController qrViewController) {
this.qrViewController = qrViewController;
qrViewController.scannedDataStream.listen((qrData) {
qrViewController.pauseCamera();
final String qrCode = qrData.code;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
qrCode: '$qrCode',
))).then((value) => qrViewController.resumeCamera());
});
}
You can just add a sentinel
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
bool scanned = false;
controller.scannedDataStream.listen((scanData) {
if (!scanned) {
scanned = true;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
});
}
I've faced the same issue, With lot of trial and error, found the solution qrViewController.dispose(). It's below.
onQRViewCreated: (QRViewController qrViewController) {
this.qrViewController = qrViewController;
qrViewController.scannedDataStream.listen((qrData) {
final String qrCode = qrData.code;
qrViewController.dispose();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
qrCode: '$qrCode',
))).then((value) => qrViewController.resumeCamera());
});
}