I am having trouble where a Slidable does not get removed when I pressed delete action as shown in an image below.
The problem is in class NotificationInputPage where onDismissed got triggered, but notificationList.removedAt(index); does not seems to work.
If you spot something isn't right, please let me know. Thank you so much.
return SlidableWidget(child: notification, onDismissed: (context) {
setState(() {
notificationList.removeAt(index);
print('deleted..');
});
});
Here is my code
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class SlidableWidget extends StatelessWidget {
final Widget child;
final void Function(BuildContext context) onDismissed;
const SlidableWidget({required this.child, required this.onDismissed , Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => Slidable(
child: child,
endActionPane: ActionPane(motion: ScrollMotion(), children: [
SlidableAction(
flex: 1,
onPressed: onDismissed,
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.delete,
label: 'delete',
),
],
extentRatio: 0.2,),
);
}
import 'package:carwashqueue/constants/constants.dart';
import 'package:carwashqueue/constants/enumerations.dart';
import 'package:carwashqueue/models/carwash_notification.dart';
import 'package:carwashqueue/widget/slidable_widget.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class NotificationInputPage extends StatefulWidget {
const NotificationInputPage({Key? key}) : super(key: key);
#override
_NotificationInputPageState createState() => _NotificationInputPageState();
}
class _NotificationInputPageState extends State<NotificationInputPage> {
DateTime thirtyDaysAgo = DateTime.now().subtract(const Duration(days: 30));
final _auth = FirebaseAuth.instance;
final _fireStore = FirebaseFirestore.instance;
#override
Widget build(BuildContext context) {
Stream<List<CarWashNotification>> readNotification() => _fireStore
.collection('notifications')
.orderBy('notification_date', descending: false)
.where('notification_date',
isGreaterThanOrEqualTo: thirtyDaysAgo)
.snapshots()
.map((snapshot) => snapshot.docs.map((doc) => CarWashNotification.fromJson(doc.data(), doc)).toList());
return Scaffold(
body: StreamBuilder<List<CarWashNotification>>(
stream: readNotification(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('There is an error. Please try again.');
} else if (snapshot.hasData) {
final notifications = snapshot.data!;
List<Widget> notificationList = notifications.map((e) => buildListTile(e)).toList();
// return ListView(children: notificationList,);
return ListView.separated(
itemBuilder: (context, index) {
final notification = notificationList[index];
return SlidableWidget(child: notification, onDismissed: (context) {
setState(() {
notificationList.removeAt(index);
print('deleted..');
});
});
},
separatorBuilder: (context, index) => Divider(),
itemCount: notificationList.length);
} else {
return Center(
child: Text('There is no notification at the moment.'));
}
}),
);
}
Widget buildListTile(CarWashNotification item) {
return ListTile(
leading: FaIcon(
FontAwesomeIcons.bullhorn,
size: 30.0,
color: mainColour1,
),
title: Text(item.title),
subtitle: Text(item.message.length > textLimit
? item.message.substring(0, textLimit) + '...'
: item.message),
onTap: () {
print('onTap');
},
);
}
You should use dissmissible widget for this.
Dissmissible Widget
Dissmissible Widget Swiping
Now I understand why item didn't get removed from the list.
Actually it did get removed but when setState function runs, it reruns the build method so all the items are retrieved from firebase and reassigned to the list. That's why the item looks like it's never been removed from the list.
So What I will need to do is to really delete selected item from firebase or I will need to use a different mechanism.
Related
I'm trying to show the full image that has been clicked on from my drawer.
I have a liste of images that I display in my drawer and What I want is that when I click on a specific image, it closes my drawer and show the image on my screen in a kind of an image slider where I can switch images directly from the opened image.
here is my code where I extract my list of images from my asset folder :
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class GetImages extends StatefulWidget {
const GetImages({super.key});
#override
State<GetImages> createState() => _GetImagesState();
}
class _GetImagesState extends State<GetImages> {
List<String> imagePaths = [];
#override
void initState() {
_initImages();
super.initState();
}
Future _initImages() async {
final Map<String, dynamic> assets =
jsonDecode(await rootBundle.loadString('AssetManifest.json'));
setState(() {
imagePaths = assets.keys
.where((String key) => key.contains('photos/'))
.where((String key) => key.contains('.JPG'))
.toList();
});
}
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return GridView.count(
crossAxisCount: constraints.maxWidth > 700 ? 4 : 2,
children: imagePaths
.map(
(path) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 17),
child: Image.asset(path),
),
)
.toList(),
);
});
}
}
And here is my code for my drawer :
import 'package:flutter/material.dart';
import 'package:myapp/widgets/get_images.dart';
import 'package:image_viewer/image_viewer.dart';
class SideBar extends StatelessWidget {
const SideBar({super.key, required this.title});
final String title;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: const Center(
child: Text('My Page!'),
),
drawer: Drawer(
child: InkWell(
child: GetImages(),
onTap: () {
//ImageViewer.showImageSlider(images: ["assets/photos/IMG_4100.JPG"]);
// montre la photo et ferme la sidebar
Navigator.pop(context);
},
),
),
);
}
}
Thanks in advance for your help :)
You could try this package that i've used before https://pub.dev/packages/lightbox seems like it does exactly what you are looking for.
I'm trying to use Pagination in my Flutter app.
I have A List called movies and I'm storing the data List coming from Api with the current page specified and expose it in a ListView.
And I have a ScrollController and I'm checking if I reached the end of the screen I call again the function with the new data by changing the page number and I add the new List to the old list movies.
But when I reach the end of the screen the new List show up and the old List disappears,
Have I miss something?
How can I solve this problem
Here's the code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:movie_app/res/constants/colors.dart';
import'package:movie_app/ui/pages/home_page/home_cubit/
home_cubit/top_rated_bloc/top_rated_cubit.dart';
import '../../../models/movies.dart';
import '../../bloc_cubit/movies_status.dart';
class TopRatedMovies extends StatefulWidget {
const TopRatedMovies({Key? key}) : super(key: key);
#override
State<TopRatedMovies> createState() => _TopRatedMoviesState();
}
class _TopRatedMoviesState extends State<TopRatedMovies> {
ScrollController scrollController = ScrollController();
List<Movies> movies = [];
#override
void initState() {
BlocProvider.of<TopRatedCubit>(context).getTopRatedMovies();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyColor.primaryColor,
appBar: AppBar(
backgroundColor: MyColor.primaryColor,
title: const Text(
'TOP RATED MOVIES'
),
),
body: BlocConsumer<TopRatedCubit,TopRatedState>(
listener: (context, state){},
builder: (context, state){
var cubit = BlocProvider.of<TopRatedCubit>(context);
movies = state.topRated;
switch(state.status){
case MoviesStatus.loading:
return const Center(child: CircularProgressIndicator());
case MoviesStatus.initial:
case MoviesStatus.success:
case MoviesStatus.failure:
return ListView.separated(
controller: scrollController..addListener(() {
if(scrollController.position.pixels ==
scrollController.position.maxScrollExtent){
cubit.currentPage++;
BlocProvider.of<TopRatedCubit>(context).getTopRatedMovies();
movies = [...movies, ...state.topRated];
}
}),
itemCount: movies.length,
itemBuilder: (context, index){
return Text(
'${movies[index].title}',style: const TextStyle(color: Colors.white),
);
},
separatorBuilder: (context, index){
return const SizedBox(height: 40,);
}
);
}
},
),
);
}
}
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:meta/meta.dart';
import '../../../../../../models/movies.dart';
import '../../../../../../repositories/movies_repo.dart';
import '../../../../../bloc_cubit/movies_status.dart';
part 'top_rated_state.dart';
class TopRatedCubit extends Cubit<TopRatedState> {
TopRatedCubit({required this.moviesRepo}) : super(const TopRatedState());
final MoviesRepo moviesRepo;
int currentPage = 1;
Future<List<Movies>> getTopRatedMovies() async{
emit(
state.copyWith(
status: MoviesStatus.loading,
)
);
moviesRepo.getTopRatedMovies(page: currentPage).then((topRatedMovies) {
emit(
state.copyWith(
status: MoviesStatus.success,
topRated: topRatedMovies
),
);
}).catchError((error){
emit(
state.copyWith(
status: MoviesStatus.failure,
exception: error
)
);
}
);
return [];
}
}
I am working on a simple ListView. I managed to update the list view with the correct data, see items which is <String>[].obs, when it got populated the data, I can see the list view is populated.
However, it seems after the list view items are built, they are not observing my selected change, which is 0.obs. From the debugging code, I can see the selected got updated, the title changes accordingly, but the list view items did not rebuild (and hence change color), and not reflecting if they are being selected.
Please help me to understand why selected change did not trigger list item rebuild, and how to fix. Thanks!
My home_controller.dart:
import 'package:get/get.dart';
class HomeController extends GetxController {
final loading = true.obs;
final items = <String>[].obs;
final selected = 0.obs;
final count = 0.obs;
#override
void onInit() {
fetchItems();
super.onInit();
}
Future<void> fetchItems() async {
loading.value = true;
Future.delayed(const Duration(seconds: 5), () {
final newItems = ['abc', 'def', 'ghij', 'klmnopq'];
items.assignAll(newItems);
loading.value = false;
});
}
void onHover(int index) {
selected.value = index;
print('onHover: $index');
}
}
And my home_view.dart:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class HomeView extends GetView<HomeController> {
const HomeView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:
Obx(() => Text('HomeView: selected ${controller.selected.value}')),
centerTitle: true,
),
body: Obx(() => Center(
child: controller.loading.value
? const CircularProgressIndicator()
: ListView.builder(
itemCount: controller.items.length,
itemBuilder: (BuildContext context, int index) {
final color = controller.selected.value == index
? Colors.green
: Colors.grey;
return MouseRegion(
onHover: (event) {
controller.onHover(index);
},
onExit: ((event) {
final selected = controller.selected.value;
print(
'exiting: $index, current selected: ${selected}');
}),
child: ListTile(
leading:
Container(width: 40, height: 40, color: color),
title: Text(controller.items[index]),
),
);
},
),
)),
);
}
}
I believe wrapping your MouseRegion with another Obx would solve it for you. It being inside another builder will not make it able to be observed by the outer Obx
I have the following code to build a listview from local JSON file and it works perfectly fine. However, when I try to add a method such as onTap: (){} to the ExpansionTile in the _buildList Widget I got the following error
Error: No named parameter with the name 'onTap'. onTap: (){}, ^^^^^ /C:/src/flutter/packages/flutter/lib/src/material/expansion_tile.dart:51:9: Context: Found this candidate, but the arguments don't match. const ExpansionTile({ ^^^^^^^^^^^^^
The code in Main.dart is
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'datamodel.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<List<Menu>> ReadJsonData() async {
//read json file
final jsondata = await rootBundle.loadString('assets/data0.json');
//decode json data as list
final list = json.decode(jsondata) as List<dynamic>;
//map json and initialize using Model
return list.map((e) => Menu.fromJson(e)).toList();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(
appBar: AppBar(
title: const Text('My Title'),
),
body: FutureBuilder(
future: ReadJsonData(),
builder: (context,data){
if(data.hasError){
return Center(child: Text("${data.error}"));
}else if(data.hasData){
var items =data.data as List<Menu>;
return ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) =>
_buildList(items[index]),
);
}else{
return Center(child: CircularProgressIndicator(),);
}
},
)
)
);
}
Widget _buildList(Menu list) {
return ExpansionTile(
leading: Icon(list.icon),
// line causing error
onTap: (){},
title: Text(
list.name!,// I added !
style: TextStyle(fontSize: list.font?.toDouble(), fontWeight: FontWeight.bold),
),
children: list.subMenu!.map(_buildList).toList(),// I added !
);
}
}
So is there any way to add the Method for each Expansion tile?
Thank you in advance!
ExpansionTile does not have an onTap property. This is because it has a default behaviour on tapping, expands or collapses.
If you'd like to execute some specific logic on expanded or collapsed, you can use onExpansionChanged:
return ExpansionTile(
onExpansionChanged: (bool expanded) {
// do what you want
},
);
I have to build multiple future builders for different types of categories for a screen, like: Weekly deal, All, Newly Arrived, etc. My codes right now is pretty barebone, but here it is.
+Home Screen
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:vgo_customer_side/models/Product.dart';
import 'package:vgo_customer_side/repos/ProductRepo.dart';
import 'package:vgo_customer_side/widgets/Boxes.dart';
import 'package:vgo_customer_side/widgets/MyFunction.dart';
class GeneralScreen extends StatefulWidget {
const GeneralScreen({Key? key}) : super(key: key);
#override
_GeneralScreenState createState() => _GeneralScreenState();
}
class _GeneralScreenState extends State<GeneralScreen> with AutomaticKeepAliveClientMixin<GeneralScreen> {
List list = ["Weekly Deal", "Relevant", "Freshly"];
late Future<Product> futureProduct;
#override
void initState(){
futureProduct = readAllProducts();
super.initState();
}
String? rise;
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
constraints: BoxConstraints.tightForFinite(),
child: SingleChildScrollView(
child: Column(
children: [
deliverField(),
SizedBox(height: 29,),
FutureBuilder<Product>(
future: readAllProducts(),
builder: (context, AsyncSnapshot<Product> snapshot){
if(snapshot.hasData){
return RowBoxes(categoryName: "Weekly Deal", icon: Icon(Icons.arrow_forward, color: Colors.orange,));
}
return Text("waiting");
}),
SizedBox(height: 10,),
ElevatedButton(onPressed: (){
setState(() {
futureProduct = readAllProducts();
});
}, child: Text("press me")),
Center(child: Text("All")),
Center(child: Text("Just for you")),
_justForYou(),
],),
),
);
}
_justForYou(){
return Container();
}
_bottomGrid(){
return Container();
}
}
+RepoProduct
import 'package:vgo_customer_side/models/ApiRespone.dart';
import 'package:vgo_customer_side/models/Product.dart';
import 'package:http/http.dart' as http;
Future<Product> readAllProducts() async{
final response = await http.get(Uri.parse('https://vgo-buyer.herokuapp.com/api/v1/shopping/products/'));
if(response.statusCode == 200){
for(dynamic data in getAllProductsResponseFromJson(response.body).payload) {
return Product.fromJson(data);
}
throw Exception("Failed to load Products");
}
else{
throw Exception("Failed to load Products");
}
}
Now, having to add each "Weekly deal, All, Newly Arrived" to a futurebuilder is a lot of boilerplates, and I would like to simplify it into a reusable widget instead. Like I do with RowBoxes().
I know how to make normal reusable widgets but when it comes to a futurebuilder, it requires generic type(for the futurebuilder widget) and specific class for its "future:" function. Can anyone show me how to do it?
You can write a generic widget similar to this one:
class DealsWidget<T> extends StatelessWidget {
final Future<T> future;
final String category;
final IconData iconData;
final Color color;
final String loadingText;
DealsWidget({
this.future,
this.category,
this.iconData,
this.color,
this.loadingText,
});
#override
Widget build(BuildContext context) {
return FutureBuilder<T>(
future: future,
builder: (context, AsyncSnapshot<T> snapshot) {
if (snapshot.hasData) {
return RowBoxes(
categoryName: category,
icon: Icon(
iconData,
color: color,
),
);
}
return Text(loadingText);
},
);
}
}
And use it like:
DealsWidget<Product>(
future: readAllProducts(),
category: 'Weekly Deals',
iconData: Icons.arrow_forward,
color: Colors.orange,
loadingText: 'Please Wait...'
)