Dynamic Row height in GridView.builder (Flutter) - flutter

I'm trying to use a GridView.builder with dynamic height depending on the largest item in each row. A Wrap is not ok for this, because it renders all items at once. the builder is only rendering the visible items. I won't calculate a childAspectRatio manually for each row.
Any ideas?

I've made a WrapBuilder which sort of like a Wrap but uses a builder. The caveat though is that you need to know the width of the items beforehand. If that's okay you can try using this class:
import 'dart:math';
import 'package:flutter/cupertino.dart';
typedef ValueWidgetBuilder<T> = Widget Function(T value);
class WrapBuilder extends StatelessWidget {
final double itemWidth;
final List items;
final ValueWidgetBuilder itemBuilder;
const WrapBuilder(
{Key? key,
required this.itemWidth,
required this.items,
required this.itemBuilder})
: super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
var itemsPerRow = max(1, constraints.maxWidth ~/ itemWidth);
return ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: (items.length / itemsPerRow).ceil(),
itemBuilder: (BuildContext context, int index) {
var rowItems = items.sublist(itemsPerRow * index,
min(itemsPerRow * (index + 1), items.length));
return Row(children: [
for (final item in rowItems)
SizedBox(
width: itemWidth,
child: itemBuilder(item))
]);
},
);
});
}
}
You might need to tweak it for your use case. In my case I have a list that is some data and the itemBuilder takes one of this data as parameter.

you can pass height as variable in list and then set height for the row from list

Related

Pass Index from Carousel Widget to Another Widget Flutter

