Flutter InAppWebView inside SingleChildScrollView - flutter

I have a request to make a card containing a web view followed by more widgets. The view should look something like this.
I have made implementation like this:
SingleChildScrollView(
...
child: Column(
children: [
Container(
...
child: Column(
children: [
SizedBox(
height: _webviewHeightSetInOnLoadStop
child: InAppWebview(
...
)
),
...
)
),
Widget1(),
Widget2(),
Widget3(),
]
Where the _webviewHeightSetInOnLoadStop is set like this:
onLoadStop: (controller, url) async {
final height = await controller.evaluateJavascript(
source: "document.documentElement.scrollHeight;",
);
...
setState(() {
_webviewHeightSetInOnLoadStop = height;
...
});
}
The problem with this implementation is that when the webview is too large the Android crashes with:
IllegalStateException: Unable to create a layer for InAppWebView, size 960x39192 exceeds max size 16384
in my understanding, this is thrown due to the height of the webview, which is system restricted.
So I desire a behavior in which webview is scrollable, its container has a fixed height that is a little bit bigger than the screen (when needed) and when the end of the scroll of the webview is reached in either direction the drag event is passed to the SingleChildScrollView.

There is another plugin which can do what you are looking for exactly
webview_flutter_plus: ^0.2.3
Try this code
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter_plus/webview_flutter_plus.dart';
class ImageEditor extends StatefulWidget {
const ImageEditor({Key? key}) : super(key: key);
#override
_ImageEditorState createState() => _ImageEditorState();
}
class _ImageEditorState extends State<ImageEditor> {
WebViewPlusController? _controller;
double _height = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height:MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: _height,
child: WebViewPlus(
onWebViewCreated: (controller) {
this._controller = controller; controller.loadUrl('https://pub.dev/packages/webview_flutter_plus');
},
onPageFinished: (url) {
_controller!.getHeight().then((double height) {
print("Height: " + height.toString());
setState(() {
_height = height;
});
});
},
javascriptMode: JavascriptMode.unrestricted,
),
),
Container(
height:200,
width: MediaQuery.of(context).size.width,
color: Colors.red,
),
Container(
height:200,
width: MediaQuery.of(context).size.width,
color: Colors.black,
),
Container(
height:200,
width: MediaQuery.of(context).size.width,
color: Colors.green,
),
],
),
),
),
);
}
}

You should try flutter_html instead web view

i think you should implement a custom size or just limiting it for the intended behaviour for the container of your webview and make that responsive so the screen doesn't get any crashes or like that in older devices.

Related

Flutter Card child content height is larger than its parent

I'm trying to use a GridView to handle displays for multiple Card, each Card contains of an Image. Unfortunately it turns out that the Image is taking a larger height than its parent (see attached picture for the details).
I'm pretty new to Flutter layout so any ideas why this is happening and how I can resolve this? I want the layout to be something like this:
Display 2 cards on each line.
The Card width or height should not be fixed.
The Image height should be scaled according to its width.
class SquadSelectionScreen extends StatelessWidget {
final List<Team> teams;
const SquadSelectionScreen({super.key, required this.teams});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Squads'),
),
body: GridView.count(
crossAxisSpacing: 10,
crossAxisCount: 2,
padding: const EdgeInsets.all(16),
children: teams
.map(
(team) => SquadView(team: team),
)
.toList(),
),
);
}
}
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
context.push('/squads/${team.code}');
},
child: Card(
elevation: 1,
child: Column(
children: [
Image(
image: NetworkImage(team.imageUrl),
),
const SizedBox(
height: 8,
),
Center(
child: Text(team.name),
),
],
),
),
);
}
}
Using GridView.count has a very visible drawback, namely the size of the aspect ratio of the grid will always be one (1:1 or Square) and can't be changed.
So if you look at the code above, you can't set an image with the same aspect ratio because the text will sink.
The first suggestion for me if you still want to use GridView.count is
Wrapping your Image with AspectRatio that has value higher than one (example set Ratio to 4/3, 5/3, 16/9, or landscape looks). Note: 4/3 = is higher than 1, 16/9 = is higher than 1, etc..
Then wrap the Text Widget with Expanded()
Example code:
class SquadView extends StatelessWidget {
final Team team;
const SquadView({super.key, required this.team});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Card(
elevation: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
AspectRatio(
aspectRatio: 4/3, // you can set the value to 16/9 or anything that result is higher than one
child: Image(
image: NetworkImage(team.imageUrl),
fit: BoxFit.cover, // set How the image looks to Fit
),
),
const SizedBox(
height: 8,
),
Expanded(
child: Center(
child: Text(team.name, overflow: TextOverflow.ellipsis),
),
),
],
),
),
),
);
}
}
I suggest you try GridView.builder or another GridView. You can look at the documentation here
or this third package this will be good for to try flutter_staggered_grid_view. The flutter_staggered_grid_view is more flexible to create GridView with various size.

