How do I save data from a stateful widget in Flutter? - ios

I have implemented a TextField that allows user input in my flutter app. Now, I would like to save the user input data to a variable on pressing a button in order to use it later on. As I see it, in order to achieve this, I have to create a specific instance of the stateful widget and of its state so that when I call the widget to extract the variable, it does not create a new version of the widget with an empty TextField. The textfields in question are ProjectNameField and ProjectDescriptionField.
Here is my implementation:
ProjectDescriptionField savedDescription = ProjectDescriptionField();
ProjectNameField savedName = ProjectNameField();
AddPage savedPage = AddPage();
_AddPageState savedPageState = _AddPageState();
List<String> image_list_encoded = [];
class AddPage extends StatefulWidget {
final _AddPageState _addPageState = _AddPageState();
AddPage({Key? key}) : super(key: key);
#override
_AddPageState createState() => _AddPageState();
}
class _AddPageState extends State<AddPage> {
List<Asset> images = <Asset>[];
String _error = NOERROR;
#override
Widget build(BuildContext context) {
if (images.isEmpty)
return AppLayout(logicalHeight * 0.15);
else
return AppLayout(logicalHeight * 0.01);
}
List<Asset> getImages() {
return images;
}
Widget AppLayout(double distanceFromImages) {
return Scaffold(
appBar: AppBar(
leading: BackButton(color: Colors.white),
title: Text(CREATEPROJECT),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.check,
color: Colors.white,
),
onPressed: () {
SavedData _savedData = SavedData(
savedName._nameFieldState.myController.value.text,
savedDescription
._descriptionFieldState.myController.value.text,
savedPage._addPageState.getImages());
print(_savedData.saved_Project_Description);
print(_savedData.saved_Project_Name);
print(_savedData.saved_images.toString());
saveNewProject(_savedData);
},
),
],
),
body: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
child: Center(
child: Container(
height: logicalHeight * 0.05,
width: logicalWidth * 0.9,
child: savedName)),
),
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: ElevatedButton(
child: Text(PICKIMAGES),
onPressed: loadAssets,
)),
Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: getWidget())),
Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
child: Center(child: Container(child: savedDescription)),
),
]),
);
}
Widget getWidget() {
if (images.length > 0) {
return Container(
height: logicalHeight * 0.4,
width: logicalWidth * 0.8,
child: ImagePages());
} else {
return Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.15, 0, 0),
child: Container(child: Text(NOPICTURESSELECTED)));
}
}
PageView ImagePages() {
final PageController controller = PageController(initialPage: 0);
List<Widget> children = [];
images.forEach((element) {
children.add(Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.01, 0, logicalWidth * 0.01, 0),
child: AssetThumb(
asset: element,
width: 1000,
height: 1000,
)));
});
return PageView(
scrollDirection: Axis.horizontal,
controller: controller,
children: children,
);
}
}
class ProjectNameField extends StatefulWidget {
ProjectNameFieldState _nameFieldState = ProjectNameFieldState();
#override
ProjectNameFieldState createState() {
return _nameFieldState;
}
}
class ProjectNameFieldState extends State<ProjectNameField> {
final myController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return TextField(
controller: myController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
hintText: PROJECTNAME,
),
maxLength: 30,
);
}
}
class ProjectDescriptionField extends StatefulWidget {
ProjectDescriptionFieldState _descriptionFieldState =
ProjectDescriptionFieldState();
#override
ProjectDescriptionFieldState createState() {
return _descriptionFieldState;
}
}
class ProjectDescriptionFieldState extends State<ProjectDescriptionField> {
final myController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return TextField(
controller: myController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
hintText: PROJECTDESCRIPTION,
),
minLines: 1,
maxLines: 5,
maxLength: 5000,
);
}
}
class SavedData {
String saved_Project_Name = "";
String saved_Project_Description = "";
List<Asset> saved_images = [];
SavedData(String saved_Project_Name, String saved_Project_Description,
List<Asset> saved_images) {
this.saved_Project_Name = saved_Project_Name;
this.saved_Project_Description = saved_Project_Description;
this.saved_images = saved_images;
}
}
I believe the problem lies here (ProjectNameField and ProjectDescriptionField are essentially the same, with only minor differences):
class ProjectNameField extends StatefulWidget {
ProjectNameFieldState _nameFieldState = ProjectNameFieldState();
#override
ProjectNameFieldState createState() {
return _nameFieldState;
}
}
class ProjectNameFieldState extends State<ProjectNameField> {
final myController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return TextField(
controller: myController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
hintText: PROJECTNAME,
),
maxLength: 30,
);
}
}
When I let createState() return _nameFieldState instead of ProjectNameFieldState(), the following error occurs:
The createState function for ProjectDescriptionField returned an old or invalid state instance: ProjectDescriptionField, which is not null, violating the contract for createState.
'package:flutter/src/widgets/framework.dart':
Failed assertion: line 4681 pos 7: 'state._widget == null'
However, when I return ProjectNameFieldState(), then this code
onPressed: () {
SavedData _savedData = SavedData(
savedName._nameFieldState.myController.value.text,
savedDescription
._descriptionFieldState.myController.value.text,
savedPage._addPageState.getImages());
print(_savedData.saved_Project_Description);
print(_savedData.saved_Project_Name);
print(_savedData.saved_images.toString());
saveNewProject(_savedData);
}
does not save the project name.
How can I get rid of this error and save the project name and project description?
Thank you!

