Close keyboard when tap outside the WebView input in flutter - flutter

I'm working on this for more than 4hrs and am unable to make it through. What I'm trying to do is hide the keyboard when tapping outside the input focus area. Somehow the keyboard is unable to hide.
I tried all the possible answers on StackOverflow but was unable to find my solution.
Output: Keyboard persist even after search on google input
My Approach
// ignore_for_file: prefer_const_constructors
// ignore: use_key_in_widget_constructors
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter/services.dart';
void main(){
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Color(0xff1e2229)
));
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body:WebViewClass()
),
);
}
}
class WebViewClass extends StatefulWidget {
WebViewState createState() => WebViewState();
}
class WebViewState extends State<WebViewClass> {
bool isLoading = false;
final key = UniqueKey();
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: Scaffold(
appBar: null,
body: SafeArea(
child: IgnorePointer(
ignoring: isLoading,
child: Stack(
children: [
WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
key: key,
onPageFinished: (value) {
setState(() {
isLoading = false;
});
},
onPageStarted: (value) {
setState(() {
isLoading = true;
});
},
),
isLoading ? Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.grey.withOpacity(0.5),
child: const Center(child: CircularProgressIndicator()) ,
) : Container(),
],
),
)),));
}
}
It would be great if someone guide me on a way to resolve this issue.

Please use this code in initstate of stateful widget.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();

According to the new flutter webview documentation:
Putting this piece of code inside the given full example will solve the keyboard dismiss the issue. Thanks to #khunt arpit
#override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
Full example code:
import 'dart:io';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewExample extends StatefulWidget {
#override
WebViewExampleState createState() => WebViewExampleState();
}
class WebViewExampleState extends State<WebViewExample> {
#override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
#override
Widget build(BuildContext context) {
return WebView(
initialUrl: 'https://flutter.dev',
);
}
}

Related

how to unfocus texfield and hide keybaord on paste flutter

I have a textfield on which i mostly paste content so i want to unfocus textfield and hide keybaord on paste so i have achive to handle on paste using textfield selectionControls but the problem is focusing and keybaord which is reopening i have tired all focus methods to unfocus here is my code
import 'package:flutter/material.dart';
main() => runApp(const App());
class App extends StatelessWidget {
const App({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(home: Home());
}
}
class Home extends StatelessWidget {
const Home({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8),
child: Center(
child: TextField(
selectionControls: MySelectionControls(
onPaste: () {
print('onPaste');
// FocusManager.instance.primaryFocus?.unfocus();
// Focus.of(context).unfocus();
// FocusScope.of(context).unfocus();
// FocusScope.of(context).requestFocus(FocusNode());
// FocusScopeNode currentFocus = FocusScope.of(context);
// if (!currentFocus.hasPrimaryFocus) {
// currentFocus.focusedChild?.unfocus();
// }
},
),
),
),
),
),
);
}
}
class MySelectionControls extends MaterialTextSelectionControls {
final Function onPaste;
MySelectionControls({required this.onPaste});
#override
Future<void> handlePaste(TextSelectionDelegate delegate) {
onPaste();
return super.handlePaste(delegate);
}
}
Try this one
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
const Home({key});
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
TextSelectionControls? _textSelectionControls;
#override
void initState() {
// TODO: implement initState
super.initState();
_textSelectionControls = MySelectionControls(onPaste: onPaste);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8),
child: Center(
child: Column(
children: [
TextField(
selectionControls: _textSelectionControls,
),
],
),
),
),
),
);
}
Future<void> onPaste(final TextSelectionDelegate? delegate) async {
Future.delayed(Duration(milliseconds: 100), () {
FocusScope.of(context).requestFocus(FocusNode());
});
}
}
class MySelectionControls extends MaterialTextSelectionControls {
MySelectionControls({required this.onPaste});
ValueChanged<TextSelectionDelegate> onPaste;
#override
Future<void> handlePaste(TextSelectionDelegate delegate) async {
onPaste(delegate);
return super.handlePaste(delegate);
}
}
I have tested this and its working

How to show Loading Indicator background transparent in WebView Flutter?

