Flutter putting Image.network into Dart isolate - flutter

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.

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 InAppWebView inside SingleChildScrollView

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.

How to play video in custom grid just like Instagram

I have a search field similar to Instagram, I achieved it using flutter_staggered_grid_view and able to shows videos in the big boxes, but how can I play them when the user scroll. I am trying to use inview_notifier_list, but unable to succeed. I want to play the video only when the user scrolls down. How can this be achieved?
I achieved this using VisibilityDetector. If 80% of the video is visible then play otherwise pause.
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final deviceRatio = size.width / size.height;
return Scaffold(
body: Center(
child: controller.value != null && controller.value.initialized
? VisibilityDetector(
key: ObjectKey(controller),
onVisibilityChanged: (visibility) {
if (visibility.visibleFraction > 0.8) {
controller.play();
} else {
controller.pause();
}
},
child: CachedVideoPlayer(controller)
)
: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
child: Image.network(
widget.image,
fit: BoxFit.cover,
),
),
),
],
),),
);
}
}

Wrap stacked cards in flutter

I'm trying to stack some playing cards like the image shows. BUT I want the cards to wrap into a new line when there is no more space on the device.
This is what I have so far
class CollectionOfCards extends StatelessWidget {
List<PlayingCard> cards;
CollectionOfCards(this.cards);
#override
Widget build(BuildContext context) {
return Stack(
children: [
...cards
.asMap()
.map(
(key, value) => MapEntry(
key,
Positioned(
child: PlayingCardWidget(value),
left: 35.0 * key,
),
),
)
.values
.toList(),
],
overflow: Overflow.visible,
);
}
}
Any ideas how to do this? I imagine it is somewhat a common task.
Another idea would be to make a fan of cards that can always fit but I imagine thats harder to achieve!
Assume we want to have 2 rows of cards, each containing 10 cards. We need to use Positioned in a way that every card is positioned according to its index in the row(from 0 to 9) and its column(column 0 or column 1). So we need to set top and left in the Positioned widget. The 0.7 * cardHeight in top is for the vertical stacking, you can change that to cardHeight if you don't want to have vertical stacking.
Note: Set width and height of the cards using MediaQuery size. I didn't have the cards so I just made empty containers instead.
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _cards = [];
void _initCards() {
double cardHeight = 400 / 2;
double cardWidth = 2500 / 20;
for (int i = 0; i < 20; i++) {
_cards.add(Positioned(
left: ((i >= 10) ? i - 10 : i) * cardWidth / 2,
top: (i >= 10) ? 0.7 * cardHeight : 0,
child: Card(
elevation: 10,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Container(width: cardWidth, height: cardHeight),
),
));
}
}
#override
void initState() {
super.initState();
_initCards();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Stack(
children: _cards
),
decoration: BoxDecoration(
color: Colors.yellow,
),
),
);
}
}
Result:
Just for reference. I ended up with solution like this inspired by #Mobina's answer
class CollectionOfCards extends StatelessWidget {
final List<PlayingCard> cards;
final double stackSkew = 0.4;
CollectionOfCards(this.cards);
#override
Widget build(BuildContext context) {
final double widgetWidth = MediaQuery.of(context).size.width - 16;
final double cardWith = PlayingCardWidget.defaultWidth(context);
final int cardsPerRow =
(widgetWidth / cardWith / stackSkew - 1 / stackSkew).floor();
return Stack(
children: [
...cards
.asMap()
.map(
(index, card) {
return MapEntry(
index,
Positioned(
child: Draggable(
childWhenDragging: Container(),
feedback: PlayingCardWidget(card: card),
child: PlayingCardWidget(
card: card,
),
),
left: stackSkew *
cardWith *
(index - (cardsPerRow) * (index / cardsPerRow).floor()),
top: (card.heightFromWidth(cardWith) + 2) *
(index / cardsPerRow).floor(),
),
);
},
)
.values
.toList(),
],
overflow: Overflow.visible,
);
}
}
It seems to work flawless.
One way you can do this is by passing left as well as top parameter to Positioned calculated based on -
Screen width
Card index ("key" in your code)
Card width
Card height
See the demo on dartpad - https://dartpad.dev/d36e018c1f1c7a6cd4b91d5b09e69a7c
Try using Wrap() widget inside a Column()
Column(
children: <Widget>[
Wrap(
children: <Widget>[
//your card widget
]
)
]
)

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!