You can use the Form and TextFormFiled Widgets to easily save the value in your variable and also validate the form fields.
Here is the code snippet:
Declare a GlobalKey for the Form Widget
final _formKey = GlobalKey<FormState>();
In your Scaffold body where you will putting your TextFormFiled add the Form Widget as the parent for all the fields
Form(
key: _formKey,
child: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
child: Center(
child: Container(
height: logicalHeight * 0.05,
width: logicalWidth * 0.9,
child: TextFormField(
controller: projectNameController,
decoration: InputDecoration(
hintText: PROJECTNAME,
border: UnderlineInputBorder()),
maxLength: 30
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectName = value),
)),
),
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: ElevatedButton(
child: Text(PICKIMAGES),
onPressed: loadAssets,
)),
Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: getWidget())),
Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
child: Center(child: Container(child: TextFormField(
controller:projectDescriptionController ,
decoration: InputDecoration(
hintText: PROJECTDESCRIPTION,
border: UnderlineInputBorder()),
minLines: 1,
maxLines: 5,
maxLength: 5000,
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectDescription = value))),
),
]),
),
Now in your IconButton's onPressed validate the form and use the save function to save the TextFormField values in your variable.
final form = _formKey.currentState;
if (form.validate()) {
form.save();
}
Now here in the above code form.validate() will invoke the validator and form.save() will invoke onSaved property of the TextFormField
Here is the complete code:
import 'package:flutter/material.dart';
class AddPage extends StatefulWidget {
AddPage({Key? key}) : super(key: key);
#override
_AddPageState createState() =>
_AddPageState();
}
class _AddPageState extends State<AddPage> {
final _formKey = GlobalKey<FormState>();
String savedProjectName;
String savedProjectDescription;
final projectNameController = TextEditingController();
final projectDescriptionController = TextEditingController();
double logicalHeight; //your logical height
double logicalWidth; //your logical widgth
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: BackButton(color: Colors.white),
title: Text(CREATEPROJECT),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.check,
color: Colors.white,
),
onPressed: () {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
}
},
),
],
),
body:Form(
key: _formKey,
child: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.02, 0, 0),
child: Center(
child: Container(
height: logicalHeight * 0.05,
width: logicalWidth * 0.9,
child: TextFormField(
controller: projectNameController,
decoration: InputDecoration(
hintText: PROJECTNAME,
border: UnderlineInputBorder()),
maxLength: 30,
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectName = value),
)),
),
Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: ElevatedButton(
child: Text(PICKIMAGES),
onPressed: loadAssets,
)),
Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, logicalHeight * 0.01, 0, 0),
child: getWidget())),
Padding(
padding: EdgeInsets.fromLTRB(
logicalWidth * 0.05, distanceFromImages, logicalWidth * 0.05, 0),
child: Center(child: Container(child: TextFormField(
controller:projectDescriptionController ,
decoration: InputDecoration(
hintText: PROJECTDESCRIPTION,
border: UnderlineInputBorder()),
minLines: 1,
maxLines: 5,
maxLength: 5000,
validator: (value) {
if (value.isEmpty) {
return 'This is a required field';
}
return null;
},
onSaved: (value) => savedProjectDescription = value),)),
),
]),
),
);
}
}

Related

Don't center a PageView - Flutter

