ListView lagging only when widgets in the list are Video_Player widgets - flutter

I'm trying to achieve a scrollable List with videos. I'm using the video_player widget and wrapping the player in a Card with a simple button.
Now i noticed that whenever I use ListView.builder with videos in the list, it is extremely lagging, especially when scrolling back up. i'm posting a GiF bellow if you would like to see the behaviour.
I have this problem ONLY when I have Videos in the list.
If I replace the videos with a simple Image widget, the scrolling is smooth and runs as intended. (Also provided a GiF below)
When I scroll through the list I get this message in my Console:
flutter: Another exception was thrown: setState() or markNeedsBuild() called during build.
And I think (but not sure) that this is the cause of the problem, maybe the way I implemented the video_player plugin (?)
class VideoPlayPause extends StatefulWidget {
VideoPlayPause(this.controller);
final VideoPlayerController controller;
#override
_VideoPlayPauseState createState() => _VideoPlayPauseState();
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
//This Part here
_VideoPlayPauseState() {
listener = () {
setState(() {});
};
}
Maybe its setting state every time I scroll ?
I tried flutter run --release but saw no difference at all.
I'm running the app on a physical Iphone X.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MVP_Test1'),
),
body: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: videoUrl.length,
itemBuilder: (context, i) {
return Card(
child: Column(
children: <Widget>[
Column(
children: <Widget>[
const ListTile(
leading: Icon(Icons.live_tv),
title: Text("Nature"),
),
Stack(
alignment: FractionalOffset.bottomRight +
const FractionalOffset(-0.1, -0.1),
children: <Widget>[
// If I replace this with Image.asset("..."), the scrolling is very smooth.
AssetPlayerLifeCycle(
videoUrl[i],
(BuildContext context,
VideoPlayerController controller) =>
AspectRatioVideo(controller)),
]),
],
),
ButtonTheme.bar(
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('ADD VIDEO'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
},
),
);
}
Result when I run Flutter Analyze
flutter analyze
Analyzing mvp_1...
No issues found! (ran in 1.9s)
You can see how when scrolling back up it looks like the app is skipping frames or something. Here's a video:
https://giphy.com/gifs/u48BNQ13r15Zay5SnN
Here's a video with photos instead of videos:
https://giphy.com/gifs/236RKyA8y1pfecmR1d

Related

How to create custom header which hides and shows on scroll, like Facebook app

I'm trying to replicate a custom header, above a ListView, which hides and shows when the user scrolls the list. Very specifically, I want it to behave like the Facebook app... as soon as the user scrolls down, the header slides up out of view. Then, no matter how far down the list you are, when you scroll back up the header slides back into view immediately.
I've been playing with various Slivers, AnimatedContainers etc, but I can't get this exact behaviour.
SliverAppBar seems the closest, but it seems to have a predetermined structure, and I can't see a way to make it completely customizable.
SliverPersistentHeader and SliverToBoxAdapter both seem to remain fixed in place, and don't reappear when you scroll back up.
Any ideas on now to achieve this please?
I made something like this a while ago but only with a searchbar as the content of the SliverAppBar and because the SliverAppBar needs a predetermined size to be built I used a work-around like this.
class _ListScreenState extends State<ListScreen> {
final GlobalKey _flexibleSpaceBarKey = GlobalKey();
late Size sizeFlexibleSpaceBar;
bool _visible = true;
getSizeAndPosition() {
RenderBox _cardBox = _flexibleSpaceBarKey.currentContext!.findRenderObject() as RenderBox;
sizeFlexibleSpaceBar = _cardBox.size;
}
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) => getSizeAndPosition());
Future.delayed(Duration(microseconds: 1)).then((value) {
setState(() {
_visible = false;
});
});
}
Widget _copyFlexibleSpaceBar() {
return Visibility(
visible: _visible,
key: _flexibleSpaceBarKey,
child: _buildSearchbar(),
);
}
Widget _buildSearchbar() {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 12.0,
),
child: MyCustomSearchBar(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Example-Title'),
elevation: 0.0,
),
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
_copyFlexibleSpaceBar(),
if (!_visible)
Expanded(
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) =>
<Widget>[
SliverAppBar(
pinned: true,
floating: true,
toolbarHeight: 0.0,
expandedHeight: sizeFlexibleSpaceBar.height,
elevation: 4.0,
flexibleSpace: FlexibleSpaceBar(
background: _buildSearchbar(),
),
),
],
body: Container() // Your content
),
),
],
),
),
);
}
}
(Not sure if it works because I copied it from my git-repo and changed/removed some stuff)
So what I did there was, to build my custom header and immediately make it invisible, thus I can get the actual size and can use it to build the SliverAppBar.
I have to add, this idea is not mine, I got it from another post which I can't find right now.
Hope it somehow helps you.

