In order to use SFSafariViewController in my Flutter app, I am using this package: flutter_inappwebview: ^3.3.0+3
While this works, there is a bug in the animation through which the SFSafariViewController appears.
Notice this:
Actual Behaviour
Expected Behaviour
Notice how in the second GIF, the app itself is pushed back, whereas in the first GIF, we can see a semi-transparent view created instead of the native push back animation for the app.
Here's the code that I am using:
final ChromeSafariBrowser browser =
MyChromeSafariBrowser(MyInAppBrowser());
await browser.open(
url: 'https://google.com',
options: ChromeSafariBrowserClassOptions(
ios: IOSSafariOptions(
barCollapsingEnabled: true,
presentationStyle: IOSUIModalPresentationStyle.POPOVER)));
Here's the Gist for the MyChromeSafariBrowser and MyInAppBrowser class.
Can you please help me with this?
You should submit an issue to flutter_inappwebview. The package uses a native modal presentations and it looks like it is not doing it correctly.
If you want to use the modal_bottom_sheet package as the gif you attached, then you can't use ChromeSafariBrowser, and you would need to use InAppWebView instead.
ChromeSafariBrowser is a native activity outside Flutter while InAppWebView is a native view wrapped inside Flutter.
InAppWebView(
initialUrl: "https://flutter.dev/",
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
setState(() {
this.url = url;
});
},
onLoadStop: (InAppWebViewController controller, String url) async {
setState(() {
this.url = url;
});
},
onProgressChanged: (InAppWebViewController controller, int progress) {
setState(() {
this.progress = progress / 100;
});
},
),
This was fixed in the release v4.0.0 and it is now possible to get the expected behaviour by setting
presentationStyle: IOSUIModalPresentationStyle.POPOVER
More information here: https://pub.dev/packages/flutter_inappwebview#-changelog-tab-
Related
In Flutter, I am using Navigator 2.0 using go_router.
I want deeplink in my app to work properly and my app has some properties which needs to be initialized before going to any screen so I have put my MaterialApp.router(...) in a conditional boolean and Splash screen is shown while app is being initialized.
But deeplink doesn't redirect to given path when app is closed on iOS, it just opens the app.
Everything is working fine in android.
It starts working fine in iOS too when I remove async app initialization function from MaterialApp.
What am I doing wrong? Is there better way to initialze app instead of a conditional check in MaterialApp.
I have created a basic reproducible code repo https://github.com/sgshivamgarg8/testapp. Please try it out. To test deeplinks I have put required scripts in scripts/ folder.
Future<void> _appInit() async {
setState(() {
isLoading = true;
});
await Future.delayed(
const Duration(seconds: 1)); // commenting this line solves the issue
setState(() {
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return isLoading
? const CircularProgressIndicator()
: MaterialApp.router(
routerConfig: _router,
title: 'Test',
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
);
Below the code loads several tweets in WebViews. This code works flawlessly in Android but produces a different result in iOS Simulator. No errors are produced in logs and nothing in flutter analyze pertain to this page and its classes.
Included <key>io.flutter.embedded_views_preview</key><true/> in info.plist
Widget build(BuildContext context) {
var child;
//print(_tweetHTML);
if (_tweetHTML != '' && _tweetHTML.length > 0) {
final downloadUrl = Uri.file(_filename).toString();
print(downloadUrl); //This prints the expected HTML file
// Create the WebView to contian the tweet HTML
final webView = WebView(
initialUrl: downloadUrl,
javascriptMode: JavascriptMode.unrestricted,
gestureNavigationEnabled: true,
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith(downloadUrl)) {
//print('allowing navigation to $request');
return NavigationDecision.navigate;
} else if (request.isForMainFrame) {
//print('allowing navigation to $request');
_launchURL(request.url);
return NavigationDecision.prevent;
} else {
//print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
},);
final box = LimitedBox(
maxHeight: 500.0,
child: webView,
);
child = box;
} else {
child = Text('Loading...');
}
return Container(child: child);
}
Expected results:
As you can see this works in Android:
Actual results:
However on iOS:
Option 1: add this to html body
<script id="twitter-wjs" type="text/javascript" async defer src="//platform.twitter.com/widgets.js"></script>
Option 2: Check out this widget on github TweetWebView for Flutter
Option 3: use Flutter WebView Plugin by fluttercommunity.dev, BUT This webview is not integrated in the widget tree, it is a native view on top of the flutter view. You won't be able see snackbars, dialogs, or other flutter widgets that would overlap with the region of the screen taken up by the webview.
When I want to display API data on the iOS Flutter webview, the page takes about 10 seconds to load. Are there any specific settings for that?
the following code that I use
child: WebView(
// controller: _webViewController,
initialUrl: new Uri
.dataFromString(
"<meta name='viewport' content='width=device-width,initial-scale=1,maximum-scale=1'/>" //IOS KONFIGURASI TEXT NORMAL DI EBVIEW
"${widget.s_content}",
mimeType: 'text/html')
.toString(),
//initialUrl: '${dataQuestions["list_question"][_counter]["data_option"][loop]["option_text"]}',
javascriptMode:
JavascriptMode.unrestricted,
onPageFinished: (finish) {
setState(() {
isLoading = false;
});
},
)
EDIT:
On real device the data loads fast but only in the emulator takes time to load.
Is there a way to block ads in an flutter WebView? I am building app that lets users browse web pages, but need to block ads. Its basically a custom browser, but I need to get rid of ads.
navigationDelegate: (NavigationRequest request) {
return NavigationDecision.prevent;}
I added that to the Webview to prevent navigation to other screens in case they accidentally click on those ads.
WebView(
initialUrl: widget.url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
javascriptChannels: <JavascriptChannel>[
_toasterJavascriptChannel(context),
].toSet(),
navigationDelegate: (NavigationRequest request) {
return NavigationDecision.prevent;
},
onPageStarted: (String url) {
print('Page started loading: $url');
},
onPageFinished: (String url) {
print('Page finished loading: $url');
},
gestureNavigationEnabled: true,
);
The webview_flutter plugin, currently, doesn't offer such feature.
However, to block ads, you can use the flutter_inappwebview plugin (I'm the author). It offers the content blocking feature through the ContentBlocker class!
I have written an article that explains it in more detail and can be found here.
Also, I have created a full project example on GitHub here (webview_ad_blocker) that uses the ContentBlocker class to block the loading of ads, such as Google Ads.
I have the current flow of screens.
Home -> Info -> Log in -> Camera.
The issue is that I don't want to add Login to the stack because then when you go back from the camera you will go back to login, rather than information.
If I use this.props.navigate('Info') from the camera, then the issue is that back now goes to the camera rather than home from info. Note I also don't want to complete delete the stack at the login page as I've seen with the Reset function here (https://reactnavigation.org/docs/navigators/navigation-actions#Reset).
Ideally what I would like to do would be:
Home -> Info -> Login (Not added to stack) -> Camera.
This was I can just go back from Camera to Info and from Info to Home.
Has anyone found a nice way around this?
Thanks
If you want to retain the whole stack and params, only ditching the Login page, then you could use Reset Navigation Action either in the Camera page :
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: 'Home', params: this.props.navigation.state.params })
NavigationActions.navigate({ routeName: 'Info', params: this.props.navigation.state.params })
]
})
this.props.navigation.dispatch(resetAction)
This way you will:
Navigate to Info page on top of Home page, meaning that Back button will lead to Home page.
Retain the params, if you need. If you don't just do not pass them.
This functionality could be added anywhere: inside the components / inside the Action Creators / inside the Back button.
Or add the same code for the Back button. In this case you need to add this to the navigationOptions of Camera route:
const backButton = ({ navigation }) => ({
headerLeft: <TouchableOpacity
onPress={() => {
// the code from above, except for import line and using navigation.whatever instead of this.props.navigation.whatever
}}
><Text>Back</Text> // this is a text of a back button, you could also use an icon
</TouchableOpacity>
});
Navigator:
const MyStack = StackNavigator({
home: { screen: Home },
info: { screen: Info },
login: { screen: Login },
camera: { screen: Camera }, navigationOptions: backButton });
With v6 of Navigation, CommonActions.reset might might work for you:
import { useNavigation,CommonActions } from "#react-navigation/native";
...
const navigation = useNavigation();
...
navigation.dispatch((state) => {
// We can keep all routes on the stack except that which has name "Login"
const routesWithoutLogin = state.routes.filter((r) => r.name !== "Login");
// We can then define the new route we want
const routeToAdd = {
name: "Info",
params: {},
};
const newRoutes = [...routesWithoutLogin, routeToAdd];
return CommonActions.reset({
...state,
routes: newRoutes,
index: newRoutes.length - 1,
});
});
Reference here: https://reactnavigation.org/docs/navigation-actions/#reset
When debugging, I find it quite useful to log my routes state to see what needs changing.
import { useNavigation,CommonActions } from "#react-navigation/native";
...
const navigation = useNavigation();
...
console.log(navigation.getState().routes);
It should look something like this
[{"key": "Root-YhPzpMumgvLs62ZwxjOJW", "name": "Root", "params": undefined}, {"key": "Chat-sDA1-4R0fImaxaj9maXGb", "name": "Chat", "params": {"conversation": [Object]}, "path": undefined}]
One more note, when I tried creating routes from scratch, I lost some of my redux state. It seems like a better idea to filter out routes you don't want than to make them from scratch.