Blocking ads (pop ups or that redirects to other websites) in flutter in app webview plugin - webview

i am making an app that contains a button which when pressed will open a website that streams video . i have used flutter inappwebview plugin and i want to use content blockers too in my code.after searching i got some codes but i am getting errors that says some part in my code isnt define.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
final Uri _url = Uri.parse('https://zoro.to');
Future<void> _launchUrl() async {
if (!await launchUrl(_url)) {
throw Exception('Could not launch $_url');
}
}
// Future main() async {
// WidgetsFlutterBinding.ensureInitialized();
//
// if (Platform.isAndroid) {
// await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
// }
//
// runApp(new animflix());
// }
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
if (!kIsWeb &&
kDebugMode &&
defaultTargetPlatform == TargetPlatform.android) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(
kDebugMode);
}
runApp(const MaterialApp(home: animflix()));
}
// void main() {
// runApp(const animflix());
// }
class animflix extends StatelessWidget {
const animflix({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'anime',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: anime(),
);
}
}
class anime extends StatefulWidget {
const anime({Key? key}) : super(key: key);
#override
State<anime> createState() => _animeState();
}
class _animeState extends State<anime> {
final GlobalKey webViewKey = GlobalKey();
final adUrlFilters = [
".*.doubleclick.net/.*",
".*.ads.pubmatic.com/.*",
".*.googlesyndication.com/.*",
".*.google-analytics.com/.*",
".*.adservice.google.*/.*",
".*.adbrite.com/.*",
".*.exponential.com/.*",
".*.quantserve.com/.*",
".*.scorecardresearch.com/.*",
".*.zedo.com/.*",
".*.adsafeprotected.com/.*",
".*.teads.tv/.*",
".*.outbrain.com/.*"
];
final List<ContentBlocker> contentBlockers = [];
var contentBlockerEnabled = true;
InAppWebViewController? webViewController;
#override
void initState() {
super.initState();
// for each ad URL filter, add a Content Blocker to block its loading
for (final adUrlFilter in adUrlFilters) {
contentBlockers.add(ContentBlocker(
trigger: ContentBlockerTrigger(
urlFilter: adUrlFilter,
),
action: ContentBlockerAction(
type: ContentBlockerActionType.BLOCK,
)));
}
// apply the "display: none" style to some HTML elements
contentBlockers.add(ContentBlocker(
trigger: ContentBlockerTrigger(
urlFilter: ".*",
),
action: ContentBlockerAction(
type: ContentBlockerActionType.CSS_DISPLAY_NONE,
selector: ".banner, .banners, .ads, .ad, .advert")));
}
adblock() async {
contentBlockerEnabled = !contentBlockerEnabled;
if (contentBlockerEnabled) {
await webViewController?.setSettings(
settings: InAppWebViewSettings(contentBlockers: contentBlockers));
} else {
await webViewController?.setSettings(
settings: InAppWebViewSettings(contentBlockers: []));
}
webViewController?.reload();
setState(() {});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.pink,
),
home: Scaffold(
appBar: AppBar(title: Text("Anime market")),
body: Center(
child: Column(
children: <Widget>[
Container(
child: ElevatedButton(
onPressed: () {
setState(() {
InAppWebView(
key: webViewKey,
initialUrlRequest: URLRequest(
url: Uri.parse('https://www.tomshardware.com/')),
initialData: InAppWebViewSettings(
contentBlockers: contentBlockers),
onWebViewCreated: (controller) {
webViewController = controller;
},
);
});
},
child: Text("sflix"),
),
)
],
)),
),
);
}
}
"InAppWebviewSettings" throughs an error which says it isnt defined.

Related

How to add a splashscreen to a Flutter webview app?