I'm new to flutter and making my first webview app. Here I'm trying to add a spinner every time when a user tries to click the link or page load. I want to make spinner background opacity a bit low just like the given example(right picture) but opacity doesn't work at all.
Here is my approach:
// ignore_for_file: prefer_const_constructors
// ignore: use_key_in_widget_constructors
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,
home: Scaffold(
body: WebViewClass()
)
);
}
}
class WebViewClass extends StatefulWidget {
WebViewState createState() => WebViewState();
}
class WebViewState extends State<WebViewClass>{
int position = 1 ;
final key = UniqueKey();
doneLoading(String A) {
setState(() {
position = 0;
});
}
startLoading(String A){
setState(() {
position = 1;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: SafeArea(child: IndexedStack(
index: position,
children: <Widget>[
WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
key: key ,
onPageFinished: doneLoading,
onPageStarted: startLoading,
),
Container(
color: Colors.white70,
child: Center(
child: CircularProgressIndicator()),
),
]))
);
}
}
Any help or guidance will highly appreciated.
You can try like this. every time when you come in the web view screen. the loader will show.
class WebViewClass extends StatefulWidget {
WebViewState createState() => WebViewState();
}
class WebViewState extends State<WebViewClass> {
bool isLoading = false;
final key = UniqueKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: SafeArea(
child: IgnorePointer(
ignoring: isLoading,
child: Stack(
children: [
WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
key: key,
onPageFinished: (value) {
setState(() {
isLoading = false;
});
},
onPageStarted: (value) {
setState(() {
isLoading = true;
});
},
),
isLoading ? Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.grey.withOpacity(0.5),
child: const Center(child: CircularProgressIndicator()) ,
) : Container(),
],
),
)),);
}
}

show button condition flutter

I would like my button to show only if my _isloading condition is false.
With my code the button appears but too early.
my widget:
i want my button after my timer set _isLoading = false.
Actually, my button show directly to start timer.
How do I get my button to only display when my timer set my _isLoading = false ?
First, you need to keep in mind the Flutter's tree widget structure. The tree hierachy should consists of only Widgets.
In your _validateUser(), since this method doesn't return a Widget, it cannot be put as a child of the Column widget. This method should be trigger by a button (after the user finish the form for example).
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: TestPage(),
);
}
}
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
bool _isLoading = false;
bool _isValidated = false;
_validateUser() async {
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 3), () {
setState(() {
_isLoading = false;
_isValidated = true;
});
});
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_validateUser();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
body: Center(
child: ModalProgressHUD(
inAsyncCall: _isLoading,
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// FlatButton(
// color: Colors.red,
// child: Text("Validate User"),
// onPressed: _validateUser,
// ),
// SizedBox(height: 10),
if (_isValidated)
FlatButton(
color: Colors.green,
child: Text("Validate User"),
onPressed: _validateUser,
),
],
),
),
),
),
));
}
}

How can I go back to the previous page with flutter_webview on button click?