I am currently using the carousel-slider library to get a carousel in Flutter.
This library is based on a PageView, and in a PageView the elements are centered.
That's the carousel I get:
And this is what I'd like to have:
Here is the code where is use the CarouselSlider:
CarouselSlider(
height: 150,
viewportFraction: 0.5,
initialPage: 0,
enableInfiniteScroll: false,
items: widget.user.lastGamesPlayed.map((game) {
return Builder(
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: GestureDetector(
onTap: () {
game.presentGame(context, widget.user);
},
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(25)),
child: Container(
color: Theme.MyColors.lightBlue,
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: AutoSizeText(game.name,
style: TextStyle(fontSize: 70),
maxLines: 1)),
),
))));
},
);
}).toList(),
)
And here is the code inside the CarouselSlider library:
#override
Widget build(BuildContext context) {
return getWrapper(PageView.builder(
physics: widget.isScrollEnabled
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
scrollDirection: widget.scrollDirection,
controller: widget.pageController,
reverse: widget.reverse,
itemCount: widget.enableInfiniteScroll ? null : widget.items.length,
onPageChanged: (int index) {
int currentPage =
_getRealIndex(index, widget.realPage, widget.items.length);
if (widget.onPageChanged != null) {
widget.onPageChanged(currentPage);
}
},
itemBuilder: (BuildContext context, int i) {
final int index = _getRealIndex(
i + widget.initialPage, widget.realPage, widget.items.length);
return AnimatedBuilder(
animation: widget.pageController,
child: widget.items[index],
builder: (BuildContext context, child) {
// on the first render, the pageController.page is null,
// this is a dirty hack
if (widget.pageController.position.minScrollExtent == null ||
widget.pageController.position.maxScrollExtent == null) {
Future.delayed(Duration(microseconds: 1), () {
setState(() {});
});
return Container();
}
double value = widget.pageController.page - i;
value = (1 - (value.abs() * 0.3)).clamp(0.0, 1.0);
final double height = widget.height ??
MediaQuery.of(context).size.width * (1 / widget.aspectRatio);
final double distortionValue = widget.enlargeCenterPage
? Curves.easeOut.transform(value)
: 1.0;
if (widget.scrollDirection == Axis.horizontal) {
return Center(
child:
SizedBox(height: distortionValue * height, child: child));
} else {
return Center(
child: SizedBox(
width:
distortionValue * MediaQuery.of(context).size.width,
child: child));
}
},
);
},
));
}
How can I prevent elements from being centered?
Thank you in advance
If you don't want to animate page size over scroll there is no need to use this carousel-slider library.
Also, PageView is not the best Widget to achieve the layout you want, you should use a horizontal ListView with PageScrollPhysics.
import 'package:flutter/material.dart';
class Carousel extends StatelessWidget {
Carousel({
Key key,
#required this.items,
#required this.builderFunction,
#required this.height,
this.dividerIndent = 10,
}) : super(key: key);
final List<dynamic> items;
final double dividerIndent;
final Function(BuildContext context, dynamic item) builderFunction;
final double height;
#override
Widget build(BuildContext context) {
return Container(
height: height,
child: ListView.separated(
physics: PageScrollPhysics(),
separatorBuilder: (context, index) => Divider(
indent: dividerIndent,
),
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (context, index) {
Widget item = builderFunction(context, items[index]);
if (index == 0) {
return Padding(
child: item,
padding: EdgeInsets.only(left: dividerIndent),
);
} else if (index == items.length - 1) {
return Padding(
child: item,
padding: EdgeInsets.only(right: dividerIndent),
);
}
return item;
}),
);
}
}
Usage
Carousel(
height: 150,
items: widget.user.lastGamesPlayed,
builderFunction: (context, item) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(25)),
child: Container(
width: 200,
color: Theme.MyColors.lightBlue,
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: AutoSizeText(
item.name,
style: TextStyle(fontSize: 70),
maxLines: 1,
),
),
),
),
);
},
)
UPDATE
As observed by #AdamK, my solution doesn't have the same scroll physics behavior as a PageView, it acts more like a horizontal ListView.
If you are looking for this pagination behavior you should consider to write a custom ScrollPhysics and use it on your scrollable widget.
This is a very well explained article that helps us to achieve the desired effect.

Can you use Flutter's transform class to animate scale?