**Hi guys, how can I add a splashscreen to this webview Flutter app.
Because I would like to upload it to App Store.
I uploaded this code to google Store and it has been accepted.
I am very new to flutter and dont have an experince, so please rewrite the code you would tell me to be sure that the answered code is working.
Thank you in advance.
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'My Website',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: 'My Website',
url: 'https://www.???.com/'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title, this.url});
final String title;
final String url;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
//Make sure this function return Future<bool> otherwise you will get an error
Future<bool> _onWillPop(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
return Future.value(true);
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onWillPop(context),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: WebView(
key: UniqueKey(),
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.future.then((value) => _controller = value);
_controllerCompleter.complete(webViewController);
},
javascriptMode: JavascriptMode.unrestricted,
initialUrl: widget.url,
)),
),
);
}
}
you can create a widget that displays a splash screen and hold the widget for some seconds then push it to your new widget, like this:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'My Website',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: 'My Website',
url: 'https://www.google.com/',
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title, this.url});
final String title;
final String url;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool loading = true;
WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
//Make sure this function return Future<bool> otherwise you will get an error
Future<bool> _onWillPop(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
return Future.value(true);
}
}
startSplashScreen() async {
var duration = const Duration(seconds: 3);
return Timer(
duration,
() {
setState(() {
loading = false;
});
},
);
}
#override
void initState() {
super.initState();
startSplashScreen();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onWillPop(context),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: loading == true
? Center(
child: Text(
'APP LOGO',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
)
: SafeArea(
child: WebView(
key: UniqueKey(),
initialUrl: widget.url,
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.complete(webViewController);
},
javascriptMode: JavascriptMode.unrestricted,
),
),
),
);
}
}
result:
use this package
Add this code before navigating to your homescreen
new SplashScreen(
seconds: 14,
navigateAfterSeconds: HomeScreen(),
title: Text('Welcome In SplashScreen'),
image: Image.asset('splash.png'),
backgroundColor: Colors.white,
photoSize: 100.0,
loaderColor: Colors.red
);
EDIT for example:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'My Website',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SplashScreen(
seconds: 5,
navigateAfterSeconds: MyHomePage(
title: 'My Website',
url: 'https://www.???.com/'),
title: Text('Welcome In SplashScreen'),
backgroundColor: Colors.white,
loaderColor: Colors.red
),
);
}
}

setState doesn't update the interface

I'm trying to learn flutter but his has been in my way for over a week, I'm not able to get setState to work properly.
In this case I want to press a button and change its icon and properties, basically having another element but I just can't get it to work.
Here's my code for the widget:
import 'package:flutter/material.dart';
class UserButton extends StatefulWidget {
#override
_UserButtonState createState() => _UserButtonState();
}
class _UserButtonState extends State<UserButton> {
#override
Widget build(BuildContext context) {
bool loggedin = false;
return Container(
child: loggedin
? IconButton(
onPressed: () {
setState(() {
loggedin = false;
});
},
icon: Icon(Icons.person),
)
: IconButton(
onPressed: () {
setState(() {
loggedin = true;
});
},
icon: Icon(Icons.person_outline),
tooltip: "Login",
));
}
}
And here is the main app code:
import 'package:flutter/material.dart';
import 'package:orar/user_button.dart';
main(List<String> args) {
runApp(Home());
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme:
ThemeData(primaryColor: Colors.cyan, accentColor: Colors.cyanAccent),
home: Scaffold(
appBar: AppBar(
title: Text("test"),
actions: <Widget>[UserButton()],
),
body: ListView(
children: <Widget>[],
),
),
);
}
}
loggedin should be state variable. In your case it is local variable inside build method.
Move loggedin out of build method and it should work

Flutter, calling FutureBuilder from a raised button's onPressed doesn't call the builder property

I'm trying to learn Dart/Flutter and am working on an example where there's a button on the app that says "Get Data", and when I touch it I want to retrieve JSON data from a restful service.
I see the web service being called in fetchPost, but the builder property of the FutureBuilder isn't called.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'ResultsList.dart';
import 'dart:convert';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Restul Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: (){
FutureBuilder<ResultsList>(
future: fetchPost(),
builder: (context, snapshot){
print('In Builder');
}
);
},
child: Text('Get data'),
)
],
),
)
);
}
}
Future<ResultsList> fetchPost() async {
final response = await http.get('http://mywebserviceurl');
if (response.statusCode == 200){
print('Received data');
return ResultsList.fromJson(json.decode(response.body));
}
else {
throw Exception('Failed to load data');
}
}
Interestingly though, if I move the FutureBuilder out of the onPressed of the button to the child of Center, I do see the builder property getting called.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'ResultsList.dart';
import 'dart:convert';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Restul Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder<ResultsList>(
future: fetchPost(),
builder: (context, snapshot){
print ('In Builder');
return Container();
}
)
)
);
}
}
Future<ResultsList> fetchPost() async {
final response = await http.get('http://mywebserviceurl');
if (response.statusCode == 200){
print('Received data');
return ResultsList.fromJson(json.decode(response.body));
}
else {
throw Exception('Failed to load data');
}
}
Obviously I'm missing something, but any idea what I'm doing wrong?
If you want to get some data from request - you don't need FutureBuilder. You can do:
RaisedButton(
onPressed: (){
fetchPost().then((result) {
print('In Builder');
})
},
child: Text('Get data'),
)
or
RaisedButton(
onPressed: () async {
var result = await fetchPost()
print('In Builder');
},
child: Text('Get data'),
)
The onPressed method in this RaisedButton is actually not doing anything. It just creates a new FutureBuilder which does nothing but existing^^ It's like you would just call 1+1;, which just creates a value, but that value is not used to do anything.
RaisedButton(
onPressed: (){
FutureBuilder<ResultsList>(
future: fetchPost(),
builder: (context, snapshot){
print('In Builder');
}
);
},
child: Text('Get data'),
)
You could have body be assigned to a Widget(which could just be called body or whatever you want^^), which you then change in a setState((){body = FutureBuilder(/*...*/}); call.
For me FutureBuilder not working in onPresses...
I used this way :
I defined a variable in state:
bool visiblity = false;
and I used this code in build:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
visiblity=true;
fetchPost();
},
child: Text('Get data'),
),
FutureBuilder<ResultsList>(
future: ("Your View Model that return from call back"),
builder: (context, snapshot) {
if (visiblity) {
print('In Builder');
visiblity=false;
} else
return Container();
}
),
],
),
)
);
}
I didn't put FutureBuilder in onPressed. I put that in body and changed visibility after return result.

