How to add new cards to gridview on tap of button flutter - flutter

I am trying to code something for my flutter app.
I have an empty gridview (5x6 configuration) on center top and 5 colored buttons (color containers) at the bottom (each with a different color), and a remove all button on the bottom right).
When I tap one of the 5 buttons, a container (or card) appears on the left in the gridview, having the same color of the button. When I press another button, another container appears, and so on (untill 30 containers appear - limit). I can make any mix of containers.
When I then press the remove all button, all containers in the grid view are removed.
I also have to use flutter riverpod for statemanagement and divide custom widgets in different files.
Can anyone tell me or code this for me?
I’m a bit stuck atm.
Thank you in advance! 🙏
MY CODE (lib folder of project)
enter link description here

I've created a flutter app that demonstrates a grid painter that you might be looking for (Gif)
In order to do this I have used the Bloc pattern using (flutter_bloc) library.
It helps to start by defining the initial state of the app which is a list of 30 transparent tiles(5x6)
part of 'painter_bloc.dart';
#immutable
abstract class PainterState extends Equatable {
final List<Tile> tiles;
const PainterState(this.tiles);
#override
List<Object?> get props => [tiles];
}
class PainterInitial extends PainterState {
PainterInitial() : super(List.generate(30, (index) => Tile.empty(index)));
}
Each Tile(defined model class) holds two properties
class Tile {
final int index;
final Color color;
Tile({
required this.index,
required this.color,
});
factory Tile.empty(int index) =>
Tile(index: index, color: Colors.transparent);
There is one event that is dispatched from the UI to the PainterBloc i.e. ColorTapped (An event that is trigged when one of the colored cards is tapped)
This event holds two pieces of information(card color, currentTileIndex)
part of 'painter_bloc.dart';
#immutable
abstract class PainterEvent {}
class ColorTapped extends PainterEvent {
final Color color;
final int currentTileIndex;
ColorTapped(this.color, this.currentTileIndex);
}
One the PainterBloc receives the ColorTapped event. It emits a PainterMarked state with a modified list of Tiles
class PainterBloc extends Bloc<PainterEvent, PainterState> {
PainterBloc() : super(PainterInitial()) {
on<ColorTapped>(_onColorTapped);
}
void _onColorTapped(ColorTapped event, emit) {
List<Tile> markedTiles = <Tile>[...state.tiles];
markedTiles[event.currentTileIndex] = state.tiles
.where((element) => element.index == event.currentTileIndex)
.first
.copyWith(color: event.color);
emit(PainterMarked(markedTiles));
}
This new state is used to rebuild the UI thereby coloring the tiles.
I have also used a CounterBloc to help keep track of the pointer/index of the grid tiles to color.
You can find the complete code here

Related

Custom Event listeners in flutter

I have a widget with a list and a button with a tree dot icon in every row that shows and hides a panel in its own row. I only want one panel open in the list. When I click on a row button, I'd like to close the panels of the other rows list.  All the buttons in the list are siblings. I'd like to send an event to the other rows' code to close the panels. Which is the correct manner of flutter?  
I have tried NotificationListener but it does not work because the components to be notified are not their parents.
The question is if the correct thing to do is to use the event_listener library or to use streams. I'm new to flutter/dart and streams seem too complex to me. It's a very simple use case and in this entry
Flutter: Stream<Null> is allowed?
they say
*
Some peoples use streams as a flux of events instead of a value
changing over time, but the class isn't designed with this in mind.
They typically try to represent the following method as a stream:
So with simple events with 0 or 1 argument. event_listener or Streams?
This is the screen I'm working on. I want that when one yellow button panel opens the other one closes.
Your question is broad and it seems to be a design question, i.e. it doesn't have a right answer.
However, I don't think you should use Streams or EventListeners at all in this case, because you should not make components in the same layer communicate with each other. Components should only communicate with their parents and children, otherwise your code will increase in complexity really fast. That's even documented in flutter_bloc.
Other than that, if you don't lift state up, i.e. move the responsibility of triggering the removal of the other rows to a parent Widget, than you're fighting against Flutter instead of letting it help you.
It's easy to create a parent Widget, just wrap one Widget around it. What you want to do is hard, so why would try to communicate with sibling widgets instead of using what's Flutter designed to do?
This is a suggestion:
class _NewsSectionState extends State<NewsSection> {
Widget build(BuildContext context) {
return ListView.builder(
itemCount: newsInSection.length;
itemBuilder: (_, int index) => NewsTile(
title: Text('${newsInSection[index].title}')
onDismiss: () => onDismiss(index),
// I don't know how you set this up,
// but () => onDismiss(Index)
// should animate the dismiss of the Row with said index
),
);
}
}
class NewsRow extends StatefulWidget {
final void Function() onDismiss;
#override
State<NewsRow> _createState => _NewsRowState();
}
class _NewsRowState extends State<NewsRow> {
Widget build(BuildContext context) {
return Row(
children: [
// title
// home button
// fav button
// remove button
IconButton(
Icons.close,
onPressed: widget.onDismiss,
),
],
);
}
}

Navigation to screen with Flutter Hooks not working

Navigating from a button press to a screen with HookConsumerWidget or HookWidget gives an error: "Hooks can only be called from within the build method of a widget that mix-in hooks".
But I thought the button that navigates to the screen is already within the build method of the calling screen. I converted the calling screen to a HookWidget just in case that was the issue but no lukc as I get the same error. I am not understanding this error.
snippet in a stateless widget containing button to navigate:
_BackLayerButton(
icon: Icons.upload,
title: 'Upload Product',
onPressed: () {
Navigator.of(context)
.pushNamed(UploadProductScreen.routeName);
},
),
UploadProducScreen that is final destination:
class UploadProductScreen extends HookConsumerWidget {
static const routeName = 'uploadProductScreen';
UploadProductScreen({Key? key}) : super(key: key);
final GlobalKey<FormState> _formKey = GlobalKey();
final _file = useState<File?>(null);
final _imageFile = useState<XFile?>(null);
final _imagePicker = ImagePicker();
#override
Widget build(BuildContext context, WidgetRef ref) {
final category = ref.watch(categoryControllerProvider).productCategory;
String _productTitle = '';
When I replace the above code with a simple Stateless widget, everything works. How can one navigate to a screen with Hooks via button pressed from another screen? Please help as I have tried everything I know to do. The same code works for stateless and stateful widgets for navigation but not for HookWidget. I need to use hooks for other features of the app so abandoning hooks is not my choice. Thanks a million.
Problem identified and resolved after combing through the code line by line and by process of elimination. The error was specifically in regards to the HookWidget class (screen) that was called and not with the calling screen.
I found that a couple of final variables (eg emailTextEditingController) defined to utilize useTextEditingController() were defined outside of the build method of the widget. Once I relocate these variables appropriately to within the build method of the widget, the error went away and navigation happened as desired. Phew!!! This took me 3 days but thank God for the insight. I still have other open issues that I seriously need help with; e.g. animation not working using HookWidget but works in Stateful class.

Flutter - PageView - Only scroll when you swipe on top of a specific component instead of any location of the page

Totally new to flutter, less than 10h on it.
I have a PageView handing 3 pages, it scrolls horizontally through them perfectly.
But I would like to change the "allowed swipe" area. I don't want to let the user change the pages scrolling from any position of the page, but instead, for example, just let him scroll the pages if he swipes on the AppBar component for example.
I saw the PageView has the applyTo method, I'm just lost about how to give to it the ID(keys) or the appBar to see if this will work.
Is there a way of achieving this "only scroll if the user swipes on the component X"?
Edit 1
Solution suggested by Alberto Miola works like a charm, here is my code (I had to implement from PreferredSizeWidget since is required to modify the AppBar).
class GestureDetectorForAppBar extends StatelessWidget implements PreferredSizeWidget {
final double height;
final AppBar appbar;
final GestureDragUpdateCallback onPanUpdate;
GestureDetectorForAppBar({
Key key,
this.appbar,
this.onPanUpdate,
#required this.height,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(child: this.appbar, onPanUpdate: this.onPanUpdate,);
}
#override
Size get preferredSize => Size.fromHeight(height);
}
First of all, you need to "block" the swipe gesture on the PageView itself using NeverScrollableScrollPhysics:
PageView(
physics: const NeverScrollableScrollPhysics(),
children: [...],
controller: ...
);
Note the usage of a const constructor. In this way you won't be able to move among pages with your finger. In Flutter you have widgets and not components.
Give a look at the GestureDetector widget which can be used to listen on swipes and change the currently visible content of your PageView. It can be used to detect swipes:
GestureDetector(
onPanUpdate: (data) {
if (data.delta.dx > 0) {
// right swipe
}
if (data.delta.dx < 0) {
// right swipe
}
}
);
In order, I suggest you to first read about NeverScrollableScrollPhysics() in the official documentation. It's used to "block" scrolling behaviors. Then, use GestureDetector() to wrap the widget you want to be used as a "scroll director" (the one that actually scrolls the pages).
Inside onPanUpdate you'll deal with the animateToPage method of the PageController to change the currently visible page.

How do I keep my widget state when reordering them via Draggable&DragTarget?

I'm using this reorderables package. This package works by having a list of children widgets that are each wrapped with a Draggable and put inside a DragTarget. Before that the childs key is assigned to a GlobalObjectKey.
After the dragTarget is created, it is assigned(or rebuild?) to a KeyedSubTree:
dragTarget = KeyedSubtree(key: keyIndexGlobalKey, child: dragTarget);
According to the comments in the package source code, this should preserve the child widgets state (toWrap) when being dragged:
// We pass the toWrapWithGlobalKey into the Draggable so that when a list
// item gets dragged, the accessibility framework can preserve the selected
// state of the dragging item.
final GlobalObjectKey keyIndexGlobalKey = GlobalObjectKey(toWrap.key);
The reordering itself happens not with the DragTarget accepting the Draggable dragged into it, but rather by using the DragTarget around each child to get index of the current position the Draggable is hovering over. When the Draggable is let go, a reorder function will get called, which removes the widget (that was being dragged) from the list and inserting it into the new position.
Now comes my problem: The state of the widget is not being preserved. I made a simple TestWidget to test this:
class TestWidget extends StatefulWidget{
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
Color boxColor;
#override
void initState() {
super.initState();
boxColor= Colors.blue;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
decoration: BoxDecoration(color: boxColor),
child: Text("Test"),
),
FlatButton(
onPressed: (){
setState(() {
boxColor = Colors.red;
});
},
padding: EdgeInsets.all(8.0),
child: Text("Change to Red"),
color: Colors.grey,
)
],
);
}
}
This widget has a Container with a initial blue background (boxColor) and a button. When the button is pressed, it will change the boxColor to red. The moment the dragging on the widget is initiated, it is rebuild and defaults to the initial state (at least the Draggable feedback is). After the reordering that doesn't change and the widget is still in it's default state.
My plan here is to have a list of different custom widgets, where the User can modify their content and if they are not happy with the order, they can drag those widgets around and rearrange them.
My question is: How do I preserve the state of my widgets?
I'm thinking of creating a class for each widget with all state relevant variables and use that to build my widgets but that seems very bloated and not really in the mind of flutter. Isn't that supposed to be the role of the state of the StatefulWidget?
EDIT:
So I solved my problem by creating an additional class for my widget state with ChangeNotifier and then moving all my variables that I want to keep track of into this class. So I basically now have two lists, one for my widgets in the reorderable list and one for their states. I still think that this is kinda scuffed. If a widget in my list has additional children of its own, I would need to create separate state classes for each of them that need it and save them somewhere. This can get very messy, very quickly.

Keep a list from provider up to date with AnimatedListView

I'm using the Provider library and have a class that extends ChangeNotifier. The class provides an UnmodifiableListView of historicalRosters to my widgets.
I want to create an AnimatedListView that displays these rosters. The animated list supposedly needs a separate list which will tell the widget when the widget to animate in or out new list items.
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
This example uses a model to update both the list attached to the widget's state and the AnimateListState at the same time.
// Inside model
void insert(int index, E item) {
_items.insert(index, item);
_animatedList.insertItem(index);
}
I want to do the same thing but I'm having trouble thinking how. That is, what's a good way I can update the list provided by provider and also change the AnimatedListState.