I need to update webview's url with my setState. How to do that?
The initial url I haved set on constructor have been loaded successfully. But it's not working when I try load the new one through my drawer menu by onTap method.
...
class _MyWebState extends State<MyWeb> {
Completer<WebViewController> _controller = Completer<WebViewController>();
final Set<String> _favorites = Set<String>();
String url;
Widget web;
_MyWebState() {
web = WebView(
initialUrl: 'https://my.flazhost.com',
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MyWeb'),
actions: <Widget>[
NavigationControls(_controller.future),
Menu(_controller.future, () => _favorites),
],
),
body: web,
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
leading: FlutterLogo(
size: 20,
),
title: Text('Web AlQudwah'),
onTap: () {
setState(() {
web = WebView(
initialUrl: 'https://flazhost.com',
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
);
Navigator.pop(context);
});
},
),
],
),
),
floatingActionButton: _bookmarkButton(),
);
}
}
...
How to update webview's url onTap method? Please help?
I struggled with the flutter webview for a long time, and I'm still not very happy with it...
But the key is the WebController instance that you get from the onWebViewCreated method of the webview. The instance of the initialized WebViewController has a method called loadUrl('your_new_url')
So you can just call following in your onTap handler:
onTap: () {
_controller.loadUrl('your_new_url');
}
When working with WebViews in flutter I experienced problems when using setState, because it will trigger a rebuild of whole page including the webview which gets reinitialized and of course reloads the page...
But that is mostly not the desired result, so if you want to controll the webview (wheater it will be changes to url, going back in browser history or executing javascript) you should handle it over the WebController initialized from the onWebViewCreated method
Here are the most used methods of the WebViewController class:
_controller.currentUrl() // Gets current url from WebView
_controller.loadUrl('your_new_url'); // Loads new url
_controller.evaluateJavascript('your js code as a string') // Execute JS over WebView console
_controller.goBack() // Goes one step back in browser history
_controller.goForward() // Goes one step forwards in browser history
_controller.clearCache() // Clears cache of WebView
It's really confusing and badly documented (espacially for beginners like me), so I hope the flutter team will provide us with more detailed information and tutorials
Related
The context:
I stumbled upon a minor crash while testing a ListView of Dismissibles in Flutter. When swiping a dismissible, a Dialog is shown using the confirmDismiss option, for confirmation. This all works well, however the UI crashes when testing an unlikely use case. On the page are several options to navigate to other (named) routes. When a dismissible is swiped, and during the animation an option to navigate to a new route is tapped, the crash happens.
How to replicate the crash:
Dismiss the Dismissible
During the animation that follows (the translation of the position of the dismissible), tap on an action that brings you to a
new route. The timeframe to do this is minimal, I've extended it in the example.
The new route loads and the UI freezes
For reference, this is the error message:
AnimationController.reverse() called after AnimationController.dispose()
The culprit is the animation that tries to reverse when it was already disposed:
package:flutter/…/widgets/dismissible.dart:449
Things I've tried:
Initially, I tried checking this.mounted inside the showDialog builder but quickly realised the problem is not situated there.
Another idea was to circumvent the problem by using CancelableOperation.fromFuture and then cancelling it in the dispose() method of the encompassing widget, but that was to no avail.
What can I do solve or at least circumvent this issue?
The code (can also be found and cloned here):
// (...)
class _DimissibleListState extends State<DimissibleList> {
int childSize = 3;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: childSize,
itemBuilder: (context, index) {
if (index == 0) {
return _buildNextPageAction(context);
}
return _buildDismissible();
},
),
);
}
Widget _buildNextPageAction(context) {
return FlatButton(
child: Text("Go to a new page"),
onPressed: () => Navigator.of(context).pushNamed('/other'),
);
}
Dismissible _buildDismissible() {
GlobalKey key = GlobalKey();
return Dismissible(
key: key,
child: ListTile(
title: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.red,
child: Text("A dismissible. Nice."),
),
),
confirmDismiss: (direction) async {
await Future.delayed(const Duration(milliseconds: 100), () {});
return showDialog(
context: context,
builder: (context) {
return Dialog(
child: FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("Confirm dismiss?"),
),
);
},
);
},
resizeDuration: null,
onDismissed: (direction) => setState(() => childSize--),
);
}
}
I had almost same problem with confirmDismiss ,in my case I was using (await Navigator.push() ) inside of confirmDismiss to navigate to another screen but in return I faced this error :
AnimationController.reverse() called after
AnimationController.dispose()
so to solve my problem inside of confirmDismiss I call a future function out side of confirmDismiss (without await ) and then add return true or false after that function call to finish animation of confirmDismiss.
I have created a webviewscaffold but can't download anything from it while browsing from the webview, I made but when I click the button it does nothing. I don't know where to start, like in other browsers that can download and preview, I'm trying to achieve the same thing in here.
class AppState extends State<App> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Align(
alignment: Alignment(1, 1),
child: Container(
child: Web(), // it is a statefulwidget that have WebviewScaffold, i have created it on a new page and imported/used here
),
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 20.0),
)
],
),
Align(
alignment: Alignment(0.7, -0.93),
child: FloatingActionButton(
tooltip: "Share",
child: Icon(
Icons.share,
color: Colors.amberAccent,
),
onPressed: () {
_onShareTap();
}),
),
],
),
);
}
I expect, when I click the print or download button within the webview it should work like any other browser.
You can use my plugin flutter_inappwebview, which is a Flutter plugin that allows you to add inline WebViews or open an in-app browser window and has a lot of events, methods, and options to control WebViews.
To be able to recognize downloadable files, you need to set the useOnDownloadStart: true option, and then you can listen the onDownloadStart event!
Also, for example, on Android you need to add write permission inside your AndroidManifest.xml file:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Then, you need to ask permission using the permission_handler plugin. Instead, to effectively download your file, you can use the flutter_downloader plugin.
Here is a complete example using http://ovh.net/files/ (in particular, the http://ovh.net/files/1Mio.dat as URL) to test the download:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize(
debug: true // optional: set false to disable printing logs to console
);
await Permission.storage.request();
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
InAppWebViewController webView;
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('InAppWebView Example'),
),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: InAppWebView(
initialUrl: "http://ovh.net/files/1Mio.dat",
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
useOnDownloadStart: true
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) {
},
onDownloadStart: (controller, url) async {
print("onDownloadStart $url");
final taskId = await FlutterDownloader.enqueue(
url: url,
savedDir: (await getExternalStorageDirectory()).path,
showNotification: true, // show download progress in status bar (for Android)
openFileFromNotification: true, // click on notification to open downloaded file (for Android)
);
},
))
])),
),
);
}
}
Here, as you can see, I'm using also the path_provider plugin to get the folder where I want to save the file.
If you are the owner of html page, you can check workaround that works for me. In source of your html page add onclick function for specific resource links:
function downloadpdf() {
var currentHref = window.location.href;
window.history.pushState(null, null, '/app/somepdf.pdf');
setTimeout(() => window.location.replace(currentHref), 1000);
}
In Flutter code add listener:
StreamSubscription<String> _onWebViewUrlChanged;
_onWebViewUrlChanged =
FlutterWebviewPlugin().onUrlChanged.listen((String url) {
if (url.contains('.pdf')) {
launchURL(url);
}
});
launchURL will open predefined url in external browser window. So you can download pdf/etc from flutter_webview_plugin.
You should add some your app-specific js/flutter magic
I'm new on flutter, I have a Homepage where I have a Drawer menu and body list content.
DRAWER MENU => On tap item list of drawer menu I'm loading a PAGE web URL and on tap BACK it returns to my homepage. So it works very well.
BODY LIST CONTENT => On tap item list it loads the page web URL well BUT when I won't return back to my homepage it returns a black screen :(
Homepage.dart
class HomePage extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _HomePage();
}
}
class _HomePage extends State<HomePage>{
#override
Widget build(BuildContext context) {
// TODO: implement build
var globalContext = context;
return Scaffold(
appBar: AppBar(
title: Text(
'Benvenuto',
style: TextStyle(color: Colors.white)
),
backgroundColor: Color(0xFF4035b1),
),
drawer: Drawer(
child: new Column(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: Text('VIA ALBERTO POLIO 54'),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF4268D3),
Color(0xFF584CD1)
],
begin: FractionalOffset(0.2, 0.0),
end: FractionalOffset(1.0, 0.6),
stops: [0.0, 0.6],
tileMode: TileMode.clamp
)
),
accountEmail: Text('ORARI: LUNEDI - VENERDI 9:30 / 19:30'),
currentAccountPicture: new CircleAvatar(
radius: 50.0,
backgroundColor: const Color(0xFF778899),
backgroundImage: AssetImage("assets/img/icon_logo.jpg"),
)
),
// This list work well!
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
],
),
),
// The menu on my body load well the page web url but doesn't return back to my homepage.
body: new Column(
children: <Widget>[
ListTile(
leading: new Icon(Icons.arrow_forward_ios),
title: new Text("TEST"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => Page("title", "www.google.com")));
}
)
])
);
}
}
Page.dart
class Page extends StatelessWidget{
final String titleText;
final String urlSource;
Page(this.titleText, this.urlSource);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new WebviewScaffold(
url: urlSource,
appBar: new AppBar(
title: Text(titleText),
),
withZoom: true,
withLocalStorage: true,
hidden: true,
);
}
}
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: HomePage()
);
}
}
Thank you for your help guys!
You shouldn't be using Navigator.pop() just before Navigator.push().
If replacing the current page with a new one is what you want, you can use Navigator.of(context).pushReplacement().
If you only want to navigate to a new route delete the pop method and only use push
The real problem here is that when you're using Navigator.pop() you're removing it from the "pages stack". When you're using Navigator.pop() at the Drawer(), the ".pop" function removes the Drawer and keeps the main page.
But at the time you use it with the ListTile(), which is part of the "main body" of the page, you just remove it.
Whatever collapses the main page when pressed, such a Drawer, Dialog or even a Keyboard, will be removed using Navigator.pop(), any other thing that is at the page which implements the "Navigator.pop()" will remove the page instead.
Navigator.of(context).pop();
this one is popping the screen in the flutter.
you can refer this doc as well https://api.flutter.dev/flutter/widgets/Navigator/pop.html
your home page doesn't have any stack behind it so when you have written Navigator.of(context).pop(); then it will pop the home page where there is not anything and it always shows the blank screen.
when you have tried Navigator.of(context).pop(); in the drawer then it has home page as a stack in the flutter which is the home page in your case and it will pop to the home page and show the blank page.
So i just implemented this WebView in flutter and it's great, but there's a problem when i embedd a youtube video using webview, the video is still playing even i close the Webview page. How do i turn it off?
the plugin that i use is flutter_webview_plugin
final flutterWebviewPlugin =FlutterWebviewPlugin();
#override
void initState() {
super.initState();
flutterWebviewPlugin.close();
}
#override
void dispose() {
super.dispose();
flutterWebviewPlugin.dispose();
}
and this is the widget:
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () {
print('Hello there!'); flutterWebviewPlugin.launch('https://www.youtube.com/embed/m5rm8ac4Gsc');
},
)
What you can do is to create a route and but inside it your webview example: /youtubeWebview and use Navigator.popAndPushNamed(context, '/yourRoute'); to go back instead of Navigator.pop(context);
I finally get the answer but i change the package to webview_flutter instead of flutter_webview_plugin.
To stop the audio from youtube or any other website that has audio we need to change the url of the current webview. and maybe it will work with flutter_webview_plugin too.
/* define webview controller */
WebViewController _controller;
/* before we leave current route, make sure to change the url to something */
Future<bool> _willPopCallback(WebViewController controller) async {
controller.loadUrl('https://www.google.com/'); /* or you can use controller.reload() to just reload the page */
return true;
}
return WillPopScope(
onWillPop: () => _willPopCallback(_controller), /* call the function here */
child: Scaffold(
appBar: AppBar(
title: Text('Just appbar'),
),
body: Column(
children: <Widget>[
Expanded(
child: WebView(
key: UniqueKey(),
javascriptMode: JavascriptMode.unrestricted,
initialUrl: widget.videoUrl,
onWebViewCreated: (WebViewController webViewController) { /* i am not sure what this line actually do */
_controller = webViewController;
},
),
),
Text(
'Please pause the video before you go back',
style: TextStyle(
color: Colors.black,
),
)
],
),
),
);
I have a page where a user makes a "post" (like twitter/ig) upon clicking submit that post is sent to the backend via a post request and then the page routes to the "Main Page" which is basically a scaffold with a persistent bottom navigation bar that creates the body based on which icon (index) in the navigation bar is pressed. By default the index is 0 (first icon) so the corresponding body is shown which also shows the newly created posts the user made by performing a get request to the server. But the problem is it doesn't show the posts unless I click hot reload. Similarly if I navigate to a different page by clicking one of the icons and come back to the first page the posts are gone till I press hot reload again. How can I ensure the posts are loaded each time the body/page is created?
Cody for page displaying the posts:
class PostPage extends StatefulWidget {
#override
_PostPageState createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
ApiClient _client = ApiClient();
String session;
int userID;
var refreshKey = GlobalKey<RefreshIndicatorState>();
Future GetInfo() async{
session = await getSession("session");
print("from get info "+ session);
userID = await getUserID("userID");
print("from get info "+ userID.toString());
}
#override
void initState() {
// TODO: implement initState
super.initState();
GetInfo();
print("called");
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text("Posts", style: Style.AppBarStyle),
bottom: TabBar(
tabs: [
Tab(
text: "Text1",
),
Tab(
text: "Text2",
),
],
),
),
body: TabBarView(
children: [
Posts(_client.getPostsOne(userID, session)),
Posts(_client.getPostsTwo(userID, session)),
],
),
),
);
}
}
Code for the future builder Posts:
Widget Posts(Future<List<Post>> future) {
return FutureBuilder<List<Post>>(
future: future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF585B8D), Color(0xFF252642)])),
child: CustomScrollView(
scrollDirection: Axis.vertical,
shrinkWrap: false,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => PostCard(
snapshot.data[index], false, true, false),
childCount: snapshot.data.length,
),
),
)
],
));
}
if (snapshot.data == null) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF585B8D), Color(0xFF252642)])),
child: Container());
}
if (snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator(),
);
}
});
}
EDIT It seemed like the GET request was being made before the userID/session key was loaded from shared preferences, adding setState({}) fixed this because it caused the widget to be repainted with the now retrieved userID/session key. But two get requests were made instead of one, to prevent this I checked if the session was null before calling or else I displayed an empty container.
session !=null?Posts(_client.getPostsOne(userID, session)):Container()
You forgot to call setState after you get the data (to rebuild the widget):
Future GetInfo() async{
session = await getSession("session");
print("from get info "+ session);
userID = await getUserID("userID");
print("from get info "+ userID.toString());
setState(() {
});
}