How to write a double back button pressed to exit app using flutter

I'm new to flutter, and I saw many android apps can exit when double press back button.
The first time press back button, app shows a toast"press again to exit app".
The following second press, app exits.
Of course, the time between two press must be not long.
How to do it in flutter?
This is an example of my code (I've used "fluttertoast" for showing toast message, you can use snackbar or alert or anything else)
DateTime currentBackPressTime;
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: WillPopScope(child: getBody(), onWillPop: onWillPop),
);
}
Future<bool> onWillPop() {
DateTime now = DateTime.now();
if (currentBackPressTime == null ||
now.difference(currentBackPressTime) > Duration(seconds: 2)) {
currentBackPressTime = now;
Fluttertoast.showToast(msg: exit_warning);
return Future.value(false);
}
return Future.value(true);
}
You can try this package.
Inside a Scaffold that wraps all your Widgets, place the DoubleBackToCloseApp passing a SnackBar:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DoubleBackToCloseApp(
child: Home(),
snackBar: const SnackBar(
content: Text('Tap back again to leave'),
),
),
),
);
}
}
The solution below must be considered deprecated because it causes a few issues that were tackled in the package mentioned. For instance, the app closes if the snack bar was dismissed by the user (see hcbpassos/double_back_to_close_app#2).
Old answer
You can also opt for a solution involving SnackBar. It's not as simple as Andrey Turkovsky's answer, but it's quite more elegant and you won't depend on a library.
class _FooState extends State<Foo> {
static const snackBarDuration = Duration(seconds: 3);
final snackBar = SnackBar(
content: Text('Press back again to leave'),
duration: snackBarDuration,
);
DateTime backButtonPressTime;
#override
Widget build(BuildContext context) {
return Scaffold(
// The BuildContext must be from one of the Scaffold's children.
body: Builder(
builder: (context) {
return WillPopScope(
onWillPop: () => handleWillPop(context),
child: Text('Place your child here'),
);
},
),
);
}
Future<bool> handleWillPop(BuildContext context) async {
final now = DateTime.now();
final backButtonHasNotBeenPressedOrSnackBarHasBeenClosed =
backButtonPressTime == null ||
now.difference(backButtonPressTime) > snackBarDuration;
if (backButtonHasNotBeenPressedOrSnackBarHasBeenClosed) {
backButtonPressTime = now;
Scaffold.of(context).showSnackBar(snackBar);
return false;
}
return true;
}
}
Unfortunately none of them worked for me, I have written one generic class (widget) to handle double tap exit. If someone is interested
class DoubleBackToCloseWidget extends StatefulWidget {
final Widget child; // Make Sure this child has a Scaffold widget as parent.
const DoubleBackToCloseWidget({
#required this.child,
});
#override
_DoubleBackToCloseWidgetState createState() =>
_DoubleBackToCloseWidgetState();
}
class _DoubleBackToCloseWidgetState extends State<DoubleBackToCloseWidget> {
int _lastTimeBackButtonWasTapped;
static const exitTimeInMillis = 2000;
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
#override
Widget build(BuildContext context) {
if (_isAndroid) {
return WillPopScope(
onWillPop: _handleWillPop,
child: widget.child,
);
} else {
return widget.child;
}
}
Future<bool> _handleWillPop() async {
final _currentTime = DateTime.now().millisecondsSinceEpoch;
if (_lastTimeBackButtonWasTapped != null &&
(_currentTime - _lastTimeBackButtonWasTapped) < exitTimeInMillis) {
Scaffold.of(context).removeCurrentSnackBar();
return true;
} else {
_lastTimeBackButtonWasTapped = DateTime.now().millisecondsSinceEpoch;
Scaffold.of(context).removeCurrentSnackBar();
Scaffold.of(context).showSnackBar(
_getExitSnackBar(context),
);
return false;
}
}
SnackBar _getExitSnackBar(
BuildContext context,
) {
return SnackBar(
content: Text(
'Press BACK again to exit!',
color: Colors.white,
),
backgroundColor: Colors.red,
duration: const Duration(
seconds: 2,
),
behavior: SnackBarBehavior.floating,
);
}
}
Use this class following way:
class Dashboard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: DoubleBackToCloseWidget(
child: Container(
child: Column(
children: [
const Text('Hello there'),
const Text('Hello there again'),
],
),
),
),
),
);
}
}
The first time press back button, app shows a AlertDialog"press yes to exit app and press No to can't exit application".
This is an example of my code (I've used 'AlertDialog')
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onBackPressed,
child: DefaultTabController(
initialIndex: _selectedIndex,
length: choices.length,
child: Scaffold(
appBar: AppBar(
),
),
),
);
}
Future<bool> _onBackPressed() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Are you sure?'),
content: Text('Do you want to exit an App'),
actions: <Widget>[
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop(false);
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop(true);
},
)
],
);
},
) ?? false;
}
This is my answer. I used AlertDialog() to achieve this
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onBackPressed,
child: Scaffold(
appBar: AppBar(),
body: Container(),
),
);
}
Future<bool> _onBackPressed() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Confirm'),
content: Text('Do you want to exit the App'),
actions: <Widget>[
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop(false); //Will not exit the App
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop(true); //Will exit the App
},
)
],
);
},
) ?? false;
}
Simply use double_back_to_close_app library
https://pub.dev/packages/double_back_to_close_app
Add double_back_to_close_app under dependencies in pubspec.yaml file
dependencies:
double_back_to_close_app: ^1.2.0
Here example code
import 'package:double_back_to_close_app/double_back_to_close_app.dart';
import 'package:flutter/material.dart';
void main() => runApp(Example());
class Example extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DoubleBackToCloseApp(
snackBar: const SnackBar(
content: Text('Tap back again to leave'),
),
child: Center(
child: OutlineButton(
child: const Text('Tap to simulate back'),
// ignore: invalid_use_of_protected_member
onPressed: WidgetsBinding.instance.handlePopRoute,
),
),
),
),
);
}
}
Just move your body contents to "DoubleBackToCloseApp's" child
The best solution without using a package use System
SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop');
or
SystemNavigator.pop();
Full Code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ExitApp extends StatefulWidget {
final Widget child;
const ExitApp({
Key? key,
required this.child,
}) : super(key: key);
#override
_ExitAppState createState() => _ExitAppState();
}
class _ExitAppState extends State<ExitApp> {
#override
build(BuildContext context) {
DateTime timeBackPressed = DateTime.now();
return WillPopScope(
child: widget.child,
onWillPop: () async {
final differeance = DateTime.now().difference(timeBackPressed);
timeBackPressed = DateTime.now();
if (differeance >= Duration(seconds: 2)) {
final String msg = 'Press the back button to exit';
Fluttertoast.showToast(
msg: msg,
);
return false;
} else {
Fluttertoast.cancel();
SystemNavigator.pop();
return true;
}
},
);
}
}
https://pub.dev/packages/flutter_close_app
This is my solution, it is very flexible and simple, does not depend on routing navigation, any page can close the App, such as my login page, and if it is Drawer and PageView, it can also flexibly support custom conditions, and does not need to rely on native method. The following functions are supported:
✅ Press back 2 times to close app
✅ Custom time interval
✅ Customize the prompt message
✅ Customize matching conditions
✅ Support Android
✅ One click to close app
✅ Support iOS
✅ Support MacOS
✅ Support Windows
✅ Support Linux
Easy to Use and Understand, double tap to exit;
Change the duration to 10000, and short toast message time;
import 'dart:io';
bool back = false;
int time = 0;
int duration = 1000;
Future<bool> willPop() async{
int now = DateTime.now().millisecondsSinceEpoch;
if(back && time >= now){
back = false;
exit(0);
}else{
time = DateTime.now().millisecondsSinceEpoch+ duration;
print("again tap");
back = true;
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Press again the button to exit")));
}
return false;
}
return WillPopScope(
onWillPop: onWill,
child: Scaffold()
);
If you want a snackbar you should provide a scaffold key as it's related to a scaffold, so this key should make the trick of calling a snackbar outside of it's scaffold parent.
Here is a solution :
class Home extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async{
DateTime initTime = DateTime.now();
popped +=1;
if(popped>=2) return true;
await _scaffoldKey.currentState.showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
content: Text('Tap one more time to exit.',textAlign: TextAlign.center,),
duration: Duration(seconds: 2),
)).closed;
// if timer is > 2 seconds reset popped counter
if(DateTime.now().difference(initTime)>=Duration(seconds: 2)) {
popped = 0;
}
return false;
},
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(title : Text("Demo")),
body: Text("body")
);
)
}
This is my solution, you can change backPressTotal value to the number of pressed you want!
int backPressCounter = 0;
int backPressTotal = 2;
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: WillPopScope(child: getBody(), onWillPop: onWillPop),
);
}
Future<bool> onWillPop() {
if (backPressCounter < 2) {
Fluttertoast.showToast(msg: "Press ${backPressTotal - backPressCounter} time to exit app");
backPressCounter++;
Future.delayed(Duration(seconds: 1, milliseconds: 500), () {
backPressCounter--;
});
return Future.value(false);
} else {
return Future.value(true);
}
}
If the condition is that the user presses only twice, you can use the first solution of course.
If you want to increase the number of times you click, you can use this solution. Where the user has to press 3 times within two seconds so he can get out
DateTime currentBackPressTime;
/// init counter of clicks
int pressCount=1;
then :
Future<bool> onWillPop() async {
DateTime now = DateTime.now();
/// here I check if number of clicks equal 3
if(pressCount!=3){
///should be assigned at the first click.
if(pressCount ==1 )
currentBackPressTime = now;
pressCount+=1;
return Future.value(false);
}else{
if (currentBackPressTime == null ||
now.difference(currentBackPressTime) > Duration(seconds: 2)) {
currentBackPressTime = now;
pressCount=0;
return Future.value(false);
}
}
return Future.value(true);
}
You can look for time duration between the two consecutive back button clicks, and if the difference is within the desired duration then exit the app.
Here is the complete code sample for the counter app, which exits the app only if the difference between two consecutive back button clicks is less than 1 second (1000 ms)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void showSnackBar() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
behavior: SnackBarBehavior.floating,
duration: Duration(milliseconds: 600),
margin: EdgeInsets.only(bottom: 0, right: 32, left: 32),
content: Text('Tap back button again to exit'),
),
);
}
void hideSnackBar() {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
}
DateTime oldTime = DateTime.now();
DateTime newTime = DateTime.now();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: WillPopScope(
onWillPop: () async {
newTime = DateTime.now();
int difference = newTime.difference(oldTime).inMilliseconds;
oldTime = newTime;
if (difference < 1000) {
hideSnackBar();
return true;
} else {
showSnackBar();
return false;
}
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
DateTime BackPressTime = DateTime.now();
#override
Widget build(BuildContext context) {
return Scaffold(
body: WillPopScope(
child: Home(),
onWillPop: exiteApp,
),
);
}
Future<bool> exiteApp() {
print("exite app");
DateTime now = DateTime.now();
if(now.difference(BackPressTime)< Duration(seconds: 2)){
return Future(() => true);
}
else{
BackPressTime = DateTime.now();
Fluttertoast.showToast(msg: "Press agin");
return Future(()=> false);
}
}

Flutter WebviewPlugin remove header and footer of website

By implementation of FlutterWebviewPlugin, I want to show a particular website in a widget but without header and footer.
is this possible in Flutter?
I guess there is a function in FlutterWebviewPlugin class .evalJavascript('some code') but don't know how to use this function. can I add javascript code to this?
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
String url = "https://flutter.io/";
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Webview Example',
theme: ThemeData.dark(),
routes: {
"/": (_) => Home(),
"/webview": (_) => WebviewScaffold(
url: url,
withJavascript: true,
withLocalStorage: true,
withZoom: true,
)
},
);
}
}
class Home extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
final webView = FlutterWebviewPlugin();
TextEditingController controller = TextEditingController(text: url);
#override
void initState() {
super.initState();
webView.close();
controller.addListener(() {
url = controller.text;
});
}
#override
void dispose() {
webView.dispose();
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("WebView"),
),
body: Center(
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(10.0),
child: TextField(
controller: controller,
),
),
RaisedButton(
child: Text("Open Webview"),
onPressed: () {
Navigator.of(context).pushNamed("/webview");
},
)
],
),
)
);
}
}
I suggest using Flutter's official WebView plugin: webview_flutter
The plugin also has a method that can run Javascript using WebViewController.evaluateJavascript(String). This method is recommended to be run after WebView.onPageFinished callback.
Your WebView widget should look like this.
WebView(
initialUrl: 'https://flutter.dev',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_webViewController = webViewController;
_controller.complete(webViewController);
},
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
onPageStarted: (String url) {
print('Page started loading: $url');
},
onPageFinished: (String url) {
print('Page finished loading: $url');
// Removes header and footer from page
_webViewController
.evaluateJavascript("javascript:(function() { " +
"var head = document.getElementsByTagName('header')[0];" +
"head.parentNode.removeChild(head);" +
"var footer = document.getElementsByTagName('footer')[0];" +
"footer.parentNode.removeChild(footer);" +
"})()")
.then((value) => debugPrint('Page finished loading Javascript'))
.catchError((onError) => debugPrint('$onError'));
},
);
Here's a complete sample that you can try.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
WebViewController _webViewController;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: 'https://flutter.dev',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_webViewController = webViewController;
_controller.complete(webViewController);
},
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
javascriptChannels: <JavascriptChannel>{
_toasterJavascriptChannel(context),
},
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
onPageStarted: (String url) {
print('Page started loading: $url');
},
onPageFinished: (String url) {
print('Page finished loading: $url');
_webViewController
.evaluateJavascript("javascript:(function() { " +
"var head = document.getElementsByTagName('header')[0];" +
"head.parentNode.removeChild(head);" +
"var footer = document.getElementsByTagName('footer')[0];" +
"footer.parentNode.removeChild(footer);" +
"})()")
.then((value) => debugPrint('Page finished loading Javascript'))
.catchError((onError) => debugPrint('$onError'));
},
gestureNavigationEnabled: true,
);
}),
);
}
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toaster',
onMessageReceived: (JavascriptMessage message) {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
});
}
}
How the app looks running
_webViewController.runJavascript(
"document.getElementsByTagName('header')[0].style.display='none'");
_webViewController.runJavascript(
"document.getElementsByTagName('footer')[0].style.display='none'");
You can use the flutter_inappwebview plugin (I'm the author) and inject an UserScript at UserScriptInjectionTime.AT_DOCUMENT_START to hide or remove HTML elements when the web page loads (check JavaScript - User Scripts official docs for User Scripts details).
As I have already answered here for a similar issue, here is a code example using the current latest version 6 (6.0.0-beta.18) with URL https://getmobie.de/impressum that removes the header and footer HTML elements:
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
if (!kIsWeb &&
kDebugMode &&
defaultTargetPlatform == TargetPlatform.android) {
await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode);
}
runApp(const MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey webViewKey = GlobalKey();
InAppWebViewController? webViewController;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("InAppWebView test"),
),
body: Column(children: <Widget>[
Expanded(
child: InAppWebView(
key: webViewKey,
initialUrlRequest:
URLRequest(url: WebUri("https://getmobie.de/impressum")),
initialUserScripts: UnmodifiableListView([
UserScript(source: """
window.addEventListener('DOMContentLoaded', function(event) {
var header = document.querySelector('.elementor-location-header'); // use here the correct CSS selector for your use case
if (header != null) {
header.remove(); // remove the HTML element. Instead, to simply hide the HTML element, use header.style.display = 'none';
}
var footer = document.querySelector('.elementor-location-footer'); // use here the correct CSS selector for your use case
if (footer != null) {
footer.remove(); // remove the HTML element. Instead, to simply hide the HTML element, use footer.style.display = 'none';
}
});
""", injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START)
]),
onWebViewCreated: (controller) {
webViewController = controller;
},
),
),
]));
}
}
For your use case, use the right CSS selector inside the user script js source to correctly get and remove the header and footer HTML elements from your web page!

Resources