I am using GetX. I need when the user delete the image from ui, the card delete. But nothing deleted from UI. if i pressed hot restart it deleted from the UI.
The Code Is:
class SelectedImagesWidget extends GetView<AddProductController> {
#override
Widget build(BuildContext context) {
return Obx(
() => (controller.product.value.pickedImages.isEmpty)
? const SizedBox.shrink()
: SingleChildScrollView(
clipBehavior: Clip.none,
child: SizedBox(
child:ListView.builder(
itemCount: controller.product.value.pickedImages
.length,
itemBuilder: (context, index) {
return Obx(()=>
Stack(
clipBehavior: Clip.none,
children: [
// Image
Card(
clipBehavior: Clip.hardEdge,
child: // AssetThumb(
Image.file(
controller.product.value.pickedImages
.getOrCrash()[index],
),
),
// Delete Button
Positioned(
child: InkWell(
onTap: () async =>
await controller.deleteImage(index),
child: const CircleAvatar(
child: Icon(
Icons.delete_outlined,
),
),
),
);
}
}
you can use this for refresh list
controller.product.refresh()
you can use update() function at the end of your logic function in controller
Related
I cannot find a satisfactory way for a grandchild widget to trigger a grandparent state change. My app saves and sources its data all from an on-device database.
Ive tried to proceed this far without using a state management library as I thought this was overkill - the app is not complex.
Ive got a ListView (grandparent), which in turn has children that are my own version of ListTiles. There are two icon buttons on each ListTile, one to edit and one to delete - both of which trigger a different alertdialog (grandchild) popup. When I perform an update or delete on the data, it is written to the db and a Future is returned - and then I need the grandparent ListView state to refresh. StatefulBuilders will only give me a way to refresh state on the grandchild (separately from the child), not a way to trigger 'multi level' state change.
Is it time for a state management solution such as BLOC or Riverpod, or is there any other solution?
ListView Grandparent Widget
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// other children here
Expanded(
flex: 11,
child: FutureBuilder<List<MyCustomObject>>(
future: _getQuotes(), // queries the db
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting
&& !snapshot.hasData) {
return const Center(
child: SizedBox(
height: AppDims.smallSizedBoxLoadingProgress,
width: AppDims.smallSizedBoxLoadingProgress,
child: CircularProgressIndicator()
),
);
} else if (snapshot.hasError) {
log(snapshot.error.toString());
log(snapshot.stackTrace.toString());
return Center(child: Text(snapshot.error.toString()));
} else {
// no point using StatefulBuilder here, as i need
// to potentially trigger _getQuotes() again to rebuild the entire ListView
return ListView.builder(
padding: const EdgeInsets.symmetric(
horizontal: AppDims.textHorizontalPadding,
vertical: AppDims.textVerticalPadding
),
itemCount: snapshot.data!.length,
itemBuilder: (context, int index) {
return MyCustomTile(
// tile data mapping from snapshot for MyCustomObject
);
},
);
}
},
)
)
]
);
}
)
);
}
MyCustomTile Child Widget
#override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDims.tileBorderRadius),
side: const BorderSide(
color: Colors.green,
width: 1.5,
)
),
child: ListTile(
// other omitted ListTile params here
trailing: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return EditDialog();
}
).then((_) => setState(() {})), // will only setState on the dialog!
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => DeleteWarningDialog(
widget.id,
AppStrings.price.toLowerCase(),
true
),
),
),
]
),
),
);
}
DeleteWarningDialog Grandchild Widget
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(_buildFinalWarningString()),
actions: [
TextButton(
child: const Text(AppStrings.cancel),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: const Text(AppStrings.delete),
onPressed: () {
_appDatabase.deleteFoo(widget.objectIdToDelete);
Navigator.pop(context);
},
)
],
);
}
you will have to declare a function in the grandParent which is the listView in your case and pass it to parent and children's. but it will be so complicated and not really efficient, using state management would make it a lot easer and clean
I am having some problems with my flutter app. I am trying to add an overlay like this in the photo below:
And it works just fine, I am able to open it on long press and close it on tap everywhere else on the screen.
The problem is that those two buttons - delete and edit - should call a bloc method that then do all the logic, but I do not have a bloc provider above the OverlayEntry. This is the error:
Error: Could not find the correct Provider<BrowseBloc> above this _OverlayEntryWidget Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that _OverlayEntryWidget is under your MultiProvider/Provider<BrowseBloc>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>().toString()),
);
}
```
consider using `builder` like so:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context, child) {
// No longer throws
return Text(context.watch<Example>().toString());
}
);
}
```
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
I've already encountered this error but this time I'm in a bit of trouble because I'm working with an overlay and not a widget.
This is my code:
late OverlayEntry _popupDialog;
class ExpenseCard extends StatelessWidget {
const ExpenseCard({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocConsumer<AppBloc, AppState>(
listener: (context, state) {},
buildWhen: (previous, current) => previous.theme != current.theme,
builder: (context, state) {
return Column(
children: [
GestureDetector(
onLongPress: () {
_popupDialog = _createOverlay(expense);
Overlay.of(context)?.insert(_popupDialog);
},
child: Card(
...some widgets
),
),
const Divider(height: 0),
],
);
},
);
}
}
OverlayEntry _createOverlay(Expenses e) {
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () => _popupDialog.remove(),
child: AnimatedDialog(
child: _createPopupContent(context, e),
),
),
);
}
Widget _createPopupContent(BuildContext context, Expenses e) {
return GestureDetector(
onTap: () {},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.9,
decoration: BoxDecoration(
color: LocalCache.getActiveTheme() == ThemeMode.dark ? darkColorScheme.surface : lightColorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(16)),
),
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...some other widgets
],
),
),
SizedBox(
width: 256,
child: Card(
child: Column(
children: [
InkWell(
onTap: () {
_popupDialog.remove();
// This is where the error is been thrown
context.read<BrowseBloc>().add(SetTransactionToEdit(e));
showBottomModalSheet(
context,
dateExpense: e.dateExpense,
total: e.total,
transactionToEdit: e,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
children: [Text(AppLocalizations.of(context).edit), const Spacer(), const Icon(Icons.edit)],
),
),
),
const Divider(height: 0),
InkWell(
onTap: () {
_popupDialog.remove();
// This is where the error is been thrown
context.read<BrowseBloc>().add(DeleteExpense(e.id!, e.isExpense));
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
children: [Text(AppLocalizations.of(context).delete), const Spacer(), const Icon(Unicons.delete)],
),
),
),
],
),
),
),
],
),
);
}
How can I add the bloc provider above my OverlayEntry? Is this the best course of action?
Thank you to everyone that can help!
Wrap your widget that you use in OverlayEntry in BlocProvider.value constructor and pass the needed bloc as an argument to it, like so
OverlayEntry _createOverlay(Expenses e, ExampleBloc exampleBloc) {
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () => _popupDialog.remove(),
child: BlocProvider<ExampleBloc>.value(
value: exampleBloc,
child: AnimatedDialog(
child: _createPopupContent(context, e),
),
),
),
);
}
I have found a solution starting from the answer of Olga P, but changing one thing. I use the BlocProvider.value but I am passing as an argument to the method the context and not the bloc itself. This is the code:
OverlayEntry _createOverlay(Expenses e, BuildContext context) {
return OverlayEntry(
builder: (_) => GestureDetector(
onTap: () => _popupDialog.remove(),
child: BlocProvider<BrowseBloc>.value(
value: BlocProvider.of(context),
child: AnimatedDialog(
child: _createPopupContent(context, e),
),
),
),
);
}
With this change the two methods - edit and delete - work perfectly. Thanks to everyone who replied, I learned something today too!
The problem is that you are using a function and not a widget. So you can either modify _createOverlay to be stateless or stateful widget, or you can pass the bloc as an argument to the function.
In the latter case this would be _createOverlay(expense, context.read<AppBloc>())
I have been trying to add drag/drop support to my app, currently what I have come with is using this library:
reorderable_grid_view
I used this example code:
code link
The reason I used this library is that it's smooth enough of animations when dragging. But what I want to do is to drag one item to another so that I can merge the one to another object when I drop. (It's like in Android/iOS home screen where you can drag apps to folders or drag into another that it creates a folder)
I have searched all the site but couldn't come across with such thing, only drag/drop libraries are available. Can anyone help me on this?
Thanks in advance.
class MyHomePage extends StatelessWidget {
MyHomePage({super.key});
final ValueNotifier<List<ValueNotifier<List<Widget>>>> items = ValueNotifier([
ValueNotifier([Text("A")]),
ValueNotifier([Text("B")]),
ValueNotifier([Text("C")]),
ValueNotifier([Text("D")]),
]);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: ValueListenableBuilder(
valueListenable: items,
builder: (BuildContext context, List<ValueNotifier<List<Widget>>> folders, Widget? child) {
return GridView.builder(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemCount: folders.length,
itemBuilder: (context, index) {
ValueNotifier<List<Widget>> item = folders[index];
return LongPressDraggable(
delay: const Duration(milliseconds: 500),
feedback: SizedBox(width: MediaQuery.of(context).size.width / 4, height: MediaQuery.of(context).size.width / 4, child: FittedBox(child: Icon(Icons.folder))),
data: index,
childWhenDragging: const SizedBox(),
child: DragTarget(
onAccept: (data) {
List<Widget> alreadyHaved = item.value;
alreadyHaved.addAll(folders[data as int].value);
item.value = alreadyHaved;
items.value.removeAt(data);
items.notifyListeners();
},
builder: (context, candidateData, rejectedData) {
return ValueListenableBuilder(
valueListenable: item,
builder: (BuildContext context, List<Widget> boxValues, Widget? child) {
return Stack(children: [
const Positioned.fill(
child: FittedBox(
child: Icon(
Icons.folder,
color: Colors.amber,
))),
Positioned.fill(
child: LayoutBuilder(
builder: (p0, p1) => SizedBox(
height: p1.maxHeight * .7,
width: p1.maxWidth * .7,
child: Center(
child: Wrap(
children: boxValues,
),
))),
)
]);
},
);
},
),
);
});
},
),
),
),
],
),
);
}
}
Im trying to show a listView.builder inside a AlertDialog, and Im filling the its list by calling a function everytime the button to open the AlertDialog is pressed but the problem is that the ui doesn’t update when the list is filled with the data, I'm using getX and I'm very new to it, can someone show me what I'm doing wrong?
I'm using the GetX builder:
GetX<DashboardController>(
init: Get.put<DashboardController>(DashboardController()),
builder: (DashboardController dashboardController) {
return GridView.builder(
My Get.dialog function:
return GestureDetector(
onTap: () {
// this is where I'm filling the list
dashboardController
.callEmployeeCheckInOutList(_employeeModel.id);
Get.dialog(
AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
height: size.height * 0.55,
width: size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
EmployeeProfileWidget(
size: size,
profileBackgroudPath: profileBackgroudPath,
employeeModel: _employeeModel,
),
// this is where my listview.builder resides
EmployeeActivityWidget(
closeCrossPath: closeCrossPath,
employeeCheckInOutList:
_employeeCheckInOutList,
employeeModel: _employeeModel,
onTap: () {},
),
],
),
),
),
);
},
My listview.builder:
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 32.0,
right: 50.0,
),
child: ListView.builder(
itemCount: employeeCheckInOutList.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final _checkInOutModel = employeeCheckInOutList[index];
return SizedBox(
height: 120,
child: TimelineTile(
beforeLineStyle: const LineStyle(
color: Color(0xffa5affb),
),
My Controller:
Rx<List<CheckInOutModel>> _employeeCheckInOutList =
Rx<List<CheckInOutModel>>([]);
List<CheckInOutModel> get employeeCheckInOutList =>
_employeeCheckInOutList.value;
Future<void> callEmployeeCheckInOutList(String id) async {
_employeeCheckInOutList =
await EmployeeService.employeeCheckInOutFuture(docId: id);
update();
}
Use .assignAll method on the RxList to trigger UI update:
Future<void> callEmployeeCheckInOutList(String id) async {
final result = await EmployeeService.employeeCheckInOutFuture(docId: id);
_employeeCheckInOutList.assignAll(result);
}
And you don't need to call update() when using Rx.
I already faced same issue.
Solution:
Simply use again GetX<Controller> inside AlertDialog
like
GetX<DashboardController>(
init: Get.put<DashboardController>(DashboardController()),
builder: (DashboardController dashboardController) {
return GridView.builder(
.....
Get.dialog(
AlertDialog(
contentPadding: EdgeInsets.zero,
content: GetX<DashboardController>(
init: Get.put<DashboardController>(DashboardController()),
builder: (DashboardController dashboardController) {
SizedBox(
How to control listviewbuilder from outside the listview in flutter?
In a textfield I can use a controller like so: controller: Textcontroller. Can I do something similar in listviewbuilder to clear all the objects in it?
So to be exact. My code looks something like this
Expanded(
child: new ListView.builder(
itemCount: List.length,
itemBuilder: (context,index){
return new Card(
//all stuff with data
),
),
);
},
....
How would I do so that when called from another function it removes all the items in the listview?
You need a Store class that holds this list along with list manipulation methods, then you can use provider for example to access that class and render that list.
You can define a variable in the state of your widget:
var _clear = false;
When this variable is true, the list will be cleared and when it's false, the list will be displayed. You can use setState to toggle this variable. Setting the itemCount of the ListView.builder to 0 clears the list.
Full code:
var _clear = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(30.0),
child: FlatButton(
child: Text('clear'),
color: Colors.pinkAccent,
onPressed: () {
setState(() {
_clear = true;
});
},
),
),
FlatButton(
child: Text('add'),
color: Colors.greenAccent,
onPressed: () {
setState(() {
_clear = false;
});
},
),
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _clear ? 0 : 100,
itemBuilder: (context, index) {
return Container(
height: 300,
child: Card(
child: Center(
child: Text('$index'),
),
),
);
},
),
),
],
),
);
}