I'm trying to animate two square containers so that when they are tapped they are animated to scale. I see all these transform class examples online that show animation of a widget however when I use the transform class the scale just jumps from its initial value to its final value.
My end goal is to animate a container to 'bounce' every time it is tapped like what you can do with bounce.js in web development. To understand what I mean you can go to http://bouncejs.com, click 'select preset' in the upper left corner, select jelly from the drop down menu and click 'play animation'.
Can this be done with the transform class?
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
var squareScaleA = 0.5;
var squareScaleB = 0.5;
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bounce Example"),
),
body: Stack(
children: <Widget>[
Container(
width: 300.0,
height: 150.0,
color: Colors.yellowAccent,
),
Column(
children: <Widget>[
Row(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
squareScaleA = 1.0;
});
},
child: Transform.scale(
scale: squareScaleA,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.green,
),
),
),
GestureDetector(
onTap: () {
setState(() {
squareScaleB = 1.0;
});
},
child: Transform.scale(
scale: squareScaleB,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.blue,
),
),
),
],
),
],
),
],
),
);
}
}
Thanks in advance for any help!
You need to use Animations, you can start using AnimationController it's very simple , I fixed your sample :
class _MyHomePageState extends State<TestingNewWidget>
with TickerProviderStateMixin {
var squareScaleA = 0.5;
var squareScaleB = 0.5;
AnimationController _controllerA;
AnimationController _controllerB;
#override
void initState() {
_controllerA = AnimationController(
vsync: this,
lowerBound: 0.5,
upperBound: 1.0,
duration: Duration(seconds: 1));
_controllerA.addListener(() {
setState(() {
squareScaleA = _controllerA.value;
});
});
_controllerB = AnimationController(
vsync: this,
lowerBound: 0.5,
upperBound: 1.0,
duration: Duration(seconds: 1));
_controllerB.addListener(() {
setState(() {
squareScaleB = _controllerB.value;
});
});
super.initState();
}
#override
void dispose() {
_controllerA.dispose();
_controllerB.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bounce Example"),
),
body: Stack(
children: <Widget>[
Container(
width: 300.0,
height: 150.0,
color: Colors.yellowAccent,
),
Column(
children: <Widget>[
Row(
children: <Widget>[
GestureDetector(
onTap: () {
if (_controllerA.isCompleted) {
_controllerA.reverse();
} else {
_controllerA.forward(from: 0.0);
}
},
child: Transform.scale(
scale: squareScaleA,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.green,
),
),
),
GestureDetector(
onTap: () {
if (_controllerB.isCompleted) {
_controllerB.reverse();
} else {
_controllerB.forward(from: 0.0);
}
},
child: Transform.scale(
scale: squareScaleB,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.blue,
),
),
),
],
),
],
),
],
),
);
}
}
Also you can read more about animation here: https://flutter.dev/docs/development/ui/animations

How can I have same page destination using hero