I want to build a simple app using webview_flutter with no AppBar, with a static URL, and just a simple floatingActionButton on the bottom of the screen to navigate to the previous page, but I don't know how to do it.
Can someone just give me some guidelines so I can try to make the button do what it is supposed to do?
Here's an image of the app with the button:
I dunno what to try, but I'm trying to learn, sry about that =D
This is my whole code:
=)
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter/services.dart';
import 'dart:async';
void main () {
runApp(MaterialApp(
title: 'Something',
home: AplicativoB2b(),
debugShowCheckedModeBanner: false,
));
SystemChrome.setEnabledSystemUIOverlays ([]);
}
class AplicativoB2b extends StatefulWidget {
#override
_AplicativoB2bState createState() => _AplicativoB2bState();
}
class _AplicativoB2bState extends State<AplicativoB2b> {
Completer<WebViewController> _controller = Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
//HELP ME HERE!! =(
},
child: const Icon(Icons.arrow_back),
backgroundColor: Colors.black,
),
// bottomNavigationBar: BottomAppBar(color: Colors.white, child: Container(height: 50.0),),
// floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
);
}
}
Future<bool> _willPopCallback() async {
WebViewController webViewController = await _controller.future;
bool canNavigate = await webViewController.canGoBack();
if (canNavigate) {
webViewController.goBack();
return false;
} else {
return true;
}
}
I did it with different code than the previous answer. I followed this tutorial to get the webview & the floating button working. From there, it is really easy to give the floating button a back arrow icon and make the webview go back a page when the floating button is clicked.
To make the webview go back a page when the floating button is clicked (put this in its onpressed method):
controller.data.goBack();
The icon of the floating button can easily be changed at this line of code:
child: Icon(Icons.arrow_back)
All of the different buttons can be found here: link
Here is all of my code:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'App'),
);
}
}
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>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: WebView(
initialUrl: "https://google.com",
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
),
floatingActionButton: FutureBuilder<WebViewController>(
future: _controller.future,
builder: (BuildContext context,
AsyncSnapshot<WebViewController> controller) {
if (controller.hasData) {
return FloatingActionButton(
child: Icon(Icons.arrow_back),
onPressed: () {
controller.data.goBack();
});
}
return Container();
}
),
);
}
}
I did it! =D
It might look simple to lots of people haha, but for someone who never touched this kind of stuff before, I'm very proud of my ninja Ctrl C + Ctrl V. Just kidding, I looked at loads of exemples and tried this and it workd, if anyone has any suggestions I'd aprecciate it! =)
Here's what I did:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter/services.dart';
import 'dart:async';
void main () {
runApp(MaterialApp(
title: 'Something',
home: AplicativoB2b(),
debugShowCheckedModeBanner: false,
));
SystemChrome.setEnabledSystemUIOverlays ([]);
}
class AplicativoB2b extends StatefulWidget {
#override
_AplicativoB2bState createState() => _AplicativoB2bState();
}
class _AplicativoB2bState extends State<AplicativoB2b> {
Completer<WebViewController> _controller = Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
),
floatingActionButton: NavigationControls(_controller.future), // <-- added this
);
}
}
And the class I used for the floatingActionButton.
class NavigationControls extends StatelessWidget {
const NavigationControls(this._webViewControllerFuture)
: assert(_webViewControllerFuture != null);
final Future<WebViewController> _webViewControllerFuture;
#override
Widget build(BuildContext context) {
return FutureBuilder<WebViewController>(
future: _webViewControllerFuture,
builder:
(BuildContext context, AsyncSnapshot<WebViewController> snapshot) {
final bool webViewReady =
snapshot.connectionState == ConnectionState.done;
final WebViewController controller = snapshot.data;
return FloatingActionButton.extended(
onPressed: !webViewReady
? null
: () => navigate(context, controller, goBack: true),
icon: Icon(Icons.arrow_back),
backgroundColor: Colors.black,
label: Text("Voltar"),
);
},
);
}
navigate(BuildContext context, WebViewController controller,
{bool goBack: false}) async {
bool canNavigate =
goBack ? await controller.canGoBack() : await controller.canGoForward();
if (canNavigate) {
goBack ? controller.goBack() : controller.goForward();
} else {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text("Sem histórico anterior")),
);
}
}
}
That's basically the whole code. Flutter is cool and easy to lean when you really want it.
Thx to all!

How to display a local image before webview is successfully loaded in Flutter?

