VideoEditorController breaks all VideoPlayers in parent widgets - ios

I have a page where I crop and trim the video.
When I initialize a VideoEditorController and for example edit the video, or just navigate back to previous page. And then I get method channel error and all CachedVideoPlayer widgets show a black screen. if we rebuild the widget with CachedVideoPlayer it'll play. And if we try to repeat the same process again for some strange reason CachedVideoPlayers won't crash.
I'm testing on a real device(Iphone 12 mini).
Error:
MissingPluginException(No implementation found for method cancel on channel flutter.io/videoPlayer/videoEvents171)
How I initialize VideoEditorController
#override
void initState() {
super.initState();
_controller = VideoEditorController.file(widget.file,
maxDuration: const Duration(seconds: 30))
..initialize().then((_) => setState(() {
_controller.preferredCropAspectRatio = widget.aspectRatio;
showTrimmer = true;
}));
}
CropScreen
class CropScreen extends StatefulWidget {
const CropScreen({
Key? key,
required this.file,
required this.exportedFile,
required this.aspectRatio,
}) : super(key: key);
final File file;
final double aspectRatio;
final Function(File) exportedFile;
#override
State<CropScreen> createState() => _CropScreenState();
}
class _CropScreenState extends State<CropScreen> {
late VideoEditorController _controller;
final double height = 60;
bool showTrimmer = false;
final ValueNotifier<bool> _isLoadingNotifier = ValueNotifier(false);
void show() {
_isLoadingNotifier.value = true;
}
void hide() {
_isLoadingNotifier.value = false;
}
#override
void initState() {
super.initState();
_controller = VideoEditorController.file(widget.file,
maxDuration: const Duration(seconds: 30))
..initialize().then((_) => setState(() {
_controller.preferredCropAspectRatio = widget.aspectRatio;
showTrimmer = true;
}));
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: _isLoadingNotifier,
builder: (context, value, child) {
return Stack(
children: [
Stack(
children: [
Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(30),
child: Column(children: [
Row(children: [
Expanded(
child: IconButton(
onPressed: () => _controller
.rotate90Degrees(RotateDirection.left),
icon: const Icon(
Icons.rotate_left,
color: white,
),
),
),
Expanded(
child: IconButton(
onPressed: () => _controller
.rotate90Degrees(RotateDirection.right),
icon: const Icon(
Icons.rotate_right,
color: white,
),
),
)
]),
const SizedBox(height: 15),
Expanded(
child: CropGridViewer(
controller: _controller, horizontalMargin: 60),
),
const SizedBox(
height: 15,
),
showTrimmer
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: _trimSlider(context))
: SizedBox(height: 100),
const SizedBox(height: 15),
Row(children: [
Expanded(
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Center(
child: Text(
"Cancel",
style: MainTheme.of(context)
.bodyText1
.copyWith(fontWeight: FontWeight.w600),
),
),
),
),
buildSplashTap("21:9", 21 / 9,
padding:
const EdgeInsets.symmetric(horizontal: 10)),
buildSplashTap("1:1", 1 / 1),
buildSplashTap("4:5", 4 / 5,
padding:
const EdgeInsets.symmetric(horizontal: 10)),
buildSplashTap("NO", null,
padding: const EdgeInsets.only(right: 10)),
Expanded(
child: IconButton(
onPressed: () async {
//2 WAYS TO UPDATE CROP
//WAY 1:
show();
_controller.updateCrop();
await _controller.exportVideo(
onCompleted: (file) {
print(file!.path);
widget.exportedFile(file);
hide();
Navigator.pop(context);
});
/*WAY 2:
controller.minCrop = controller.cacheMinCrop;
controller.maxCrop = controller.cacheMaxCrop;
*/
},
icon: Center(
child: Text(
"Crop",
style: MainTheme.of(context)
.bodyText1
.copyWith(fontWeight: FontWeight.w600),
),
),
),
),
]),
]),
),
),
),
],
),
if (value)
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 4.0, sigmaY: 4.0),
child: const Opacity(
opacity: 0.8,
child:
ModalBarrier(dismissible: false, color: Colors.black),
),
),
if (value)
Center(
child: FutureBuilder(
future: Future.delayed(Duration(milliseconds: 500)),
builder: (context, snapshot) {
return const SpinKitFadingCircle(
size: 60, color: primary);
},
),
),
],
);
});
}
Widget buildSplashTap(
String title,
double? aspectRatio, {
EdgeInsetsGeometry? padding,
}) {
return InkWell(
onTap: () => _controller.preferredCropAspectRatio = aspectRatio,
child: Padding(
padding: padding ?? EdgeInsets.zero,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.aspect_ratio, color: Colors.white),
Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold, color: white),
),
],
),
),
);
}
List<Widget> _trimSlider(BuildContext context) {
return [
AnimatedBuilder(
animation: _controller.video,
builder: (_, __) {
final duration = _controller.video.value.duration.inSeconds;
final pos = _controller.trimPosition * duration;
final start = _controller.minTrim * duration;
final end = _controller.maxTrim * duration;
return Padding(
padding: EdgeInsets.symmetric(horizontal: height / 4),
child: Row(children: [
Text(
formatter(Duration(seconds: pos.toInt())),
style: MainTheme.of(context).bodyText1,
),
const Expanded(child: SizedBox()),
OpacityTransition(
visible: _controller.isTrimming,
child: Row(mainAxisSize: MainAxisSize.min, children: [
Text(
formatter(Duration(seconds: start.toInt())),
style: MainTheme.of(context).bodyText1,
),
const SizedBox(width: 10),
Text(
formatter(Duration(seconds: end.toInt())),
style: MainTheme.of(context).bodyText1,
),
]),
)
]),
);
},
),
Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(vertical: height / 4),
child: TrimSlider(
controller: _controller,
height: height,
horizontalMargin: height / 4,
child: TrimTimeline(
controller: _controller,
margin: const EdgeInsets.only(top: 10))),
)
];
}
String formatter(Duration duration) => [
duration.inMinutes.remainder(60).toString().padLeft(2, '0'),
duration.inSeconds.remainder(60).toString().padLeft(2, '0')
].join(":");
}
To use VideoEditorController you need to add this package:
video_editor: ^1.4.1
Dart SDK:
sdk: ">=2.16.1 <3.0.0"

