I'm having a weird issue with the Flutter InAppWebView plugin version 4.0.0+4 here https://pub.dev/packages/flutter_inappwebview where I try to load the simple contact us form into the plugin and realize that I can't enter the content into html input text field if I use non English keyboard, in my case I use Vietnamese keyboard. If I switch the keyboard to English one then its working. I double checked the contact us form and made sure its working 100% on Chrome browser outside of the Flutter app using even non English keyboard. I don't use any special code or settings for the plugin, just same as the one mentioned in the pub.dev. I'm using Flutter channel stable v. 1.22.6
This is my code in case you need it:
class WebViewerWidget extends StatefulWidget {
final Map<String, String> metaData;
WebViewerWidget({this.metaData});
#override
_WebViewerWidgetState createState() => _WebViewerWidgetState();
}
class _WebViewerWidgetState extends State<WebViewerWidget> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
InAppWebViewController _webviewCtrl;
double progressIndicator = 0;
return Scaffold(
backgroundColor: ColorPalette.white,
appBar: PreferredSize(
child: TopNavWidget(
title: widget.metaData['title'] ?? '',
),
preferredSize: Size.fromHeight(50.0),
),
body: Builder(
builder: (BuildContext context) {
return Container(
child: Column(
children: [
Container(
child: progressIndicator < 1
? LinearProgressIndicator(
value: progressIndicator,
backgroundColor: Colors.black12,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.blue),
)
: Container()),
Expanded(
child: InAppWebView(
initialUrl: widget.metaData['url'] ?? 'about:blank',
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
javaScriptEnabled: true,
useShouldInterceptAjaxRequest: true,
useShouldInterceptFetchRequest: true,
),
ios: IOSInAppWebViewOptions(),
android: AndroidInAppWebViewOptions(),
),
onWebViewCreated:
(InAppWebViewController webviewController) {
_webviewCtrl = webviewController;
},
onProgressChanged:
(InAppWebViewController controller, int progress) {
setState(() {
progressIndicator = progress / 100;
});
},
),
),
],
),
);
},
));
}
}
Thanks.
Ok, after spending a couple days fixing the issue, I had to give up on this one. Its definitely a bug from the plugin, found someone had the similar issue here https://github.com/pichillilorenzo/flutter_inappwebview/issues/560. I then tried another plugin called WebView https://pub.dev/packages/webview_flutter and it worked perfectly.
This has been fixed with new version 5.0.0 (the latest version now is 5.0.5+3): use useHybridComposition: true Android-specific webview option, all the issues related to the Android keyboard are fixed.
Related
I am having a Scaffold with a floatingActionButton. Inside I have an inappwebview which displays some webpage. The floatingActionButton is displayed in the front above the webview. Also I show some dialog for a second.
Everything worked fine until yesterday. I upgraded flutter and I suddenly experienced weird behaviour which is why i downgraded flutter again. But I still got the same problem. All the stuff that is rendered above is never cleared and just re-painted all the time, which leads to dark shadows and never disappearing dialog windows.
Here you can see the dark shadow around the floatingActionButton and the bottomBar. Also the "<3 + Karma" badge does not disappear anymore.
The weird thing is, that I did not change anything. On my other machine everything still builds fine. There I use the same code, same package versions in my pubspec.yaml and also the same flutter/dart version.
I am really puzzled here. I can't seem to build a new app version without this bug on my main desktop machine.
This is my currently used flutter version:
Flutter 2.10.2 • channel unknown • unknown source
Framework • revision 097d3313d8 (3 months ago) • 2022-02-18 19:33:08 -0600
Engine • revision a83ed0e5e3
Tools • Dart 2.16.1 • DevTools 2.9.2
flutter doctor tells me, No issues found!
This is my code for reference:
return WillPopScope(
onWillPop: ArticleHandler().backButtonPressed,
child: Scaffold(
floatingActionButton: Visibility(
visible: _fabIsVisible,
child: FloatingActionButton(
onPressed: () {
webView.scrollTo(x: 0, y: 0, animated: true);
},
child: Icon(
Icons.arrow_upward,
color: Colors.white,
),
backgroundColor: Theme.of(context).primaryColor,
)),
floatingActionButtonLocation: MarginEndFloatFABLocation(),
floatingActionButtonAnimator: NoFABAnimation(),
appBar: TopBar(widget.articlePreview.url),
body: Builder(builder: (BuildContext context) {
return Container(
child: Column(
children: <Widget>[
progress < 1.0
? Container(
height: 3,
child: LinearProgressIndicator(
value: progress,
backgroundColor: Colors.white,
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
))
: Container(height: 0),
Expanded(
child: InAppWebView(
initialOptions: InAppWebViewGroupOptions(
android: AndroidInAppWebViewOptions(useHybridComposition: true),
crossPlatform: InAppWebViewOptions(
//cacheEnabled: false, // uncomment this line to clean cache on open
)),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onProgressChanged: (InAppWebViewController controller, int progress) {
setState(() {
this.progress = progress / 100;
});
},
onOverScrolled: (InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY) {
// maybe show karma badge
},
onScrollChanged: (InAppWebViewController controller, int x, int y) async {
if (y > 0) {
if (!_fabIsVisible) {
setState(() {
_fabIsVisible = true;
});
}
} else {
if (_fabIsVisible) {
setState(() {
_fabIsVisible = false;
});
}
}
})),
BottomTagBar(),
],
));
}),
));
I'm using the settings_ui package.
I'm making an app that primary will be used on Android but also iOS. In the settings screen I want to display the web view version.
My settings are made using the settings_ui package, and the app consists of a web view, using this package.
A method exists to return the web view version Android is using. To retrieve this, we need to make use of await.
I want to dynamically add a section to my list of settings if the device is Android. That part is simple.
The problem is I can't work out how to read the async value inside what I think I need, a FutureBuilder.
The settings_ui expects an array of List sections.
For example:
return SettingsList(
sections: [
SettingsSection(
title: const Text('Common'),
tiles: [
SettingsTile(
title: const Text('Web address'),
description: const Text('Change the web address.'),
leading: const Icon(Icons.language),
onPressed: (context) {
Navigator.push(
context,
MaterialPageRoute(fullscreenDialog: true, builder: (context) => const WebAddressForm()),
);
},
),...
I then have code if the device is Android (simplified for demonstration purposes):
if (showAndroidSection)
FutureBuilder<SettingsSection>(
future: _someFutureToGetAndroidWebViewVersion(), builder: (context, snapshot) => SettingsSection(tiles: []))
Doing this gives me the error "The element type 'FutureBuilder' can't be assigned to the list type 'AbstractSettingsSection'". I have tried casting to no avail.
I am aware I shouldn't be calling the future in the build method - and I will move to initState but the problem will still remain. How do I return the web view version within a dynamic SettingsSection?
I hope that makes sense. Thanks.
The way I'm working around this currently is the following, but feels wrong to me...
late String _versionName = "";
#override
void initState() {
super.initState();
enableCustomChromeTabs = SharedPrefs.instance.getBool(Settings.customChromeTabs) ?? false;
if (!kIsWeb && Platform.isAndroid) {
showAndroidSection = true;
getPackageInfo().then((value) {
setState(() {
_versionName = value!.versionName ?? "Unable to retrieve.";
});
});
}
Then later...
SettingsSection(
title: const Text(_versionName),
tiles: [
SettingsTile.switchTile(
title: const Text('xyz'),
...
I solved it as follows (unsure if this is the correct way or an "easier" way):
Added this to the array of sections:
MyWidget()
and...
class MyWidget extends AbstractSettingsSection {
#override
Widget build(context) {
return FutureBuilder<AndroidWebViewPackageInfo?>(
future: getPackageInfo(),
builder: (context, AsyncSnapshot<AndroidWebViewPackageInfo?> snapshot) {
return SettingsSection(
title: const Text("Android debugging"),
tiles: [
CustomSettingsTile(
child: Container(
padding: const EdgeInsetsDirectional.only(
start: 25,
),
child: Text(
snapshot.data,
),
),
),
],
);
});
}
}
I was using youtube_player_flutter and implemented everything correctly as it was written in it's README.
But still I was facing one issue that whenever I open that page where I want the youtube player to open, it keeps loading and never loads the video.
I've searched about this issue everywhere but didn't get any solution. One of those solutions was that to include internet permission in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
I did this, nothing changed. I also downgraded the package to v6.1.1, someone asked me to do this in github issue, but that also did nothing.
How can I resolve this issue?
I'm answering my own question as I didn't find anything that can resolve this problem when I was searching about this issue.
So, I tried to define the controller in initState() and it worked, and now it's working in v7.0.0+7. This is my code:
class AboutTopic extends StatefulWidget {
final String videoLink;
AboutTopic({this.videoLink});
#override
_AboutTopicState createState() => _AboutTopicState();
}
class _AboutTopicState extends State<AboutTopic> {
YoutubePlayerController _controller;
#override
void initState() {
_controller = YoutubePlayerController(
initialVideoId:
YoutubePlayer.convertUrlToId(widget.videoLink),
flags: YoutubePlayerFlags(
mute: false,
autoPlay: true,
disableDragSeek: true,
loop: false,
enableCaption: false),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About'),
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop();
},
)
),
body: YoutubePlayer(
controller: _controller,
showVideoProgressIndicator: true,
bottomActions: <Widget>[
const SizedBox(width: 14.0),
CurrentPosition(),
const SizedBox(width: 8.0),
ProgressBar(isExpanded: true),
RemainingDuration(),
],
aspectRatio: 4 / 3,
progressIndicatorColor: Colors.white,
onReady: () {
print('Player is ready.');
},
),
);
}
}
I want to use fancy bottom navigation in flutter. When i switch between tabs, it is showing the tab switching only at the navigation bar but, The body is not switching. It is not showing another tabs as i switch.
Here's my code
return Scaffold(
body: _getPage(currentPage),
bottomNavigationBar: FancyBottomNavigation(
key: bottomNavigationKey,
tabs: [
TabData(iconData: Icons.home,title: 'Home'),
TabData(iconData: Icons.search, title: 'Search'),
TabData(iconData: Icons.person, title: 'Profile'),
],
onTabChangedListener: (position){
setState(() {
currentPage = position;
final FancyBottomNavigationState fState =
bottomNavigationKey.currentState;
fState.setPage(position);
print('currentPage = $currentPage');
});
},
)
);
_getPage(int page){
switch(page) {
case 0:
return Page1();
case 1:
return Search();
case 2:
return Profile();
}
}
You don't need the GlobalKey here. It is enough to just set the currentPage value in the setState.
The Dev Nathan Withers writes here that the key prop defaults to null and is only used for Programmatic Selection of tabs. This special use case is only relevant if you want to change tabs by not clicking on the actual buttons. You don't need this feature. You can check out the example at line 48 to 50 for an actual use-case for the key prop.
Also examine if an IndexedStack suits you. It saves the pages state. You're _getPage() method destroys and builds each page new on each switch.
I put everything together in this example:
class _HomePageState extends State<HomePage> {
int _currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: false,
child: IndexedStack(
index: _currentPage,
children: [StartView(), AllServicesView()],
),
),
bottomNavigationBar: FancyBottomNavigation(
tabs: [
TabData(iconData: Icons.home,title: 'Home'),
TabData(iconData: Icons.search, title: 'Search'),
],
onTabChangedListener: (position){
setState(() {
_currentPage = position;
print('currentPage = $_currentPage');
});
},
)
);
}
Yesterday I spent over ten hours trying to learn a bit of MobX and applying a simple SnackBar if there is an error coming from the API. My question is if the solution I found can be considered good and appropriate or there is a better one to be implemented.
class _LoginPageState extends State<LoginPage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
final _controller = Modular.get<LoginController>();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(widget.title),
),
body: Observer(
builder: (context) {
if (_controller.token?.error != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(_controller.token?.error),
duration: Duration(seconds: 2),
));
});
}
return Center(
child: PrimaryButton(
onPressed: () => _controller.authenticate(),
text: 'Enviar',
icon: Icons.send,
),
);
},
),
);
}
}
In case you're curious about it, I'm using flutter_modular, hence the Modular.get<>()
I like this approach, that is as long as you make sure your snackbar does NOT cover the content of the page, as you know errors from API's could be complex and well documented, therefore you may come across a situation where the snackbar would cover your content.
I usually would use showDialog instead, as errors should not usually accur. when they do I would push a popup displaying and explaining the situation using the error details.
This is my customized version of popups:
class ButtonsAndAction extends FlatButton{
///Providing a func is "optional", just pass null if you want the defualt action to pop the navigator.
ButtonsAndAction(BuildContext context, String text, Function func ) : super(child: new Text(text, textDirection: Helper.textDirection(),style: TextStyle(color: ConstantValues.mainBackgroundColor)
,), onPressed: func == null ? () {Navigator.of(context).pop();} : func);
}
class Helper{
static TextDirection textDirection() => AppConfig.rtl ? TextDirection.rtl : TextDirection.ltr;
/// Used to push alerts of texts with a set of actions(buttons and actions) if wanted
static Future pushDialog(BuildContext context, String title, String body, {List<ButtonsAndAction> actions, bool dismissable = true}) {
return showDialog(
context: context,
builder: (BuildContext context) {
return new WillPopScope(
onWillPop: () async => dismissable,
child:
new AlertDialog(
shape: new RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(ConstantValues.roundRadius)),
side: BorderSide(color: ConstantValues.mainBackgroundColor, width: ConstantValues.roundBorderWidthForPopup)),
title: new Container(child: new Text(title, textDirection: textDirection(), style: TextStyle(color: ConstantValues.mainBackgroundColor),), width: double.infinity,),
content: new Container(child: SingleChildScrollView(child:
new Text(body, textDirection: textDirection(), style: TextStyle(color: ConstantValues.mainBackgroundColor))),
width: double.infinity),
actions: actions
));
},
);
}
}
Good luck!