I am new to Flutter and stumped on how to do this. I have a screen that has a Carousel Slider widget on it that I am holding in a separate file/widget to keep the code as clean as possible. To that Carousel I am passing a List which are urls of images and videos. I have already implemented an indicator bar and have the index of the list held within activeIndex variable within the Carousel widget. I then need to pass that index value to a separate widget held in a variable on the main page of my app (one with the clean code).
I basically need help on where to define variables in one widget that I can then define and pass to multiple other widgets. Please let me know if you need more context as I am new to coding in general. Thanks!
Carousel Widget
class AssetCarouselBuilder extends StatefulWidget {
const AssetCarouselBuilder({
#required this.assets,
this.activeIndex
});
final List<String> assets;
final int activeIndex;
#override
State<AssetCarouselBuilder> createState() => _AssetCarouselBuilderState();
}
class _AssetCarouselBuilderState extends State<AssetCarouselBuilder> {
int activeIndex = 0;
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CarouselSlider.builder(itemCount: widget.assets.length,
itemBuilder: (context, index, realIndex){
final assetUrl = widget.assets[index];
options: CarouselOptions(
onPageChanged: (index, reason) =>
setState (() => activeIndex = index)
//this is where I am holding the variable 'activeIndex' that I need elsewhere
if (assetUrl.contains('jpg')) {
return buildImage(assetUrl, index);
}
const SizedBox(height: 5),
buildIndicator(),
Widget buildImage(String imageUrl, int index) => Image(),
Widget buildIndicator() => AnimatedSmoothIndicator(
activeIndex: activeIndex,
count: widget.assets.length,
effect: ColorTransitionEffect()
Implementation of Carousel on "main page"
class FeedPageWidget extends StatefulWidget {
const FeedPageWidget({
Key key,
}) : super(key: key);
#override
_FeedPageWidgetState createState() => _FeedPageWidgetState();
}
class _FeedPageWidgetState extends State<FeedPageWidget>
int _currentIndex = 0;
AssetCarouselBuilder(assets: listViewPostsRecord.postAssets.asList())
And then widget I need to pass the index to another widget on the "main page".
ShareMenuWidget(
postRef: listViewPostsRecord,
assetIndex: _currentIndex)
Any help on how I get the "activeIndex" value on the setState function in the Carousel slider is very appreciated!
You can use callback method like Function(int activeIndex)? onIndexChanged;.
class CarouselCW extends StatefulWidget {
final Function(int activeIndex)? onIndexChanged;
const CarouselCW({
Key? key,
this.onIndexChanged,
}) : super(key: key);
#override
State<CarouselCW> createState() => _CarouselCWState();
}
class _CarouselCWState extends State<CarouselCW> {
final CarouselController carouselController = CarouselController();
#override
Widget build(BuildContext context) {
return CarouselSlider.builder(
itemCount: 4,
itemBuilder: (BuildContext context, int index, int realIndex) {
return Text(
index.toString(),
);
},
options: CarouselOptions(
onPageChanged: (index, reason) {
if (widget.onIndexChanged != null) widget.onIndexChanged!(index);
},
),
);
}
}
And while using this widget you will get
CarouselCW(
onIndexChanged: (activeIndex) {
print(activeIndex.toString());
},
)

Flutter web ignore resize

I would like to make my web app does not resize/responsive when the screen become smaller. For example when i decrease my browser size, instead of its adapting to the width and height, i want the web app to ignore it and user can just scroll up, down, left and right to view the web.
I suggest you to use a LayoutBuilder and eventually add a SingleChildScrollView when the size becomes lower than your app expects.
This MinSize widget accomplishes it.
/// Starts scrolling [child] vertically and horizontally when the widget sizes
/// reaches below [minWidth] or [minHeight]
class MinSize extends StatefulWidget {
const MinSize({
Key? key,
this.minWidth,
this.minHeight,
required this.child,
}) : super(key: key);
final Widget child;
final double? minWidth;
final double? minHeight;
#override
State<MinSize> createState() => _MinSizeState();
}
class _MinSizeState extends State<MinSize> {
late final _verticalController = ScrollController();
late final _horizontalController = ScrollController();
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final shouldScrollVertical = widget.minHeight != null &&
constraints.maxHeight <= widget.minHeight!;
final contentHeight =
shouldScrollVertical ? widget.minHeight : constraints.maxHeight;
final verticalPhysics = shouldScrollVertical
? const AlwaysScrollableScrollPhysics()
: const NeverScrollableScrollPhysics();
final shouldScrollHorizontal =
widget.minWidth != null && constraints.maxWidth <= widget.minWidth!;
final contentWidth =
shouldScrollHorizontal ? widget.minWidth : constraints.maxWidth;
final horizontalPhysics = shouldScrollHorizontal
? const AlwaysScrollableScrollPhysics()
: const NeverScrollableScrollPhysics();
return Scrollbar(
controller: _verticalController,
thumbVisibility: shouldScrollVertical,
child: SingleChildScrollView(
controller: _verticalController,
scrollDirection: Axis.vertical,
physics: verticalPhysics,
child: Scrollbar(
interactive: true,
controller: _horizontalController,
thumbVisibility: shouldScrollHorizontal,
child: SingleChildScrollView(
controller: _horizontalController,
scrollDirection: Axis.horizontal,
physics: horizontalPhysics,
child: UnconstrainedBox(
child: SizedBox(
height: contentHeight,
width: contentWidth,
child: widget.child,
),
),
),
),
),
);
},
);
}
}
Alternatively, try the InteractiveViewer to freely move the content in the app window.
Use a SizedBox with a fixed height/width property

Custom ListView.builder -Flutter

I would like to do Custom ListView builder as U see below, but I have a problem with index in Widget where I use it. "Missing parameter type of index", could U tell me how to handle with it?
And how else could I make a Custom Widget that I could use in several places.
Any help will be greatly appreciated.
class BuilderNews extends StatelessWidget {
final double height;
final ItemBuilder itemBuilder;
final ScrollPhysics physics;
final int itemcount;
BuilderNews({
required this.height,
required this.itemBuilder,
required this.physics,
required this.itemcount});
ApiService client = ApiService();
late final Article article;
#override
Widget build(BuildContext context) {
return Container(
height: height,
child: FutureBuilder(
future: client.getArticle(),
builder:
(BuildContext context, AsyncSnapshot<List<Article>> snapshot) {
if (snapshot.hasData) {
var article = snapshot.data!;
return ListView.builder(
// scrollDirection: scrollDirection,
physics: physics,
shrinkWrap: true,
itemCount: itemcount,
itemBuilder: itemBuilder);
}
return const Center(
child: CircularProgressIndicator(),
);
}));
}
}
Define it like this, not ItemBuilder:
Widget Function(BuildContext, int) test = (context,index)=> Text('testing ItemBuilder');
.
.
.
.
ListView.builder(
physics: physics,
shrinkWrap: true,
itemCount: itemcount,
itemBuilder:test
Would you change itemBuilder's definition type from 'ItemBuilder' to 'IndexedWidgetBuilder'?
Because at the flutter 'scroll_view.dart' file, itemBuilder parameter type at ListView is 'IndexedWidgetBuilder'.
class BuilderNews extends StatelessWidget {
final double height;
final IndexedWidgetBuilder itemBuilder;
final ScrollPhysics physics;
final int itemcount;
...
<'scroll_view.dart' File>
ListView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
#required IndexedWidgetBuilder itemBuilder,
...

How to properly initialize a Future in Flutter Provider

so I am trying to build up a list in my provider from a Future Call.
So far, I have the following ChangeNotifier class below:
class MainProvider extends ChangeNotifier {
List<dynamic> _list = <dynamic>[];
List<dynamic> get list => _list;
int count = 0;
MainProvider() {
initList();
}
initList() async {
var db = new DatabaseHelper();
addToList(Consumer<MainProvider>(
builder: (_, provider, __) => Text(provider.count.toString())));
await db.readFromDatabase(1).then((result) {
result.forEach((item) {
ModeItem _modelItem= ModeItem.map(item);
addToList(_modelItem);
});
});
}
addToList(Object object) {
_list.add(object);
notifyListeners();
}
addCount() {
count += 1;
notifyListeners();
}
}
However, this is what happens whenever I use the list value:
I can confirm that my initList function is executing properly
The initial content from the list value that is available is the
Text() widget that I firstly inserted through the addToList function, meaning it appears that there is only one item in the list at this point
When I perform Hot Reload, the rest of the contents of the list seems to appear now
Notes:
I use the value of list in a AnimatedList widget, so I am
supposed to show the contents of list
What appears initially is that the content of my list value is only one item
My list value doesn't seem to automatically update during the
execution of my Future call
However, when I try to call the addCount function, it normally
updates the value of count without needing to perform Hot Reload -
this one seems to function properly
It appears that the Future call is not properly updating the
contents of my list value
My actual concern is that on initial loading, my list value doesn't
properly initialize all it's values as intended
Hoping you guys can help me on this one. Thank you.
UPDATE: Below shows how I use the ChangeNotifierClass above
class ParentProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<MainProvider>(
create: (context) => MainProvider(),
),
],
child: ParentWidget(),
);
}
}
class ParentWidget extends StatelessWidget {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
#override
Widget build(BuildContext context) {
var mainProvider = Provider.of<MainProvider>(context);
buildItem(BuildContext context, int index, Animation animation) {
print('buildItem');
var _object = mainProvider.list[index];
var _widget;
if (_object is Widget) {
_widget = _object;
} else if (_object is ModelItem) {
_widget = Text(_object.unitNumber.toString());
}
return SizeTransition(
key: ValueKey<int>(index),
axis: Axis.vertical,
sizeFactor: animation,
child: InkWell(
onTap: () {
listKey.currentState.removeItem(index,
(context, animation) => buildItem(context, index, animation),
duration: const Duration(milliseconds: 300));
mainProvider.list.removeAt(index);
mainProvider.addCount();
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: _widget,
),
),
),
);
}
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(32.0),
child: mainProvider.list == null
? Container()
: AnimatedList(
key: listKey,
initialItemCount: mainProvider.list.length,
itemBuilder:
(BuildContext context, int index, Animation animation) =>
buildItem(context, index, animation),
),
),
),
);
}
}
You are retrieving your provider from a StatelessWidget. As such, the ChangeNotifier can't trigger your widget to rebuild because there is no state to rebuild. You have to either convert ParentWidget to be a StatefulWidget or you need to get your provider using Consumer instead of Provider.of:
class ParentWidget extends StatelessWidget {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
#override
Widget build(BuildContext context) {
return Consumer<MainProvider>(
builder: (BuildContext context, MainProvider mainProvider, _) {
...
}
);
}
As an aside, the way you are using provider is to add the MainProvider to its provider and then retrieve it from within its immediate child. If this is the only place you are retrieving the MainProvider, this makes the provider pattern redundant as you can easily just declare it within ParentWidget, or even just get your list of images using a FutureBuilder. Using provider is a good step toward proper state management, but also be careful of over-engineering your app.

How to create infinity PageView in Flutter

How to create an PageView which are supported circle scroll in Flutter? That's mean when I stand on 0 page, I could scroll to left to the last page.
Updated: I answered this question and update a gist source also.
What I did with mine was I set my page controller's initialPage to 10000 * pageCount, and in my page view itself, I have itemBuilder: (context, index) => pages[index % pageCount], and itemCount: null. It's not really infinite, but most users will not scroll 10000 pages back, so it works for my use case. As far as I know, there isn't an elegant way to make it truly infinite. You could probably set up a listener so that whenever the controller.page is about to become 0, you set it back to 10000 * pageCount or something similar.
I found a solution here. I create a CustomScrollView with 2 slivers. One for go forward, one for go back. However, I have to calculate if my list short.
typedef Widget Builder(BuildContext buildContext, int index);
class InfiniteScrollView extends StatefulWidget {
final Key center = UniqueKey();
final Builder builder;
final int childCount;
InfiniteScrollView(
{Key key, #required this.builder, #required this.childCount})
: super(key: key);
#override
_InfiniteScrollViewState createState() => _InfiniteScrollViewState();
}
class _InfiniteScrollViewState extends State<InfiniteScrollView> {
#override
Widget build(BuildContext context) {
return Container(
child: CustomScrollView(
center: widget.center,
scrollDirection: Axis.horizontal,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => widget.builder(
context, widget.childCount - index % widget.childCount - 1),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(widget.builder),
key: widget.center,
)
],
),
);
}
}
Updated: I write a new widget which support infinity TabBar.
https://gist.github.com/MrNinja/6f6a5fc73803bdfaf2a493a35c258fee