Related

Change background color of ListTile upon selection and custom height of tiles flutter

I've made a GridView, but now I have some ListTiles in this GridView that I need to be selected Upon selection, I want the background color to change.
And I am also facing one more issue, I want to custom the heights of these ListTile, but I am not getting any success to do so. I am gonna attach the picture the output that I am getting and the output that I want.
What I am getting:
What I want:
Here is the code:
class _SelectLanguageNewState extends State<SelectLanguageNew> {
List<Results> listResponseData;
bool _color;
#override
void initState() {
// TODO: implement initState
super.initState();
getLang();
_color = false;
}
Future getLang() async {
await getLanguage().then((GetLanguageResponse getlanguage)
{
if(getlanguage.isSuccess)
{
setState(() {
listResponseData = getlanguage.responseData.listResults;
});
}
});
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height - kToolbarHeight - 24) / 2;
final double itemWidth = size.width / 2;
void _onTileClicked(int index){
debugPrint("You tapped on item $index");
}
// Get grid tiles
List<Widget> _getTiles() {
final List<Widget> tiles = <Widget>[];
for (int i = 0; i < listResponseData.length; i++) {
tiles.add(ListTileTheme(
selectedColor: Colors.blue,
child: new InkWell(
child: new Card(
elevation: 5.0,
child: new Container(
color: _color ? Colors.black : Colors.grey,
alignment: Alignment.center,
child: new Text(listResponseData[i].nativeText,
style: TextStyle(color: Colors.white),),
),
),
onTap: () {
setState(() {
_color = !_color;
});
/* showDialog(
barrierDismissible: false,
context: context,
child: new CupertinoAlertDialog(
content: new Container(
width: 40.0,
child: new CircularProgressIndicator()),
actions: <Widget>[
new FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: new Text("OK"))
],
),
);*/
},
),
));
}
return tiles;
}
return Scaffold(
bottomNavigationBar: BottomAppBar(
child: Container(
height: 50.0,
child: InkWell(
onTap: ()=> print('pressed'),
child: Center(
child: Text('Next', style: TextStyle(color: Colors.white,
),
),
),
),
),
color: Colors.blue,
),
body: new Container(
child: listResponseData == null || listResponseData.isEmpty ? new Container(
child: new Text('Loading data......'),
) : new GridView.count(
shrinkWrap: true,
scrollDirection: Axis.vertical,
childAspectRatio:1.0,
crossAxisCount: MediaQuery.of(context).size.width <= 400.0 ? 3 : MediaQuery.of(context).size.width >= 1000.0 ? 5 : 2,
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this would produce 2 rows.
crossAxisSpacing: 5.0,
// Generate 100 Widgets that display their index in the List
children: _getTiles() ,
),
),
);
}
}
Screenshot
Complete solution:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(scaffoldBackgroundColor: Colors.black, brightness: Brightness.dark),
home: Scaffold(
appBar: AppBar(title: Text("Choose Language")),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _languageIndex = -1;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 2.4,
children: <Widget>[
GestureDetector(
child: _buildWidget("English", 0),
onTap: () => setState(() => _languageIndex = 0),
),
GestureDetector(
child: _buildWidget("Española", 1),
onTap: () => setState(() => _languageIndex = 1),
),
GestureDetector(
child: _buildWidget("Française", 2),
onTap: () => setState(() => _languageIndex = 2),
),
GestureDetector(
child: _buildWidget("Gaeilge", 3),
onTap: () => setState(() => _languageIndex = 3),
),
GestureDetector(
child: _buildWidget("العربية", 4),
onTap: () => setState(() => _languageIndex = 4),
),
GestureDetector(
child: _buildWidget("جغتای", 5),
onTap: () => setState(() => _languageIndex = 5),
),
GestureDetector(
child: _buildWidget("ગુજરાતી", 6),
onTap: () => setState(() => _languageIndex = 6),
),
GestureDetector(
child: _buildWidget("हिन्दी", 7),
onTap: () => setState(() => _languageIndex = 7),
),
GestureDetector(
child: _buildWidget("日本語", 8),
onTap: () => setState(() => _languageIndex = 8),
),
GestureDetector(
child: _buildWidget("коми", 9),
onTap: () => setState(() => _languageIndex = 9),
),
GestureDetector(
child: _buildWidget("ਲਹਿੰਦੀ", 10),
onTap: () => setState(() => _languageIndex = 10),
),
GestureDetector(
child: _buildWidget("Schwyzerdütsch", 11),
onTap: () => setState(() => _languageIndex = 11),
),
],
),
SizedBox(
width: double.maxFinite,
height: 44,
child: RaisedButton(
child: Text("SUBMIT"),
onPressed: () {
print("languageIndex = ${_languageIndex}");
},
),
),
],
),
);
}
Widget _buildWidget(String language, int index) {
bool isSelected = _languageIndex == index;
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(color: isSelected ? Colors.blue[300].withOpacity(0.8) : Colors.grey[700]),
color: Colors.grey[900],
),
child: Text(
language,
style: TextStyle(fontSize: 16, color: isSelected ? Colors.blue[400] : null),
),
);
}
}
You can watch which language user selected by using the _languageIndex.