flutter image placeholder (FadeInImage) without setting fixed size?

How can I use something like FadeInImage to setup and hold the layout of a page before images have downloaded? As expected, just using Image.network causes the page to jump around once the images load and become visible. I don't have set image sizes (i allow them to resize based on screen/etc) and want to avoid setting a fixed height. The images load and show fine using FadeInImage however the screen still jumps a lot.
#override
Widget build(BuildContext context) {
return
Scaffold(
appBar: AppBar(
title: Text('Welcome!'),
),
drawer: sideDrawer(),
body: new SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(height: 28),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(width: 64),
Flexible( // tried Expanded too
child:
FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: 'https://www.xyzserver.com/images/dummyimage.png',
fit: BoxFit.scaleDown,
),
),
SizedBox(width: 64),
],
),
SizedBox(height: 28),
Text("stuff below the image"),
],
),
)
);
}
When using "Expanded" the image row/area is very tall vertically (the text "stuff below the image" is at the bottom of the page so the page jumps up when the image loads. When using "Flexible" the image row/area is somewhat smaller and the page jumps down when the image loads.
In the image I'm playing around with now, it's a horizontal image that is larger than the available screen space, so it will get scaled down. I guess I was thinking that since flutter can calculate the max width of what's available to the expanded/flexible, it should be able to calculate the height, but as I write this I'm thinking that's impossible since it doesn't know the height/width ratio so it can't predict the height.
How can I set this up so that images can be resized and show correctly and the page doesn't jump around? I can't imagine using fixed height/width settings is the way to go. Maybe my approach to images is all wrong and I should always use a set height/width although that can be rather difficult when people are allowed to upload their own images/etc.
Thanks!
Check this one
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Uint8List? imageData;
Future<Uint8List> dosometinhdd() async {
return (await rootBundle.load('assets/images/a.png')).buffer.asUint8List();
}
#override
void initState() {
dosometinhdd().then((value) {
setState(() {
imageData = value;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Welcome!'),
),
body: new SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
imageData != null
? Expanded(
child: FadeInImage.memoryNetwork(
placeholder: imageData!,
image:
'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1200px-Image_created_with_a_mobile_phone.png',
fit: BoxFit.scaleDown,
),
)
: Container(),
Text("stuff below the image"),
],
),
),
));
}

RenderFlex overflowed only on screen transition but no visual issues