Unable to position AndroidView with video

I am using flutter 3.0.0
In my application I am displaying a native video using platform view.
The video is displaying but it is always displaying on upper left corner and it covers other
widgets even they are in a stack.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Stack(
children: [
Center(
child: ConstrainedBox(
constraints:
const BoxConstraints.expand(height: 200, width: 200),
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child:
const AndroidView(viewType: 'remote-video'),
),
),
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
MethodChannels.coreMethodChannel
.invokeMethod("load");
},
child: const Text('Invoke'),
),
),
],
),
],
),
);
}
This is how it looks when I run the code
As you can see it is displaying over everything.
Can you provide some advice on how to fix this?
As of 31/07/2022 this seems to be a bug in flutter >= 3.0.0. Following the solution in this question Flutter AndroidView Widget I downgraded to 2.10.5 and then it worked as expected. Hopefully the flutter team will resolve it shortly.

Flutter custom widget fails to display

I am a newbie to Flutter and Dart, and am trying to create a clickable container class that is based on the
GestureDetector class. My code for my container is shown below:
class CustomWidget extends GestureDetector
{
final aTitle;
CustomWidget(this.aTitle)
{
onTap() {
print("$aTitle was pressed!");
}
child: new Container(
padding: const EdgeInsets.all(8),
child: Text(aTitle),
color: Colors.blueGrey[200],
);
}
}
I am attempting to display this widget in my main application screen using the following code in the body of my app widget:
body: CustomScrollView(
primary: false,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.all(20),
sliver: SliverGrid.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 2,
children: <Widget>[
CustomWidget('Test Display'),
],
),
),
],
),
I seem to have two problems: (1) my development environment is telling me that my onTap() method is "unused", indicating
that it will not capture tap events, and (2) that doesn't seem to matter much because the CustomWidget instance that I am
creating in the app widget is not appearing on my screen.
I am clearly missing something. Can someone help me correct my code so that my custom widget will be displayed and process
onTap events?
Generally speaking, the CustomWidget is a good idea, but inheritance is the wrong implementation. Flutter strongly favors composition over inheritance.
Your custom widget using composition:
class CustomWidget extends StatelessWidget {
final String title;
const CustomWidget(this.title);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print("$title was pressed!");
},
child: new Container(
padding: const EdgeInsets.all(8),
child: Text(title),
color: Colors.blueGrey[200],
));
}
}

Flutter bottomNavigationBar (change only body section)

Ok so I have got the following bottomNavigationBar working, however it's not clean enough for me and I am hoping there is a better way that someone knows.
Basically I only want to change the body section and not the full page.
However I want to have each section in different classes to keep it neat (ideally new .dart files - as they will all have different functions)
Currently all the page info is inside the body tag
body: PageView(
controller: _myPage,
onPageChanged: (int) {
print('Page Changes to index $int');
},
children: <Widget>[
Center(
child: Container(
child: Text('Empty Body 0'),
),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('images/pic1.jpg'),
Text('images/pic2.jpg'),
Text('images/pic3.jpg'),
],
),
),
Center(
child: Container(
child: Text('Empty Body 2'),
),
),
Center(
child: Container(
child: Text('DRN1 app is made using Google Flutter. While every attempt was made to make sure that this app works on as many devices as possible. We are unable to test every device. If you have issues running this app please let us know'),
),
)
],
physics: NeverScrollableScrollPhysics(), // Comment this if you need to use Swipe.
),
what I would like is something like this.
body: PageView(
controller: _myPage,
onPageChanged: (int) {
print('Page Changes to index $int');
},
children: <Widget>[
home(),
news() , // this is the main body for home
shows(), // this one shows the class shows() which fetches JSON and returns a different body layout
about(), // about text
]
Does anyone know of a better way to do this?
What you can do is create separate Dart files and import them into your main.dart
Then inside your State class define a list which contains the pages that you want to show
List<Widget> _myPage = <Widget>[Page1class(),Page2class(),Page3class()];
After that you can use the below code which looks quite clean. Using the builder() method will allow you to create pages based on the size of _myPage,
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: Text('sdf')),
body: PageView.builder(
itemBuilder: (context, position) => _myPage[position],
itemCount: _myPage.length,
),
)
);
Here is a sample program: PageView sample