In Flutter, I would like to display a local image before my webview is loaded. In case the user hasn't turn on their wifi, an image will be shown instead of a blank white screen(ios) or an error message saying cannot connect to said webpage(android).
I am using the official webview_flutter package for this app.
Below is the code that I've tried, but it works on ios but doesn't work on android. In Android, when I turned off the wifi and launch the app, an error message which displays the webview link is shown.
Edit: Does not change from image to webview after wifi is connected and pressed the reload button.
final webViewKey1 = GlobalKey<WebViewContainerState>();
var _url = 'http://www.google.com';
final _key = UniqueKey();
bool _isLoadingPage;
class WebViewPage extends StatefulWidget {
#override
WebViewPageState createState() => WebViewPageState();
}
class WebViewPageState extends State<WebViewPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Screen 1'),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: (){
Scaffold.of(context).openDrawer();
},
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
color: Colors.white,
onPressed: () {
webViewKey1.currentState?.reloadWebView();
},
),
]
),
body:
WebViewContainer(key: webViewKey1),
);
}
}
class WebViewContainer extends StatefulWidget {
WebViewContainer({Key key}) : super(key: key);
#override
WebViewContainerState createState() => WebViewContainerState();
}
class WebViewContainerState extends State<WebViewContainer> {
WebViewController _webViewController;
void initState() {
super.initState();
_isLoadingPage = true;
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Opacity(opacity: _isLoadingPage?0:1, child: WebView(
key: _key,
initialUrl: _url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
_webViewController = controller;
},
onPageFinished: (finish) {
setState(() {
_isLoadingPage = false;
});
},
),
),
_isLoadingPage
? Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/fail.png'),
fit: BoxFit.fill,
),
),
)
: Container(
color: Colors.transparent,
),
],
);
}
void reloadWebView() {
_webViewController?.reload();
}
}
Use opacity widget to make it invisible and on completion show it.
Opacity(opacity: _isLoadingPage?0:1, child: WebView(
key: _key,
initialUrl: _url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (webViewCreate) {
_controller.complete(webViewCreate);
},
onPageFinished: (finish) {
setState(() {
_isLoadingPage = false;
});
},
),)
You can use IndexedStack and switch between widget after WebView loaded using onPageFinished. You can use the connectivity plugin to check for connectivity.
ConnectivityResult _connectionStatus;
final Connectivity _connectivity = Connectivity();
StreamSubscription<ConnectivityResult> _connectivitySubscription;
int _page = 1;
#override
void initState() {
super.initState();
initConnectivity();
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
}
#override
void dispose() {
_connectivitySubscription.cancel();
super.dispose();
}
Future<void> initConnectivity() async {
ConnectivityResult result;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
result = await _connectivity.checkConnectivity();
} on PlatformException catch (e) {
print(e.toString());
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) {
return;
}
_updateConnectionStatus(result);
}
#override
Widget build(BuildContext context) {
print(_connectionStatus);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: IndexedStack(
index: _page,
children: <Widget>[
WebView(
initialUrl: 'https://www.google.com/',
onPageFinished: (_) {
if (_connectionStatus != ConnectivityResult.none) {
setState(() {
_page = 0;
});
}
},
),
Container(
child: Center(
child: Text('Some Image'),
),
),
],
),
);
}
Future<void> _updateConnectionStatus(ConnectivityResult result) async {
switch (result) {
case ConnectivityResult.wifi:
case ConnectivityResult.mobile:
case ConnectivityResult.none:
setState(() => _connectionStatus = result);
break;
default:
setState(() => _connectionStatus = result);
break;
}
}
You can use my plugin flutter_inappwebview, that has a lot of events, including events to manage errors (such as net::ERR_ADDRESS_UNREACHABLE) while the WebView is loading an url (onLoadError event) and when it receives HTTP errors, such as 403, 404, etc (onLoadHttpError event).
You can use the connectivity plugin to listen for network state changes by subscribing to the stream exposed by connectivity plugin.
Also, you can use IndexedStack and switch between widgets after the WebView is loaded using onLoadStop.
Full example:
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:connectivity/connectivity.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: InAppWebViewPage()
);
}
}
class InAppWebViewPage extends StatefulWidget {
#override
_InAppWebViewPageState createState() => new _InAppWebViewPageState();
}
class _InAppWebViewPageState extends State<InAppWebViewPage> {
InAppWebViewController webView;
int _page = 2;
bool _loadError = false;
StreamSubscription<ConnectivityResult> subscription;
#override
initState() {
super.initState();
subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
if (result != ConnectivityResult.none && webView != null) {
print("reload");
_loadError = false;
webView.reload();
}
});
}
#override
dispose() {
super.dispose();
subscription.cancel();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InAppWebView")
),
body: IndexedStack(
index: _page,
children: <Widget>[
InAppWebView(
initialUrl: "https://flutter.dev",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
clearCache: true,
debuggingEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) {
print(url);
setState(() {
if (!_loadError) {
_page = 0;
} else {
_page = 1;
}
});
},
onLoadError: (InAppWebViewController controller, String url, int code, String message) async {
print("error $url: $code, $message");
_loadError = true;
},
onLoadHttpError: (InAppWebViewController controller, String url, int statusCode, String description) async {
print("HTTP error $url: $statusCode, $description");
},
),
(Platform.isAndroid) ? Container(
child: Text("My custom error message"),
) : Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/fail.png'),
fit: BoxFit.fill,
),
)
),
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/loading.jpg'),
fit: BoxFit.fill,
),
)
)
],
),
);
}
}