I am trying to make a flutter app. It is an app that can create containers. Then, when you press on those containers, it will open up a page. So, I tried to use hero with infinitely creatable containers. This is what I came up with:
import 'package:flutter/material.dart';
void main() => runApp(MainPage());
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.white,
body: Column(children: <Widget>[
Body(),
])));
}
}
class Body extends StatefulWidget {
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
final String open1 = 'open';
int count = 1;
#override
Widget build(BuildContext context) {
List cards = List.generate(count, (int i) => RCard(count));
return Expanded(
child: Container(
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
reverse: true,
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: count,
itemBuilder: (context, i) {
if (i == 0) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page(
open: open1,
)),
);
count++;
},
child: Hero(
tag: open1,
child: Padding(
padding: EdgeInsets.only(
left:
MediaQuery.of(context).size.height *
0.015,
right:
MediaQuery.of(context).size.height *
0.015,
top: MediaQuery.of(context).size.width *
0.08,
bottom:
MediaQuery.of(context).size.width *
0.15),
child: Material(
borderRadius:
BorderRadius.circular(40.0),
color: Colors.white,
elevation: 8.0,
child: InkWell(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.add,
size: 30.0,
color: Colors.black,
)
]),
)))));
} else {
return cards[i];
}
}))));
}
}
class RCard extends StatefulWidget {
final int count;
RCard(this.count);
#override
RCardState createState() => RCardState();
}
class RCardState extends State<RCard> {
int count;
String open2;
#override
void initState() {
super.initState();
open2 = 'open$count';
count = widget.count;
}
#override
Widget build(BuildContext context) {
return Hero(
tag: open2,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page(
open: open2,
)),
);
},
child: Padding(
padding: EdgeInsets.only(
left: MediaQuery.of(context).size.height * 0.015,
right: MediaQuery.of(context).size.height * 0.015,
top: MediaQuery.of(context).size.width * 0.08,
bottom: MediaQuery.of(context).size.width * 0.15),
child: Material(
borderRadius: BorderRadius.circular(40.0),
color: Colors.white,
elevation: 8.0,
child: Padding(
padding:
EdgeInsets.all(MediaQuery.of(context).size.width * 0.15),
)),
)),
);
}
}
class Page extends StatelessWidget {
final String open;
Page({this.open});
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Hero(tag: open, child: Material()),
onTap: () {
Navigator.pop(context);
},
);
}
}
But, this code only works when I create 1 container. When I create 2 containers and press on it, it gives me a black screen. How can I solve this problem?
I've figured out that all you need to do is delete the list 'cards' and change the 'cards[i]' method to 'RCard(i)'.
import 'package:flutter/material.dart';
void main() => runApp(MainPage());
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.white,
body: Column(children: <Widget>[
Body(),
])));
}
}
class Body extends StatefulWidget {
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
final String open1 = 'open';
int count = 1;
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
reverse: true,
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: count,
itemBuilder: (context, i) {
if (i == 0) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page(
open: open1,
)),
);
count++;
},
child: Hero(
tag: open1,
child: Padding(
padding: EdgeInsets.only(
left:
MediaQuery.of(context).size.height *
0.015,
right:
MediaQuery.of(context).size.height *
0.015,
top: MediaQuery.of(context).size.width *
0.08,
bottom:
MediaQuery.of(context).size.width *
0.15),
child: Material(
borderRadius:
BorderRadius.circular(40.0),
color: Colors.white,
elevation: 8.0,
child: InkWell(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.add,
size: 30.0,
color: Colors.black,
)
]),
)))));
} else {
return RCard(i);
}
}))));
}
}
class RCard extends StatefulWidget {
final int count;
RCard(this.count);
#override
RCardState createState() => RCardState();
}
class RCardState extends State<RCard> {
int count;
String open2;
#override
void initState() {
super.initState();
open2 = 'open$count';
count = widget.count;
}
#override
Widget build(BuildContext context) {
return Hero(
tag: open2,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page(
open: open2,
)),
);
},
child: Padding(
padding: EdgeInsets.only(
left: MediaQuery.of(context).size.height * 0.015,
right: MediaQuery.of(context).size.height * 0.015,
top: MediaQuery.of(context).size.width * 0.08,
bottom: MediaQuery.of(context).size.width * 0.15),
child: Material(
borderRadius: BorderRadius.circular(40.0),
color: Colors.white,
elevation: 8.0,
child: Padding(
padding:
EdgeInsets.all(MediaQuery.of(context).size.width * 0.15),
)),
)),
);
}
}
class Page extends StatelessWidget {
final String open;
Page({this.open});
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Hero(tag: open, child: Material()),
onTap: () {
Navigator.pop(context);
},
);
}
}
You can only have one Hero widget with the same tag value in a single class. If you want to have more than two Hero animation happen simultaneously, you have to give each Hero widgets different tag values.
Try giving a unique tag value every time you create the Hero. The black screen error is most likely because two or more Hero elements are getting the same tag value.
For example, inside the itemBuilder: (context, i) you are getting the current index in the varible i. You can use this index to make the tag value unique.
child: Hero(
tag: open1 + "$i",
child: /*Rest of your code*/
)
You can do something similar to make sure the values for all the tag elements are unique. Let me know if it helps!

How to add a widget when a button is pressed flutter