First, let me start with I am new to Flutter and this is my first app.
I am running into an issue where I am getting the error:
════════ Exception caught by rendering library ═════════════════════════════════
A RenderFlex overflowed by 24 pixels on the bottom.
The relevant error-causing widget was
Column
lib/widgets/home_screen_cta.dart:42
════════════════════════════════════════════════════════════════════════════════
For the 4 buttons on my home screen.
The weird part is the screen looks fine. No yellow and black bars or anything. This only happens when I transition from the login screen to the home screen. If I move around the app, this does not happen. I do notice it on another form once it is submitted (only for the first submission, not for any others). It also looks bad for the first animation but after that it looks fine.
I was wondering if I should just ignore this but it seems like that is a bad idea.
I have tried changing this to use many other options. This includes:
Changed between Expanded and Flexible
Tried using a container/SizedBox instead
Tried to find another solution for the CTA Item but couldn't find anything that would do this look
At this point, I understand it's a overflow issue but there are no indications of an overflow appearing in the app.
I am trying to make the app responsive in nature so I believe I am doing okay for that. This RenderFlex overflow issue is really puzzling. The fact I don't see the yellow/black bars in the app makes me think this is happening during a transition and I wonder if I should be concerned about it?
Is there a way to control these animations? I really do not like them and I feel it's the transition causing the overflow issue... I'm a bit lost on this.
My code:
Home Screen (HomeDashboard):
import 'package:flutter/material.dart';
import '../app/components/app_drawer.dart';
import '../app/components/app_bar.dart';
import '../lang/app_localizations.dart';
import '../widgets/clock_text.dart';
import '../widgets/home_screen_cta.dart';
import '../widgets/home_screen_dashboard.dart';
import '../widgets/logo_horizontal.dart';
class HomeDashboard extends StatelessWidget {
const HomeDashboard({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: FreshFlowAppBar.get(title: AppLocalizations.of(context)!.title),
drawer: const AppDrawer(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: LogoHorizontal(),
),
Flexible(
flex: 1,
fit: FlexFit.loose,
child: ClockText(),
),
Flexible(
flex: 6,
fit: FlexFit.tight,
child: HomeScreenDashboard(),
),
Flexible(
flex: 1,
fit: FlexFit.tight,
child: HomeScreenCta(),
),
],
),
),
),
);
}
}
HomeScreenCta:
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:fresh_flow/services/user.service.dart';
import 'package:provider/provider.dart';
import '../lang/app_localizations.dart';
import '../screens/home/pre_op_entry.dart';
import '../screens/home/prod_entry.dart';
import '../screens/home/post_op_entry.dart';
import '../screens/home/downtime_entry.dart';
import '../services/pre-op.service.dart';
import '../utils/size_helpers.dart';
class HomeScreenCta extends StatefulWidget {
const HomeScreenCta({Key? key}) : super(key: key);
#override
State<HomeScreenCta> createState() => _HomeScreenCtaState();
}
class _HomeScreenCtaState extends State<HomeScreenCta> {
bool _isPreOpSetupCompleted = false;
/// Returns a Widget for the CTA item
Widget _getCtaItem(
BuildContext context,
String title,
IconData iconData,
String routeName,
bool isEnabled,
) {
return GestureDetector(
onTap: () {
if (isEnabled) {
Navigator.of(context).pushNamed(routeName);
}
},
child: Opacity(
opacity: isEnabled ? 1.0 : 0.4,
child: Column( // This is causing RenderFlex overflow issue
children: [
Icon(
iconData,
size: displayHeight(context) * .05,
),
const SizedBox(
height: displayHeight(context) * .03,
),
Text(
title,
style: TextStyle(
fontSize: displayWidth(context) * 0.03,
),
)
],
),
),
);
}
#override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
// Define services
final _preOpService = Provider.of<PreOpService>(
context,
listen: false, // No need to listen
);
final _userService = Provider.of<UserService>(
context,
listen: false, // No need to listen
);
_preOpService.init(_userService.getId()!);
// Only Pre-Op Entry is allowed until it is complete all others need to be disabled
if (_preOpService.hasCompletedFirstPreopOfTheDay()) {
setState(() {
_isPreOpSetupCompleted = true;
});
}
final _width =
(displayWidth(context) - MediaQuery.of(context).padding.left) *
0.24;
final _children = [
SizedBox(
width: _width,
child: _getCtaItem(
context,
l10n!.preOpEntry,
FontAwesome.wrench,
PreOpEntry.routeName,
true,
),
),
SizedBox(
width: _width,
child: _getCtaItem(
context,
l10n.prodEntry,
MaterialCommunityIcons.account_hard_hat,
ProdEntry.routeName,
_isPreOpSetupCompleted,
),
),
SizedBox(
width: _width,
child: _getCtaItem(
context,
l10n.postOpEntry,
Icons.task_alt,
PostOpEntry.routeName,
_isPreOpSetupCompleted,
),
),
SizedBox(
width: _width,
child: _getCtaItem(
context,
l10n.downtimeEntry,
Icons.flag_sharp,
DowntimeEntry.routeName,
_isPreOpSetupCompleted,
),
),
];
return Center(
child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
children: _children,
),
);
}
}

Flutter putting Image.network into Dart isolate

Here i have a simple class that i show images from network with Image.network. as i used this class into a Listview, during scrolling that cause of a bit lag and i think i can fix it with Isolate.
after reading some documentation about this feature in Dart i'm not sure how can i put this this class or part of that such as a simple widget into that.
class InistaLikers extends HookWidget {
final List<String> imageUrls;
const InistaLikers({required this.imageUrls});
#override
Widget build(BuildContext context) {
late double _width = 0;
late int count = 4;
final _orientation = MediaQuery.of(context).orientation;
final _screenWidth = MediaQuery.of(context).size.width;
useEffect((){
if(_orientation == Orientation.portrait){
_width = _screenWidth* 0.39;
count = 4;
}else if(_orientation == Orientation.landscape){
_width = (_screenWidth / 2) * 0.39;
count = 3;
}
});
return Container(
width: _width,
height: 35.0,
child: Row(
children: [
Expanded(
child: Stack(
children: List.generate(
count,
(i) {
return Positioned(
right: imageUrls.length + (20.0 * i),
child: ClipOval(
child: Container(
width: 35,
height: 35,
child: Image.network(
imageUrls[i],
),
),
),
);
},
).toList(),
),
),
ClipOval(
child: Container(
width: 35,
height: 35,
child: Image.network(
imageUrls.last,
),
),
),
],
),
);
}
}
You cannot build a widget through an isolate as dart:ui which is used to render your widgets is only available on the main isolate. Moreover, Image.network already uses an ImageStream to manage the recuperation of an online image.
If you have some performance issues you should try to optimize the way you are building your widgets, for example if it was not the case already you should use ListView.builder if you have a lot of widgets to render.
You can find some "Performance best practices" documentation on the flutter website or the article Flutter Performance Tips written by Hasan Basri Bayat.
Here's some of the tips described in this article which you can apply to improve the performances of your app:
Use Widgets Over Functions
// Don't do this
[
_buildHeaderWidget(),
_buildMainWidget(context),
_buildFooterWidget(),
]
// Do this
[
HeaderWidget(),
MainWidget(),
FooterWidget(),
]
Use const where possible
const _myFixedHeight = 48.0;
Use const constructors whenever possible
class CustomWidget extends StatelessWidget {
const CustomWidget();
#override
Widget build(BuildContext context) {
// ...
}
}
Use nil instead of Container()
// Don't do this
Column(
children: [
text != null ? Text(text) : Container(),
],
)
// Do this
Column(
children: [
if (text != null)
Text(text),
],
)
And you'll find some more tips in the article.