How to show a menu at press/finger/mouse/cursor position in flutter

I have this piece of code which I got from Style clipboard in flutter
showMenu(
context: context,
// TODO: Position dynamically based on cursor or textfield
position: RelativeRect.fromLTRB(0.0, 600.0, 300.0, 0.0),
items: [
PopupMenuItem(
child: Row(
children: <Widget>[
// TODO: Dynamic items / handle click
PopupMenuItem(
child: Text(
"Paste",
style: Theme.of(context)
.textTheme
.body2
.copyWith(color: Colors.red),
),
),
PopupMenuItem(
child: Text("Select All"),
),
],
),
),
],
);
This code works great, except that the popup that is created is at a fixed position, how would I make it so that it pops up at the mouse/press/finger/cursor position or somewhere near that, kind of like when you want to copy and paste on your phone. (This dialog popup will not be used for copy and pasting)
I was able to solve a similar issue by using this answer:
https://stackoverflow.com/a/54714628/559525
Basically, I added a GestureDetector() around each ListTile and then you use onTapDown to store your press location and onLongPress to call your showMenu function. Here are the critical functions I added:
_showPopupMenu() async {
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
await showMenu(
context: context,
position: RelativeRect.fromRect(
_tapPosition & Size(40, 40), // smaller rect, the touch area
Offset.zero & overlay.size // Bigger rect, the entire screen
),
items: [
PopupMenuItem(
child: Text("Show Usage"),
),
PopupMenuItem(
child: Text("Delete"),
),
],
elevation: 8.0,
);
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
}
And then here is the full code (you'll have to tweak a few things like the image, and filling in the list of devices):
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'dart:core';
class RecentsPage extends StatefulWidget {
RecentsPage({Key key, this.title}) : super(key: key);
final String title;
#override
_RecentsPageState createState() => _RecentsPageState();
}
class _RecentsPageState extends State<RecentsPage> {
List<String> _recents;
var _tapPosition;
#override
void initState() {
super.initState();
_tapPosition = Offset(0.0, 0.0);
getRecents().then((value) {
setState(() {
_recents = value;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(height: 25),
Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 40),
child: Center(
child: AutoSizeText(
"Recents",
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 32),
),
),
),
Container(
padding: EdgeInsets.only(left: 30, top: 0),
child: GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Transform.scale(
scale: 2.0,
child: Icon(
Icons.chevron_left,
),
)),
),
],
),
Container(
height: 15,
),
Container(
height: 2,
color: Colors.blue,
),
Container(
height: 10,
),
Flexible(
child: ListView(
padding: EdgeInsets.all(15.0),
children: ListTile.divideTiles(
context: context,
tiles: _getRecentTiles(),
).toList(),
),
),
Container(height: 15),
],
),
),
),
);
}
List<Widget> _getRecentTiles() {
List<Widget> devices = List<Widget>();
String _dev;
String _owner = "John Doe";
if (_recents != null) {
for (_dev in _recents.reversed) {
if (_dev != null) {
_dev = _dev.toUpperCase().trim();
String serial = "12341234";
devices.add(GestureDetector(
onTapDown: _storePosition,
onLongPress: () {
print("long press of $serial");
_showPopupMenu();
},
child: ListTile(
contentPadding: EdgeInsets.symmetric(vertical: 20),
leading: Transform.scale(
scale: 0.8,
child: Image(
image: _myImage,
)),
title: AutoSizeText(
"$_owner",
maxLines: 1,
style: TextStyle(fontSize: 22),
),
subtitle: Text("Serial #: $serial"),
trailing: Icon(Icons.keyboard_arrow_right),
)));
}
}
} else {
devices.add(ListTile(
contentPadding: EdgeInsets.symmetric(vertical: 20),
title: AutoSizeText(
"No Recent Devices",
maxLines: 1,
style: TextStyle(fontSize: 20),
),
subtitle:
Text("Click the button to add a device"),
onTap: () {
print('add device');
},
));
}
return devices;
}
_showPopupMenu() async {
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
await showMenu(
context: context,
position: RelativeRect.fromRect(
_tapPosition & Size(40, 40), // smaller rect, the touch area
Offset.zero & overlay.size // Bigger rect, the entire screen
),
items: [
PopupMenuItem(
child: Text("Show Usage"),
),
PopupMenuItem(
child: Text("Delete"),
),
],
elevation: 8.0,
);
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
}
Use gesture detector's onTapDown like this
GestureDetector(
onTapDown: (TapDownDetails details) {
showPopUpMenu(details.globalPosition);
},
then in this method we use tap down details to find position
Future<void> showPopUpMenu(Offset globalPosition) async {
double left = globalPosition.dx;
double top = globalPosition.dy;
await showMenu(
color: Colors.white,
//add your color
context: context,
position: RelativeRect.fromLTRB(left, top, 0, 0),
items: [
PopupMenuItem(
value: 1,
child: Padding(
padding: const EdgeInsets.only(left: 0, right: 40),
child: Row(
children: [
Icon(Icons.mail_outline),
SizedBox(
width: 10,
),
Text(
"Menu 1",
style: TextStyle(color: Colors.black),
),
],
),
),
),
PopupMenuItem(
value: 2,
child: Padding(
padding: const EdgeInsets.only(left: 0, right: 40),
child: Row(
children: [
Icon(Icons.vpn_key),
SizedBox(
width: 10,
),
Text(
"Menu 2",
style: TextStyle(color: Colors.black),
),
],
),
),
),
PopupMenuItem(
value: 3,
child: Row(
children: [
Icon(Icons.power_settings_new_sharp),
SizedBox(
width: 10,
),
Text(
"Menu 3",
style: TextStyle(color: Colors.black),
),
],
),
),
],
elevation: 8.0,
).then((value) {
print(value);
if (value == 1) {
//do your task here for menu 1
}
if (value == 2) {
//do your task here for menu 2
}
if (value == 3) {
//do your task here for menu 3
}
});
hope it works
Here is a reusable widget which does what you need. Just wrap your Text or other Widget with this CopyableWidget and pass in the onGetCopyTextRequested. It will display the Copy menu when the widget is long pressed, copy the text contents returned to the clipboard, and display a Snackbar on completion.
/// The text to copy to the clipboard should be returned or null if nothing can be copied
typedef GetCopyTextCallback = String Function();
class CopyableWidget extends StatefulWidget {
final Widget child;
final GetCopyTextCallback onGetCopyTextRequested;
const CopyableWidget({
Key key,
#required this.child,
#required this.onGetCopyTextRequested,
}) : super(key: key);
#override
_CopyableWidgetState createState() => _CopyableWidgetState();
}
class _CopyableWidgetState extends State<CopyableWidget> {
Offset _longPressStartPos;
#override
Widget build(BuildContext context) {
return InkWell(
highlightColor: Colors.transparent,
onTapDown: _onTapDown,
onLongPress: () => _onLongPress(context),
child: widget.child
);
}
void _onTapDown(TapDownDetails details) {
setState(() {
_longPressStartPos = details?.globalPosition;
});
}
void _onLongPress(BuildContext context) async {
if (_longPressStartPos == null)
return;
var isCopyPressed = await showCopyMenu(
context: context,
pressedPosition: _longPressStartPos
);
if (isCopyPressed == true && widget.onGetCopyTextRequested != null) {
var copyText = widget.onGetCopyTextRequested();
if (copyText != null) {
await Clipboard.setData(ClipboardData(text: copyText));
_showSuccessSnackbar(
context: context,
text: "Copied to the clipboard"
);
}
}
}
void _showSuccessSnackbar({
#required BuildContext context,
#required String text
}) {
var scaffold = Scaffold.of(context, nullOk: true);
if (scaffold != null) {
scaffold.showSnackBar(
SnackBar(
content: Row(
children: <Widget>[
Icon(
Icons.check_circle_outline,
size: 24,
),
SizedBox(width: 8),
Expanded(
child: Text(text)
)
],
)
)
);
}
}
}
Future<bool> showCopyMenu({
BuildContext context,
Offset pressedPosition
}) {
var x = pressedPosition.dx;
var y = pressedPosition.dy;
return showMenu<bool>(
context: context,
position: RelativeRect.fromLTRB(x, y, x + 1, y + 1),
items: [
PopupMenuItem<bool>(value: true, child: Text("Copy")),
]
);
}

setState() only updates with List.generate [duplicate]

This question already has answers here:
ListView does not refresh whereas attached list does (Flutter)
(2 answers)
Closed 4 years ago.
I'm trying to create an app where I am able to get data from Cloud Firestore and create multiple custom widgets with the data.
The custom widget is eventCardWidget
import 'package:flutter/material.dart';
import 'package:do_and_stuff/settings/theme.dart';
class EventCardWidget extends StatelessWidget {
EventCardWidget(this.eventImage, this.eventTitle, this.eventDescription,
this.eventAddress, this.eventDate, this.eventTime, this.peopleRequired);
String eventTitle;
String eventAddress;
String eventDate;
String eventImage;
String eventTime;
String eventDescription;
int peopleRequired;
#override
Widget build(BuildContext context) {
return Material(
child: GestureDetector(
child: InkWell(
splashColor: ThemeSettings.CardInkwell,
onTap: () {},
child: Card(
elevation: 1.0,
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 14.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: const EdgeInsets.only(right: 19.0),
width: 100.0,
height: 100.0,
child: Image.asset(this.eventImage, scale: .5),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Container(
margin: const EdgeInsets.only(
top: 12.0, bottom: 10.0),
child: Text(
eventTitle,
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
),
Container(
margin: const EdgeInsets.only(right: 10.0),
child: Text(
this.eventAddress,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: Colors.grey[600]),
),
),
],
),
Container(
margin: const EdgeInsets.only(right: 10.0),
child: Text(
this.eventDescription,
style: TextStyle(
fontSize: 16.0,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: EdgeInsets.fromLTRB(0.0, 25.0, 0.0, 0.0),
margin: const EdgeInsets.only(top: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
this.eventDate,
style: TextStyle(
color: Colors.grey[500],
fontSize: 11.0,
),
),
Container(
margin: const EdgeInsets.only(right: 10.0),
child: Text(peopleRequired.toString())),
],
),
),
],
),
),
],
),
),
),
),
);
}
}
Now, above my Widget build(BuildContext context) {
I have
List<Widget> cards = [];
and then within the Widget build(BuildContext context) { I have children: cards,
So at this point, this page would open up with cards being initialized empty.
But, I now have
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final List results = await db.getEvent(
DateFormat("y-MM-dd").format(DateTime.now()),
DateFormat("y-MM-dd").format(DateTime.now().add(Duration(days: 20))),
2,
);
for (DocumentSnapshot result in results) {
cards.add(EventCardWidget(
"assets/flutter-icon.png",
result.data.values
.toList()[result.data.keys.toList().indexOf("name")],
result.data.values
.toList()[result.data.keys.toList().indexOf("description")],
"1 mi",
"2018-10-29",
// result.data.values
// .toList()[result.data.keys.toList().indexOf("eventDate")],
"Time",
result.data.values
.toList()[result.data.keys.toList().indexOf("num")],
));
setState(() {
return cards;
});
}
});
}
Now, in Android Studio, I am able to see that cards has more elements when I set breakpoints, but nothing updates on the screen.
Now what's weird, is that when I do something like
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final List results = await db.getEvent(
DateFormat("y-MM-dd").format(DateTime.now()),
DateFormat("y-MM-dd").format(DateTime.now().add(Duration(days: 20))),
2,
);
for (DocumentSnapshot result in results) {
setState(() {
return cards = List.generate(
3,
(i) => new EventCardWidget(
"assets/flutter-icon.png",
"Title",
"Description",
"12 mi",
"2018-10-29",
"Time",
5),
growable: true,
);
});
}
});
}
Then the text on the screen updates.
Any ideas?
This is not a duplicate because I have tried the solution Here and it still does not work
for (DocumentSnapshot result in results) {
setState(() {
cards = List.from(cards)
..add(EventCardWidget(
"assets/flutter-icon.png",
result.data.values
.toList()[result.data.keys.toList().indexOf("name")],
result.data.values
.toList()[result.data.keys.toList().indexOf("description")],
"1 mi",
"2018-10-29",
result.data.values.toList()[
result.data.keys.toList().indexOf("peopleRequired")],
));
});
}
Your code started working for me when I did this
for (DocumentSnapshot result in results) {
cards = List.from(cards)
..add(EventCardWidget(
"assets/flutter-icon.png",
result.data.values
.toList()[result.data.keys.toList().indexOf("name")],
result.data.values
.toList()[result.data.keys.toList().indexOf("description")],
"1 mi",
"2018-10-29",
"8:00pm",
result.data.values
.toList()[result.data.keys.toList().indexOf("peopleRequired")],
));
setState(() {
this.cards = cards;
});
}