For my flutter app I need a container that can be added when I press the add button. So I then looked at other Stack Overflow questions such as: Flutter - Render new Widgets on click and Flutter - Add new Widget on click. After, this is what I came up with.
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
var tPadding= MediaQuery.of(context).size.width * 0.08;
var bPadding= MediaQuery.of(context).size.width * 0.15;
var vPadding = MediaQuery.of(context).size.height * 0.015;
return Expanded (
child: Container (
child: NotificationListener<OverscrollIndicatorNotification> (
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: container.length,
itemBuilder: (context, i) {
return Padding (
padding: EdgeInsets.only(
left: vPadding,
right: vPadding,
top: tPadding,
bottom: bPadding
),
child: container[i]
);
},
)
)
)
);
}
}
int _count = 1;
List container = [
List.generate(_count, (int i) => ContainerCard),
AddContainer()
];
class ContainerCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Material (
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
);
}
}
class AddContainer extends StatefulWidget {
#override
AddContainerState createState() => AddContainerState();
}
class AddContainerState extends State<AddContainer> {
#override
Widget build(BuildContext context) {
return Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
child: InkWell (
onTap: _addContainer,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
child: Column (
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon (
Icons.add,
size: 50.0,
)
]
),
)
);
}
void _addContainer() {
setState(() {
_count = _count + 1;
});
}
}
But for some reason this is not working. What is wrong and how can I fix this?
Full Code:
import 'package:flutter/material.dart';
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp (
debugShowCheckedModeBanner: false,
home: Scaffold (
backgroundColor: Colors.white,
body: Column (
children: <Widget> [
AppBar(),
Body(),
]
)
)
);
}
}
class AppBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
var abHeight = MediaQuery.of(context).size.height * 0.2;
var vPadding = MediaQuery.of(context).size.height * 0.07;
return Container (
height: abHeight,
child: Column (
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget> [
Row (
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: vPadding),
child: PoppinsText (
text: "App",
fontSize: 40.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
)
]
)
]
)
);
}
}
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
var tPadding= MediaQuery.of(context).size.width * 0.08;
var bPadding= MediaQuery.of(context).size.width * 0.15;
var vPadding = MediaQuery.of(context).size.height * 0.015;
return Expanded (
child: Container (
child: NotificationListener<OverscrollIndicatorNotification> (
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: container.length,
itemBuilder: (context, i) {
return Padding (
padding: EdgeInsets.only(
left: vPadding,
right: vPadding,
top: tPadding,
bottom: bPadding
),
child: container[i]
);
},
)
)
)
);
}
}
int _count = 1;
List container = [
List.generate(_count, (int i) => ContainerCard),
AddContainer()
];
class ContainerCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Material (
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
);
}
}
class AddContainer extends StatefulWidget {
#override
AddContainerState createState() => AddContainerState();
}
class AddContainerState extends State<AddContainer> {
#override
Widget build(BuildContext context) {
return Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
child: InkWell (
onTap: _addContainer,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
child: Column (
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon (
Icons.add,
size: 50.0,
)
]
),
)
);
}
void _addContainer() {
setState(() {
_count = _count + 1;
});
}
}
class PoppinsText extends StatelessWidget {
PoppinsText ({Key key,
this.text,
this.fontSize,
this.fontWeight,
this.color}) : super(key: key);
final String text;
final double fontSize;
final FontWeight fontWeight;
final Color color;
#override
Widget build(BuildContext context) {
return Text (
text,
style: TextStyle (
fontFamily: 'Poppins',
fontWeight: fontWeight,
fontSize: fontSize,
color: color
),
);
}
}
You are using a Stateless widget. Switch to Stateful widget
Edit
Updated as per requirement.
The trick here is to use reverse property of pageview.
class Body extends StatefulWidget {
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
int count = 1;
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
reverse: true,
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: count,
itemBuilder: (context, i) {
print(i);
if (i == 0) {
return Padding(
padding: EdgeInsets.only(
left:
MediaQuery.of(context).size.height * 0.015,
right:
MediaQuery.of(context).size.height * 0.015,
top: MediaQuery.of(context).size.width * 0.08,
bottom:
MediaQuery.of(context).size.width * 0.15),
child: Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
child: InkWell(
onTap: () {
setState(() {
count++;
});
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.add,
size: 50.0,
)
]),
)));
} else {
return Padding(
padding: EdgeInsets.only(
left:
MediaQuery.of(context).size.height * 0.015,
right:
MediaQuery.of(context).size.height * 0.015,
top: MediaQuery.of(context).size.width * 0.08,
bottom:
MediaQuery.of(context).size.width * 0.15),
child: Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
));
}
}))));
}
I don't know If you found your solution yet. This is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
const double paddingInset = 5;
Color tileColor = Colors.grey[350];
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
List<Widget> bodyElements = [];
int num = 0;
void addBodyElement() {
bodyElements.add(
Padding(
padding: const EdgeInsets.all(paddingInset),
child: Container(
height: 500,
width: double.infinity,
child: Center(child: Text('This is section $num')),
decoration: BoxDecoration(
color: tileColor,
borderRadius: BorderRadius.circular(10),
),
),
),
);
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Center(child: Text('Home')),
brightness: Brightness.dark,
leading: IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
setState(() {
bodyElements.clear();
num = 0;
});
},
),
),
body: ListView(
children: <Widget>[
Column(
children: bodyElements,
),
],
),
floatingActionButton: FloatingActionButton.extended(
icon: Icon(Icons.add),
label: Text('Add'),
onPressed: () {
num++;
setState(() {
addBodyElement();
});
},
),
);
}
}

Flutter custom range slider