Resizing parent widget to fit child post 'Transform' in Flutter

I'm using Transforms in Flutter to create a scrolling carousel for selecting from various options.
This uses standard elements such as ListView.builder, which all works fine, aside from the fact that the parent widget of the Transform doesn't scale down to fit the content as seen here:
Here's the code used to generate the 'card' (there was actually a Card in there, but I've stripped it out in an attempt to get everything to scale correctly):
return Align(
child: Transform(
alignment: Alignment.center,
transform: mat,
child: Container(
height: 220,
color: color,
width: MediaQuery.of(context).size.width * 0.7,
child: Text(
offset.toString(),
style: TextStyle(color: Colors.white, fontSize: 12.0),
),
),
),
);
}
Even if I remove the 'height' parameter of the Container (so everything scales to fit the 'Text' widget), the boxes containing the Transform widgets still have the gaps around them.
Flutter doesn't seem to have any documentation to show how to re-scale the parent if the object within is transformed - anyone here knows or has any idea of a workaround?
EDIT: The widget returned from this is used within a build widget in a Stateful widget. The stack is Column > Container > ListView.builder.
If I remove the Transform, the Containers fit together as I'd like - it seems that performing a perspective transform on the Container 'shrinks' it's content (in this case, the color - check the linked screen grab), but doesn't re-scale the Container itself, which is what I'm trying to achieve.
I have a tricky solution for this: addPostFrameCallback + overlay.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
// ignore: must_be_immutable
class ChildSizeWidget extends HookWidget {
final Widget Function(BuildContext context, Widget child, Size size) builder;
final Widget child;
final GlobalKey _key = GlobalKey();
OverlayEntry _overlay;
ChildSizeWidget({ this.child, this.builder });
#override
Widget build(BuildContext context) {
final size = useState<Size>(null);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) {
_overlay = OverlayEntry(
builder: (context) => Opacity(
child: SingleChildScrollView(
child: Container(
child: child,
key: _key,
),
),
opacity: 0.0,
),
);
Overlay.of(context).insert(_overlay);
WidgetsBinding.instance.addPostFrameCallback((timestamp) {
size.value = _key.currentContext.size;
_overlay.remove();
});
});
return () => null;
}, [child]);
if (size == null || size.value == null) {
return child;
} else {
return builder(context, child, size.value);
}
}
}
Usage:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class HomeView extends HookWidget {
#override
Widget build(BuildContext context) {
final change = useState<bool>(false);
final normal = Container(
color: Colors.blueAccent,
height: 200.0,
width: 200.0,
);
final big = Container(
color: Colors.redAccent,
height: 300.0,
width: 200.0,
);
return Column(
children: [
Container(
alignment: Alignment.center,
child: ChildSizeWidget(
child: change.value ? big : normal,
builder: (context, child, size) => AnimatedContainer(
alignment: Alignment.center,
child: SingleChildScrollView(child: child),
duration: Duration(milliseconds: 250),
height: size.height,
),
),
color: Colors.grey,
),
FlatButton(
child: Text('Toggle child'),
onPressed: () => change.value = !change.value,
color: Colors.green,
),
],
);
}
}
I have a menu with several options, they have different height and with the help of the animations this is ok, it's working really nice for me.
Why are you using Align, as much as I can see in your code, there is no property set or used, to align anything. So try removing Align widget around Transform.
Because according to the documentation, Transform is such a widget that tries to be the same size as their children. So that would satisfy your requirement.
For more info check out this documentation: https://flutter.dev/docs/development/ui/layout/box-constraints
I hope it helps!