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 [];
}
}
Related
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 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.
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...'
)
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
class SearchView extends StatefulWidget {
#override
_SearchViewState createState() => _SearchViewState();
}
class _SearchViewState extends State<SearchView> {
List data = [];
Future<String> getData() async {
http.Response res = await http
.get(Uri.parse("https://gcse.doky.space/api/schedule/buildings"));
this.setState(() {
data = jsonDecode(res.body)["result"];
});
return "success";
}
#override
void initState() {
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('건물 선택')),
body: new ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Text(data[index]),
);
},
),
);
}
}
I am making lecture room reservation system.
But each listview block is too small so it's hard to click.
How to make text and block larger?
also where should I put onTap to make listview touchable?
Might I suggest you use a ListTile instead of a Text?
ListTile(
onTap: _itemtapped,
title: Text(myText)
)
void _itemtapped() {
// do you thing here
}
Use InkWell widget. It will give you a nice tap effect.
InkWell(
child: Text(data[index]),
onTap: _onTap,
)
And implement on tap:
void _onTap() {}
Here there are my two Dart file from that I retrieve my Video from firestore but in that, I made video array and try to retrieve it but it gives me an error , if I am doing it with string instead of array It looks fine but in my project, I am requiring array so please help me to finger out.
Code:1 main.dart
import 'dart:io';
import 'chewie_list_item.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:video_player/video_player.dart';
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.dumpErrorToConsole(details);
if (kReleaseMode) exit(1);
};
runApp(MaterialApp(
home: CourseApp(),
));
}
class CourseApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Demo"),
),
body: StreamBuilder(
stream: Firestore.instance.collection("courses").snapshots(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot courses = snapshot.data.documents[index];
return (ChewieListItem(
videoPlayerController: VideoPlayerController.network(
courses['video'] ?? 'default'),
));
},
);
},
));
}
}
[code 2: chewie_list_item.dart]
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class ChewieListItem extends StatefulWidget {
// This will contain the URL/asset path which we want to play
final VideoPlayerController videoPlayerController;
final bool looping;
ChewieListItem({
#required this.videoPlayerController,
this.looping,
Key key,
}) : super(key: key);
#override
_ChewieListItemState createState() => _ChewieListItemState();
}
class _ChewieListItemState extends State<ChewieListItem> {
ChewieController _chewieController;
#override
void initState() {
super.initState();
// Wrapper on top of the videoPlayerController
_chewieController = ChewieController(
videoPlayerController: widget.videoPlayerController,
aspectRatio: 16 / 9,
// Prepare the video to be played and display the first frame
autoInitialize: true,
looping: widget.looping,
// Errors can occur for example when trying to play a video
// from a non-existent URL
errorBuilder: (context, errorMessage) {
return Center(
child: Text(
errorMessage,
style: TextStyle(color: Colors.white),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Chewie(
controller: _chewieController,
),
);
}
#override
void dispose() {
super.dispose();
// IMPORTANT to dispose of all the used resources
widget.videoPlayerController.dispose();
_chewieController.dispose();
}
}
Here I am retrieving my video array from a firebase but I get this error please give me a solution of this code.