Flutter snackbar alternative or easier method than wrapping everything in Scaffold?

I'm working on my first Flutter app (debugging on my Android phone). I have a list with row items. When you long-press the row, it copies the content into the user's clipboard. This is working great!
But I need to let the user know that the content was copied.
I've attempted to follow many tutorials on trying to get the row surrounded by a build method or inside a Scaffold, but I can't get any to work. Is there an alternative method to notifying the user (simply) that something like "Copied!" took place?
Notice the commented out Scaffold.of(... below. It just seems like there must be an easier method to notifying the user other than wrapping everything in a Scaffold. (and when I try, it breaks my layout).
import 'package:flutter/material.dart';
import 'package:my_app/Theme.dart' as MyTheme;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';
class RowRule extends StatelessWidget {
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'],
style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'],
style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
// Scaffold.of(context).showSnackBar(SnackBar
// (content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(
vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
)),
)
],
),
),
));
}
}
The goal is to have a page like this (see image), which I have, and it works and scrolls...etc, but I cannot get it to work with a Scaffold, and therefore, haven't been able to use the snackbar. Each "Row" (which this file is for) should show a snackbar on longPress.
You can use GlobalKey to make it work the way you want it.
Since I don't have access to your database stuff, this is how I gave you an idea to do it. Copy and paste this code in your class and make changes accordingly. I also believe there is something wrong in your RowRule class, can you just copy the full code I have given you and run?
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF).withOpacity(0.9),
key: _key,
body: Column(
children: <Widget>[
Container(
color: Color.fromRGBO(52, 56, 245, 1),
height: 150,
alignment: Alignment.center,
child: Container(width: 56, padding: EdgeInsets.only(top: 12), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.yellow)),
),
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 120,
itemBuilder: (context, index) {
return Container(
color: Colors.white,
margin: const EdgeInsets.all(4),
child: ListTile(
title: Text("Row #$index"),
onLongPress: () => _key.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Copied \"Row #$index\""))),
),
);
},
),
),
],
),
);
}
}
These is a simple plugin replacement for the Snackbar named "Flushbar".
You can get the plugin here - https://pub.dartlang.org/packages/flushbar
You don't have to take care of any wrapping of widgets into scaffold also you get a lot of modifications for you like background gradient, adding forms and so on into Snackbar's and all.
Inside your onLongPressed in GestureDetectore you can do this.
onLongPressed:(){
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
Flushbar(
message: "Copied !!",
duration: Duration(seconds: 3),
)..show(context);
}
This will display the snackbar in you app where you would want to see it also you can get a lot of modification available to you so the you can make it look as per your app.
There are couple of things you need to do, like use onPressed property of the FlatButton it is mandatory to allow clicks, wrap your GestureDetector in a Scaffold. I have further modified the code so that it uses GlobalKey to make things easy for you.
Here is the final code (Your way)
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
onPressed: () => print("Handle button press here"),
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
),
),
)
],
),
),
),
),
);
}
}
I made a dropdown banner package on pub that allows you to easily notify users of errors or confirmation of success. It's a work in progress as I continue to add visually rich features.
I am not sure if your build() method is completed or you are yet to change it, because it consist of many widgets which are just redundant. Like there is no need to have Container in Container and further Padding along with a FlatButton which would make complete screen clickable. Also having Column won't be a good idea because your screen may overflow if you have more data. Use ListView instead.
So, if you were to take my advice, use this simple code that should provide you what you are really looking for. (See the build() method is of just 5 lines.
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
if (!ruleGroup['details'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: ListView(children: _buildChildren()),
);
}
}
I read your comments on all answers and here is my conslusion:
You need ScaffoldState object that is just above the widget in tree to show Snackbar. You can either get it through GlobalKey as many have suggested. Fairly simple if the Scaffold is created inside build of the widget, but if it is outside the widget (in your case) then it becomes complicated. You need to pass that key, wherever you need it through Constructor arguments of child widgets.
Scaffold.of(context) is a very neat way to just do that. Just like an InheritedWidget, Scaffold.of(BuildContext context) gives you access of the closest ScaffoldState object above the tree. Else it could be a nightmare to get that instance (by passing it through as constructor arguments) if your tree was very deep.
Sorry, to disappoint but I don't think there is any better or cleaner method than this, if you want to get the ScaffoldState that is not built inside build of that widget. You can call it in any widget that has Scaffold as a parent.