Flutter - widget testing for the Cupertino picker in flutter/Dart

i am testing the Cupertino picker,for which i am writing widget testing and i am struggling to implement how the onselectItemChanged in testing and accordingly the values chosen by the wheeling of the cupertino picker. The testing has to be done so that the value when user chooses accordingly the test case has to written in such a way that it suits to different values chosen
List<String> ages1 = ["-- select --"];
List<String> ages2 = List<String>.generate(
45, (int index) => (21 + index).toString(),
growable: false);
List<String> ages = [ages1, ages2].expand((f) => f).toList();
picker.dart:
Widget _buildAgePicker(BuildContext context) {
final FixedExtentScrollController scrollController =
FixedExtentScrollController(initialItem: _selectedAgeIndex);
return GestureDetector(
key: Key("Age Picker"),
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) {
return _buildBottomPicker(
CupertinoPicker(
key: Key("Age picker"),
scrollController: scrollController,
itemExtent: dropDownPickerItemHeight,
backgroundColor: Theme.of(context).canvasColor,
onSelectedItemChanged: (int index) {
setState(() {
_selectedAgeIndex = index;
ageValue = ages[index];
if (ageValue == S.of(context).pickerDefaultValue) {
ageDividerColor = Theme.of(context).errorColor;
errorText = S.of(context).pickerErrorMessage;
ageDividerWidth = 1.2;
} else {
ageDividerColor = Colors.black87;
errorText = "";
ageDividerWidth = 0.4;
}
});
},
children: List<Widget>.generate(ages.length, (int index) {
return Center(
child: Text(ages[index]),
);
}),
),
);
},
);
},
child: _buildMenu(
<Widget>[
Text(
S.of(context).Age,
style: TextStyle(fontSize: 17.0),
),
Text(
ages[_selectedAgeIndex],
),
],
),
);
}
Widget _buildMenu(List<Widget> children) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
),
height: 44.0,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SafeArea(
top: false,
bottom: false,
child: DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
),
),
),
);
}
Widget _buildBottomPicker(Widget picker) {
return Container(
height: dropDownPickerSheetHeight,
padding: const EdgeInsets.only(top: 6.0),
color: Theme.of(context).canvasColor,
child: DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
fontSize: 22.0,
),
child: GestureDetector(
key: Key("picker"),
onTap: () {},
child: SafeArea(
top: false,
child: picker,
),
),
),
);
}
widget_test.dart:
testWidgets("picker test",(WidgetTester tester)async{
await tester.tap(find.byKey(Key("Age Picker")));
await tester.drag(find.byKey(Key("Age Picker")), Offset(0.0,70.0));
await tester.pumpAndSettle();
expect(ages[1], "21");
});