I'm trying to create a range slider on top of a Row of Containers which should create an audio waveform, but I have no idea where to even start...
The main issue is that the range slider sits right on top of the row of containers and it should change their colors on the "selected" section.
Here's what I currently have:
The code to create the image and details.
class BeatLyricsPage extends StatefulWidget {
final Beat beat;
BeatLyricsPage(this.beat);
#override
_BeatLyricsPageState createState() => _BeatLyricsPageState(beat);
}
class _BeatLyricsPageState extends State<BeatLyricsPage> {
final Beat beat;
final _kPicHeight = 190.0;
// used in _buildPageHeading to add the beat key and beat bpm
Widget _buildBeatInfoItem(String text) => DecoratedBox(
decoration: BoxDecoration(
border: Border.all(color: MyColor.white, width: 1.0),
borderRadius: BorderRadius.circular(4.0),
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 3.0, horizontal: 12.0),
child: Text(text, style: TextStyle(color: MyColor.white, fontSize: 10.0, fontWeight: FontWeight.w600)),
),
);
final _kAudioControlsWidth = 180.0;
final _kAudioControlsHeight = 36.0;
final _kAudioControlsMainButtonSize = 56.0;
Widget _buildAudioControls(BuildContext context) => Positioned(
left: (MediaQuery.of(context).size.width / 2) - (_kAudioControlsWidth / 2),
top: _kPicHeight - (_kAudioControlsHeight / 2),
child: Stack(
overflow: Overflow.visible,
children: [
Container(
width: _kAudioControlsWidth,
height: _kAudioControlsHeight,
decoration: BoxDecoration(color: MyColor.darkGrey, borderRadius: BorderRadius.circular(100.0)),
padding: EdgeInsets.symmetric(horizontal: LayoutSpacing.sm),
child: Row(
children: [
CButtonLike(beatId: beat.id),
Spacer(),
GestureDetector(
behavior: HitTestBehavior.opaque,
child: Icon(BeatPulseIcons.cart),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => LicenseOptionsPage(beat))),
),
],
),
),
// ****** MAIN BUTTON (Play/Pause) ******
Positioned(
left: (_kAudioControlsWidth / 2) - (_kAudioControlsMainButtonSize / 2),
top: (_kAudioControlsHeight - _kAudioControlsMainButtonSize) / 2,
child: Container(
height: _kAudioControlsMainButtonSize,
width: _kAudioControlsMainButtonSize,
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, colors: [MyColor.primary, Color(0xFFf80d0a)]),
borderRadius: BorderRadius.circular(100.0)),
child: CButtonPlay(),
),
)
],
),
);
Widget _buildWaveForm() {
// creates a random list of doubles, "fake data"
var rng = Random();
final List waveFormData = [];
for (var i = 0; i < 90; i++) {
waveFormData.add(rng.nextInt(45).toDouble());
}
// player bloc
final playerBloc = BlocProvider.getPlayerBloc(context);
// renders
return Container(
height: _kPicHeight,
padding: EdgeInsets.symmetric(vertical: LayoutSpacing.xxxl),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// current playing second
StreamBuilder<double>(
stream: playerBloc.playingSecond,
initialData: 0.0,
builder: (_, playingSecondSnapshot) {
// current beat playing
return StreamBuilder<Beat>(
stream: playerBloc.playingBeat,
builder: (_, playingBeatSnapshot) {
final playingBeat = playingBeatSnapshot.data;
// if the beat playing is the same as the beat selected for the lyrics, show playing seconds
if (playingBeat?.id == beat.id)
return Text(secondsToTime(playingSecondSnapshot.data), style: MyFontStyle.sizeXxs);
// otherwise show 0:00
else
return Text(secondsToTime(0), style: MyFontStyle.sizeXxs);
},
);
},
),
SizedBox(width: LayoutSpacing.xs),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: waveFormData
.map((waveFormDataIndex) => Container(
height: waveFormDataIndex > 5.0 ? waveFormDataIndex : 5.0,
width: 2,
color: MyColor.white,
margin: EdgeInsets.only(right: 1),
))
.toList(),
),
SizedBox(width: LayoutSpacing.xs),
Text(secondsToTime(beat.length), style: MyFontStyle.sizeXxs),
],
),
);
}
Widget _buildPageHeading(BuildContext context, {#required String imageUrl}) => Stack(
children: [
Column(
children: [
Hero(
tag: MyKeys.makePlayerCoverKey(beat.id),
child: Opacity(
opacity: 0.3,
child: Container(
height: _kPicHeight,
decoration: BoxDecoration(
image: DecorationImage(image: CachedNetworkImageProvider(imageUrl), fit: BoxFit.cover),
),
),
),
),
Container(color: MyColor.background, height: LayoutSpacing.xl)
],
),
Padding(
padding: EdgeInsets.all(LayoutSpacing.xs),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
_buildBeatInfoItem(beat.key),
SizedBox(width: 4.0),
_buildBeatInfoItem('${beat.bpm} BPM'),
],
),
),
_buildAudioControls(context),
_buildWaveForm(),
],
);
}
To create a custom range slider, you can use the GestureRecognizer and save the position of each slider in variable inside a StatefulWidget. To decide wether a bar with the index i is inside the range, you can divide the pixel position of the limiter(bar1&bar2 in the source below) by the width of bars and compare it to i.
Sadly I couldn't work with your code example. Instead I created a bare minimum example as you can see below. If you take a minute to read into, I'm sure you can transfer it to your application.
import 'dart:math';
import 'package:flutter/material.dart';
List<int> bars = [];
void main() {
// generate random bars
Random r = Random();
for (var i = 0; i < 50; i++) {
bars.add(r.nextInt(200));
}
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> {
static const barWidth = 5.0;
double bar1Position = 60.0;
double bar2Position = 180.0;
#override
Widget build(BuildContext context) {
int i = 0;
return Scaffold(
body: Center(
child: Stack(
alignment: Alignment.centerLeft,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: bars.map((int height) {
Color color =
i >= bar1Position / barWidth && i <= bar2Position / barWidth
? Colors.deepPurple
: Colors.blueGrey;
i++;
return Container(
color: color,
height: height.toDouble(),
width: 5.0,
);
}).toList(),
),
Bar(
position: bar2Position,
callback: (DragUpdateDetails details) {
setState(() {
bar2Position += details.delta.dx;
});
},
),
Bar(
position: bar1Position,
callback: (DragUpdateDetails details) {
setState(() {
bar1Position += details.delta.dx;
});
},
),
],
),
),
);
}
}
class Bar extends StatelessWidget {
final double position;
final GestureDragUpdateCallback callback;
Bar({this.position, this.callback});
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: position >= 0.0 ? position : 0.0),
child: GestureDetector(
onHorizontalDragUpdate: callback,
child: Container(
color: Colors.red,
height: 200.0,
width: 5.0,
),
),
);
}
}
in order to have a wave slider :
class WaveSlider extends StatefulWidget {
final double initialBarPosition;
final double barWidth;
final int maxBarHight;
final double width;
WaveSlider({
this.initialBarPosition = 0.0,
this.barWidth = 5.0,
this.maxBarHight = 50,
this.width = 60.0,
});
#override
State<StatefulWidget> createState() => WaveSliderState();
}
class WaveSliderState extends State<WaveSlider> {
List<int> bars = [];
double barPosition;
double barWidth;
int maxBarHight;
double width;
int numberOfBars;
void randomNumberGenerator() {
Random r = Random();
for (var i = 0; i < numberOfBars; i++) {
bars.add(r.nextInt(maxBarHight - 10) + 10);
}
}
_onTapDown(TapDownDetails details) {
var x = details.globalPosition.dx;
print("tap down " + x.toString());
setState(() {
barPosition = x;
});
}
#override
void initState() {
super.initState();
barPosition = widget.initialBarPosition;
barWidth = widget.barWidth;
maxBarHight = widget.maxBarHight.toInt();
width = widget.width;
if (bars.isNotEmpty) bars = [];
numberOfBars = width ~/ barWidth;
randomNumberGenerator();
}
#override
Widget build(BuildContext context) {
int barItem = 0;
return Scaffold(
backgroundColor: Colors.grey[900],
body: Center(
child: GestureDetector(
onTapDown: (TapDownDetails details) => _onTapDown(details),
onHorizontalDragUpdate: (DragUpdateDetails details) {
setState(() {
barPosition = details.globalPosition.dx;
});
},
child: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
children: bars.map((int height) {
Color color = barItem + 1 < barPosition / barWidth
? Colors.white
: Colors.grey[600];
barItem++;
return Row(
children: <Widget>[
Container(
width: .1,
height: height.toDouble(),
color: Colors.black,
),
Container(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(1.0),
topRight: const Radius.circular(1.0),
),
),
height: height.toDouble(),
width: 4.8,
),
Container(
width: .1,
height: height.toDouble(),
color: Colors.black,
),
],
);
}).toList(),
),
),
),
),
);
}
}
and use it like :
WaveSlider(
initialBarPosition: 180.0,
barWidth: 5.0,
maxBarHight: 50,
width: MediaQuery.of(context).size.width,
)

Resources