I've been developing a mobile app in flutter for a few days and just to reuse some code I'd like to pass a widget as a parameter so I don't have to duplicate a bunch of code. I tried some solutions I found here but none of them worked.
Here's what I have so far:
CarouselItem.dart
class CarouselItem extends StatelessWidget {
CarouselItem({
Key? key,
required this.index, required this.height, required this.width, required this.title, required this.destination,
}) : super(key: key);
final int index;
final double height;
final double width;
final String title;
final RouteOneCallBack destination; //trying to pass the widget
//List of images
List<String> list = [
'https://audioguia.s3.eu-west-3.amazonaws.com/FOTOS/Alqueva/A1-placa+entrada.jpg',
];
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.all(6.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(30.0),
child: GestureDetector(
child: Image.network(
list[index],
fit: BoxFit.cover,
height: height,
width: width,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
destination(), //widget goes here
),
);
},
),
),
),
SizedBox(height: 20),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
title,
style: TextStyle(fontWeight: FontWeight.bold),
),
),
],
);
}
}
Home.dart
How I want this to go
items: [
CarouselItem(
index: 0,
width: 500,
height: 200,
title: "Destino 1",
destination: RouteOne()
),
Am I missing an extra step? Thanks in advance.
Basically what I want to do is the same I do with the String or int parameters I already have, but can't seem to understand how to do this with a widget.
RouteOne() is a stateful widget
class BottomSheetDialog extends StatelessWidget {
final Widget? child;
const BottomSheetDialog({Key? key, #required this.child}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: Sizes.s350,
decoration: BoxDecoration(
color: Get.theme.cardColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(Sizes.s30),
topRight: Radius.circular(Sizes.s30))),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: child!),
],
));
}
}
// call
BottomSheetDialog(child:const Text("test"));
You can pass the widget as a parameter because the StatefulWidget extends the Widget.
Here is common usage.
final Widget destination;
Related
I'm having overflow problems with a Column inside a grid.
I need a grid of rounded images, placed in a column with a title and the author.
Does someone know what's the problem?
Thanks in advance!
Gallery_screen code:
This is where I have the grid:
body: ListView(
children: [
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: numItemsPerRow,
),
shrinkWrap: true,
children: [
for (var i = 0; i < b.length; i++)
MyImageThumbnail(
artpiece: b[i],
//gets image height and width from screen size
imageHeight: image_width.toInt(),
imageWidth: image_width.toInt(),
),
],
)
],
),
MyImageThumbnail code:
This is where I have the column with the RoundedImage and the texts
class MyImageThumbnail extends StatelessWidget {
const MyImageThumbnail(
{Key? key,
required this.artpiece,
required this.imageHeight,
required this.imageWidth})
: super(key: key);
final ArtPiece artpiece;
final int imageWidth;
final int imageHeight;
#override
Widget build(BuildContext context) {
//container with rounded corners and image
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
RoundedImage(artpiece: artpiece, imageHeight: imageHeight),
Text(artpiece.title),
// Text(artpiece.timeframe),
],
);
Rounded image:
This is where I have the rounded image code
class RoundedImage extends StatelessWidget {
const RoundedImage({
Key? key,
required this.artpiece,
required this.imageHeight,
}) : super(key: key);
final ArtPiece artpiece;
final int imageHeight;
#override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
height: imageHeight.toDouble(),
width: imageHeight.toDouble(),
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fitHeight,
image: NetworkImage(
artpiece.url,
),
),
),
),
);
}
}
I'm trying to make a hight picker and using a image along with the number of height.i want when user pick any height number the size of image increase or decrease according to the selected Height number.
Try as follows:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var items = [
200,
300,
400,
];
var index=0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Test"),
),
body: Column(
children:[
ListView.builder(
shrinkWrap:true,
itemCount:items.length,
itemBuilder:(ctx,i){
return TextButton(onPressed:(){
setState((){
index=i;
});
},child:Text(items[i].toString()));
}
),
Image.network("https://images.unsplash.com/photo-1453728013993-6d66e9c9123a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8dmlld3xlbnwwfHwwfHw%3D&w=1000&q=80",
height:items[index].toDouble()
),
]
)
);
}
}
Please refer to below code example
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController heightControl = TextEditingController();
final ValueNotifier<double> height1 = ValueNotifier<double>(200);
final ValueNotifier<double> _height2 = ValueNotifier<double>(120);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: AnimatedBuilder(
animation: Listenable.merge([
height1,
_height2,
]),
child: null,
builder: (BuildContext context, _) {
return Stack(
children: <Widget>[
Positioned(
left: 30,
right: 30,
bottom: 10,
child: Column(
children: <Widget>[
InkWell(
onTap: () {
height1.value = 80.0;
},
child: Container(
color: Colors.transparent,
height: height1.value,
child: Stack(
clipBehavior: Clip.none,
children: [
Image.network(
'https://images.stockfreeimages.com/2911/sfi226w/29118870.jpg',
fit: BoxFit.fill,
height: height1.value,
),
Text(
"Current Height ${height1.value} \n Tap to reduce height",
style: TextStyle(
color: Colors.white,
),
),
],
),
),
),
InkWell(
onTap: () {
_height2.value = 290.0;
},
child: Container(
color: Colors.orange,
height: _height2.value,
child: Stack(
clipBehavior: Clip.none,
children: [
Image.network(
'https://images.stockfreeimages.com/5439/sfi226w/54392167.jpg',
fit: BoxFit.fill,
height: _height2.value,
),
Text(
'Current height : ${_height2.value} \n Tap to increase height'),
],
),
),
),
],
),
),
],
);
},
),
);
}
}
Faced a problem when adding Bloc. I use Counter which is in Dialog and do everything through Block but for some reason I got this error (see below). I did the same before and there was no error. I do not fully understand what the error is connected with and how to solve it correctly. I have a HomePage class in which I declare Bloc and in which the HomeBody class is nested, and in this class there is a button for opening Dialog (FilterDialog) and in this Dialog I have a Counter that I did through Bloc. I will be grateful for help.
home page
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterCubit>(
create: (context) => CounterCubit(),
child: Scaffold(
extendBodyBehindAppBar: true,
appBar: LogoAppBar(
buttonIcon: SvgPicture.asset(constants.Assets.burgerMenu)),
body: const HomeBody(),
),
);
}
}
home body
class HomeBody extends StatelessWidget {
const HomeBody({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return Container(
width: size.width,
height: size.height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/background/main_background.png'),
fit: BoxFit.cover,
),
),
child: _child(context, size),
);
}
Widget _child(context, Size size) => Padding(
padding: const EdgeInsets.only(top: 121, right: 24),
child: Align(
alignment: Alignment.topRight,
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return const FilterDialog();
},
);
},
child: Container(
height: 40,
width: 50,
decoration: const BoxDecoration(
color: Colors.amber,
),
alignment: Alignment.center,
child: const Text('Dialog'),
),
),
),
);
}
FilterDialog
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Dialog(
insetPadding: const EdgeInsets.only(top: 100, left: 24, right: 24),
backgroundColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(24))),
child: Container(
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
color: constants.Colors.greyDark,
borderRadius: BorderRadius.all(Radius.circular(24)),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(21, 38, 21, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PriceCounter(title: 'From'),
]
price counter
class PriceCounter extends StatelessWidget {
final String title;
const PriceCounter({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
final CounterCubit cubit = BlocProvider.of<CounterCubit>(context);
return Column(
children: [
BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) => InputField(
price: state.countValue.toString(),
textStyle: constants.Styles.normalBookTextStyleWhite),
),
Row(
children: [
IconButton(
onPressed: () => cubit.increment(),
icon: SvgPicture.asset(constants.Assets.plus),
constraints: const BoxConstraints(),
padding: EdgeInsets.zero,
),
Text('Test', style: constants.Styles.smallLtStdTextStyleWhite),
IconButton(
onPressed: () => cubit.decrement(),
icon: SvgPicture.asset(constants.Assets.minus),
constraints: const BoxConstraints(),
padding: EdgeInsets.zero,
),
],
)
],
);
}
}
counter state
class CounterState {
final double countValue;
const CounterState({required this.countValue});
}
counter cubit
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(const CounterState(countValue: 0.13));
void increment() => emit(CounterState(countValue: state.countValue + 0.1));
void decrement() => emit(CounterState(countValue: state.countValue - 0.1));
}
The following assertion was thrown building PriceCounter(dirty):
BlocProvider.of() called with a context that does not contain a CounterCubit.
No ancestor could be found starting from the context that was passed to BlocProvider.of<CounterCubit>().
This can happen if the context you used comes from a widget above the BlocProvider.
The context used was: PriceCounter(dirty)
The relevant error-causing widget was PriceCounter
lib\…\widgets\filter_dialog.dart:233 When the exception was thrown,
this was the stack
Unfortunately, your FilterDialog cannot find the CounterCubit provider, since showDialog is a bit tricky about it, so you have to re-supply your CounterCubit to FilterDialog in this way:
Widget _child(context, Size size) => Padding(
padding: const EdgeInsets.only(top: 121, right: 24),
child: Align(
alignment: Alignment.topRight,
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return BlocProvider.value( // in this way
value: CounterCubit(),
child: FilterDialog(),
);
},
);
},
child: Container(
height: 40,
width: 50,
decoration: const BoxDecoration(
color: Colors.amber,
),
alignment: Alignment.center,
child: const Text('Dialog'),
),
),
),
);
}
With BlocProvider.value you do not create a Bloc, but only assign to its child the bloc that you have already created in HomePage.
That should work, but if for some reason you are going to use this CounterCubit in another page and you don't want to use BlocProvider.value again, I strongly suggest that you make it global, in other words that you provide the CounterCubit in all your application in this way :
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterCubit(), // here
child: MaterialApp(
title: 'Material App',
debugShowCheckedModeBanner: false,
home: const HomePage(),
),
);
}
}
With this, now every application will be able to get the context of your CounterCubit.
With both codes, one with the BlocProvider.value or without it and using it global, it works.
I want to change my width and height of container widget which is inside custom widget forProducts and I want to pass that width and height from another custom ProductContainer widget but when I pass it in ProductContainer it gives undefined name height and width.
here is my code for this
your help is appreciated,
thank you,
import 'package:myEcomm/screens/info_page.dart';
import 'package:flutter/material.dart';
class ProductContainer extends StatefulWidget {
final List products;
final double height;
final double width;
const ProductContainer({
Key key,
this.products,
this.height,
this.width,
}) : super(key: key);
#override
_ProductContainerState createState() => _ProductContainerState();
}
class _ProductContainerState extends State<ProductContainer> {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 150,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.products.length,
itemBuilder: (context, index) {
return forProducts(
image: widget.products[index]['imageLink'],
tag: widget.products[index]['tag'],
name: widget.products[index]['name'],
category: widget.products[index]["category"],
price: widget.products[index]["price"],
context: context);
},
),
),
],
),
);
}
}
Widget forProducts(
{String image = "images/",
#required String tag,
#required String name,
#required String category,
#required String price,
#required BuildContext context}) {
return Column(children: [
Material(
child: Hero(
tag: tag,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InfoPage(
image: image,
tag: tag,
name: name,
category: category,
price: price,
)));
},
child: Container(
padding: EdgeInsets.all(2),
margin: EdgeInsets.all(5),
height:
height, // this is the height which I want to assign from productcontainer(height: anyValue) widget
width:
width, // this is the width which I want to assign from productcontainer(width: anyValue) widget
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
fit: BoxFit.cover, image: AssetImage(image)),
),
),
))),
Text(name),
]);
}
This worked for me
import 'package:myEcomm/screens/info_page.dart';
import 'package:flutter/material.dart';
class ProductContainer extends StatefulWidget {
final List products;
final double height;
final double width;
const ProductContainer({
Key key,
this.products,
this.height,
this.width,
}) : super(key: key);
#override
_ProductContainerState createState() => _ProductContainerState();
}
class _ProductContainerState extends State<ProductContainer> {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 150,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.products.length,
itemBuilder: (context, index) {
return forProducts(
height: widget.height,
width: widget.width,
image: widget.products[index]['imageLink'],
tag: widget.products[index]['tag'],
name: widget.products[index]['name'],
category: widget.products[index]["category"],
price: widget.products[index]["price"],
context: context);
},
),
),
],
),
);
}
}
Widget forProducts(
{String image = "images/",
#required String tag,
#required String name,
#required String category,
#required String price,
#required double height,
#required double width,
#required BuildContext context}) {
return Column(children: [
Material(
child: Hero(
tag: tag,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InfoPage(
image: image,
tag: tag,
name: name,
category: category,
price: price,
)));
},
child: Container(
padding: EdgeInsets.all(2),
margin: EdgeInsets.all(5),
height: height,
width: width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
fit: BoxFit.cover, image: AssetImage(image)),
),
),
))),
Text(name),
]);
}
I'm trying to implement a solution where a row (containing both a TextField and a Text) in ListView.Builder is automatically for every record retrieved from a webserver.
However when I want to start typing in such a TextField the keyboard appears and immediatly disappears again.
This is the code of my screen.
class GameScreen extends StatelessWidget {
static const RouteName = "/GameScreen";
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
const horizontalMargin = 20.0;
return Scaffold(
appBar: getAppBar(),
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
Background(),
Column(
children: <Widget>[
Header("Starting letter: B"),
Expanded(
child: ListBlocProvider(
listWidget: GameCategoriesList(),
itemsService: CategoriesService(),
margin: EdgeInsets.only(
left: horizontalMargin,
bottom: 10,
right: horizontalMargin,
),
),
),
SizedBox(
height: 20,
),
SizedBox(
width: size.width - 40,
height: 60,
child: Container(
height: 60,
child: TextButtonWidget(
() {
// Navigator.of(context).pushNamed(GameScreen.RouteName);
},
"Stop game",
),
),
),
SizedBox(
height: 20,
)
],
),
],
),
);
}
}
This is the code of my ListBlocProvider:
class ListBlocProvider extends StatelessWidget {
final ListWidget listWidget;
final ItemsService itemsService;
final bool useColor;
final bool usePaddingTop;
final double height;
final EdgeInsets margin;
const ListBlocProvider({
#required this.listWidget,
#required this.itemsService,
this.useColor = true,
this.usePaddingTop = true,
this.height = 200,
this.margin,
});
#override
Widget build(BuildContext context) {
const horizontalMargin = 20.0;
return BlocProvider(
create: (context) => ItemsBloc(itemsService: itemsService)..add(ItemsFetched()),
child: Container(
padding: usePaddingTop ? EdgeInsets.only(top: 10) : null,
decoration: BoxDecoration(
color: this.useColor ? Color.fromRGBO(10, 50, 75, 0.9) : null,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
margin: this.margin,
height: this.height,
child: this.listWidget,
),
);
}
}
This is the code of my List:
class GameCategoriesList extends ListWidget {
#override
_GameCategoriesListState createState() => _GameCategoriesListState();
}
class _GameCategoriesListState extends State<GameCategoriesList> {
#override
Widget build(BuildContext context) {
return BlocBuilder<ItemsBloc, ItemsState>(
builder: (context, state) {
if (state is ItemsFailure) {
return Center(
child: Text('failed to fetch categories'),
);
}
if (state is ItemsSuccess) {
if (state.items.isEmpty) {
return Center(
child: Text('no categories found.'),
);
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
var textEditingController = TextEditingController();
return GameCategoryItemWidget(
key: UniqueKey(),
categoryModel: state.items[index],
textEditingController: textEditingController,
);
},
itemCount: state.items.length,
);
}
return Center(
child: LoadingIndicator(),
);
},
);
}
}
And this is the code where the both the TextField and the Text are build:
class GameCategoryItemWidget extends StatefulWidget {
final CategoryModel categoryModel;
final TextEditingController textEditingController;
const GameCategoryItemWidget({Key key, this.categoryModel, this.textEditingController}) :
super(key: key);
#override
_GameCategoryItemWidgetState createState() => _GameCategoryItemWidgetState();
}
class _GameCategoryItemWidgetState extends State<GameCategoryItemWidget> {
var formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: this.formKey,
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10, top: 20, bottom: 10),
child: Text(
this.widget.categoryModel.name,
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
Container(
color: Colors.white,
child: InputField(
InputDecoration(labelText: this.widget.categoryModel.name),
this.widget.textEditingController,
false,
),
),
],
),
),
);
}
#override
void dispose() {
this.widget.textEditingController.dispose();
super.dispose();
}
}
The InputField is a custom widget to hide the switch between a Material and a Cupertino version of the TextField.
I've already tried to remove the Key from the custom TextField widget. The funny part is that the input is actually working, however it can't determine for which of the TextFields in the ListView the input is determined so it adds the input to all of them. I've also tried to swap things around with making Stateless widgets Statefull, but that didn't help either.
The entire build is based upon: https://bloclibrary.dev/#/flutterinfinitelisttutorial.
Hoping you guys can help me.