Sliding animation to bottom in flutter

I have a dot indicator in bottom of the page. I need to hide this after 5 seconds sliding to bottom. When user move to other page show dots sliding to top and finally after 5 seconds hide again. Now the dots hide after 5 seconds in fade out but i need other type of animation.
import 'package:flutter/material.dart';
import 'package:iGota/screens/partials/dots_indicator.dart';
import 'package:iGota/screens/posts_page.dart';
import 'package:iGota/screens/maps_page.dart';
class HomePage extends StatefulWidget {
static String tag = 'home-page';
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<HomePage> {
final _controller = new PageController();
static const _kDuration = const Duration(milliseconds: 300);
static const _kCurve = Curves.ease;
final _kArrowColor = Colors.black.withOpacity(0.8);
bool _visible = true;
void initState() {
super.initState();
Future.delayed(Duration(milliseconds: 5)).then((_) => _visible = !_visible);
}
#override
Widget build(BuildContext context) {
final List<Widget> _pages = <Widget>[
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new FlatButton(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.pushNamed(context, PostsPage.tag);
},
child: Column(
children: <Widget>[
IconButton(
icon:
Icon(Icons.save_alt, color: Colors.white, size: 30.0),
onPressed: () {
Navigator.pushNamed(context, PostsPage.tag);
},
),
Text(
"Contenedores",
style: TextStyle(color: Colors.white, fontSize: 20.0),
)
],
),
),
],
),
splashColor: Colors.white,
color: Colors.blue[300],
onPressed: () {
Navigator.pushNamed(context, PostsPage.tag);
},
),
),
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new FlatButton(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.pushNamed(context, MapsPage.tag);
},
child: Column(
children: <Widget>[
IconButton(
icon: Icon(Icons.bubble_chart,
color: Colors.white, size: 30.0),
onPressed: () {
Navigator.pushNamed(context, MapsPage.tag);
},
),
Text(
"Válvulas",
style: TextStyle(color: Colors.white, fontSize: 20.0),
)
],
),
),
],
),
splashColor: Colors.white,
color: Colors.red[300],
onPressed: () {
Navigator.pushNamed(context, MapsPage.tag);
},
),
),
];
return new Scaffold(
body: new IconTheme(
data: new IconThemeData(color: _kArrowColor),
child: new Stack(
children: <Widget>[
new PageView.builder(
physics: new AlwaysScrollableScrollPhysics(),
controller: _controller,
itemCount: _pages.length,
itemBuilder: (BuildContext context, int index) {
this._visible=true;
return _pages[index % _pages.length];
},
),
new Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 3000),
child: new Container(
color: Colors.grey[800].withOpacity(0.5),
padding: const EdgeInsets.all(20.0),
child: new Center(
child: new DotsIndicator(
controller: _controller,
itemCount: _pages.length,
onPageSelected: (int page) {
_controller.animateToPage(
page,
duration: _kDuration,
curve: _kCurve,
);
},
),
),
),
),
)
],
),
),
);
}
}
I think position transition would help me but i don't know exactly how can i add to my code without rewriting. So anybody can help me?
UPDATE
import 'package:flutter/material.dart';
import 'package:iGota/screens/partials/dots_indicator.dart';
import 'package:iGota/screens/posts_page.dart';
import 'package:iGota/screens/maps_page.dart';
class HomePage extends StatefulWidget {
static String tag = 'home-page';
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
final _controller = new PageController();
static const _kDuration = const Duration(milliseconds: 300);
static const _kCurve = Curves.ease;
final _kArrowColor = Colors.black.withOpacity(0.8);
AnimationController controller;
Animation<Offset> offset;
#override
void initState() {
super.initState();
controller =AnimationController(vsync: this, duration: Duration(seconds: 1));
Future.delayed(Duration(seconds: 5)).then((_) => controller.forward());
offset = Tween<Offset>(begin: Offset.zero, end: Offset(0.0, 1.0))
.animate(controller);
}
#override
Widget build(BuildContext context) {
GestureDetector(onTap: () {
setState(() {
controller.reverse();
});
});
final List<Widget> _pages = <Widget>[
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new FlatButton(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.pushNamed(context, PostsPage.tag);
},
child: Column(
children: <Widget>[
IconButton(
icon:
Icon(Icons.save_alt, color: Colors.white, size: 30.0),
onPressed: () {
Navigator.pushNamed(context, PostsPage.tag);
},
),
Text(
"Contenedores",
style: TextStyle(color: Colors.white, fontSize: 20.0),
)
],
),
),
],
),
splashColor: Colors.white,
color: Colors.blue[300],
onPressed: () {
Navigator.pushNamed(context, PostsPage.tag);
},
),
),
new ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: new FlatButton(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.pushNamed(context, MapsPage.tag);
},
child: Column(
children: <Widget>[
IconButton(
icon: Icon(Icons.bubble_chart,
color: Colors.white, size: 30.0),
onPressed: () {
Navigator.pushNamed(context, MapsPage.tag);
},
),
Text(
"Válvulas",
style: TextStyle(color: Colors.white, fontSize: 20.0),
)
],
),
),
],
),
splashColor: Colors.white,
color: Colors.red[300],
onPressed: () {
Navigator.pushNamed(context, MapsPage.tag);
},
),
),
];
return new Scaffold(
body: new IconTheme(
data: new IconThemeData(color: _kArrowColor),
child: new Stack(
children: <Widget>[
new PageView.builder(
physics: new AlwaysScrollableScrollPhysics(),
controller: _controller,
itemCount: _pages.length,
itemBuilder: (BuildContext context, int index) {
return _pages[index % _pages.length];
},
),
new Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: SlideTransition(
position: offset,
child: new Container(
color: Colors.grey[800].withOpacity(0.5),
padding: const EdgeInsets.all(20.0),
child: new Center(
child: new DotsIndicator(
controller: _controller,
itemCount: _pages.length,
onPageSelected: (int page) {
_controller.animateToPage(
page,
duration: _kDuration,
curve: _kCurve,
);
},
),
),
),
),
)
],
),
),
);
}
}
Now i need to call controller.reverse when user touch screen...
To create a sliding animation for your indicator (if I've understood your requirement right), I would simply suggest using the SlideTransition widget. It should not require much work to integrate it in your existing code.
The code belows shows a minimal example of the SlideTransition. If you'd like to keep displaying it during the navigation from one screen to another, you'd have to draw it in a layer above your Navigator.
If you do not like to use a Stack, you can instead use the Overlay functionality of flutter, as given in this answer. This would also solve the struggle, with keeping the animation displayed during the navigation transition.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() => HomeState();
}
class HomeState extends State<Home> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> offset;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
offset = Tween<Offset>(begin: Offset.zero, end: Offset(0.0, 1.0))
.animate(controller);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Center(
child: RaisedButton(
child: Text('Show / Hide'),
onPressed: () {
switch (controller.status) {
case AnimationStatus.completed:
controller.reverse();
break;
case AnimationStatus.dismissed:
controller.forward();
break;
default:
}
},
),
),
Align(
alignment: Alignment.bottomCenter,
child: SlideTransition(
position: offset,
child: Padding(
padding: EdgeInsets.all(50.0),
child: CircularProgressIndicator(),
),
),
)
],
),
);
}
}

Resources