Related items in datails page with getx - flutter

I have a details page and it has a related items section
I was using get.put, but the page did not change by clicking on one of these elements and using Get.toNamed. I searched and found that Get.put should be replaced by Get.create, but the elements still did not change. Why??
Controller:
class EpisodeController extends GetxController
with StateMixin<List<TvEpisode>> {
final TvApiProvider tvApiProvider;
EpisodeController({required this.tvApiProvider});
final String _sequence = Get.parameters['sequence']!;
#override
void onInit() {
_getNextEpisode();
super.onInit();
}
void _getNextEpisode() async {
await tvApiProvider
.getNextEpisode(
sequence: _sequence)
.then((value) {
change(value, status: RxStatus.success());
}, onError: (err) {
change(null, status: RxStatus.error(err.toString()));
});
}
}
View:
class NextEpisode extends GetWidget<EpisodeController> {
const NextEpisode({super.key});
#override
Widget build(BuildContext context) {
return controller.obx(
(data) => SizedBox(
height: 375,
child: ListView.builder(
reverse: true,
scrollDirection: Axis.horizontal,
itemCount: data!.length,
itemBuilder: (context, index) {
return movieCard(
data[index],
data[index].id,
data[index].title,
data[index].seriesId!,
data[index].seriesTitle!);
},
),
),
}

Related

Flutter: I wants to show message to the user only if the user reaches end of the ListView

Below is my code but it is showing the SnackBar frequently when I reach the bottom of ListView. It also shows the SnackBar on the pages also but I wants to show it only one time how to do that.
final snackBar = SnackBar(content: const Text('Yay! A SnackBar!'));
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: docs.length,
itemBuilder: (context, index) {
final doc = docs[index];
print(doc);
//_checkController();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
return builddoc(doc);
},
Because you are assigning new listeners every time item builder calls.
put this code in ititState so it just called once.
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
Remove the listener from the itembuilder, instead use it in initState(),as everytime item is builded on listview, it will call this listener, so it is going on everytime item get builded.
You can use Listener on ScrollController, your issue is that you assign Listener to controller in build method which is wrong, you should do it once in initState. This is a full example of what you want:
class ScrollPageTest extends StatefulWidget {
const ScrollPageTest({Key? key}) : super(key: key);
#override
State<ScrollPageTest> createState() => _ScrollPageTest();
}
class _ScrollPageTest extends State<ScrollPageTest> {
ScrollController controller = ScrollController();
#override
void initState() {
// TODO: implement initState
super.initState();
controller.addListener(() {
if (controller.position.atEdge) {
if (controller.position.pixels != 0) {
final snackBar = SnackBar(content: const Text('Yay! A SnackBar!'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text('data = $index'),
);
},
itemCount: 100,
),
);
}
}
Try this:
bool isSnackBarShown = false;
...
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: docs.length,
itemBuilder: (context, index) {
final doc = docs[index];
print(doc);
//_checkController();
_scrollController.addListener(() {
if ((_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent)
&& !isSnackBarShown) {
isSnackBarShown = true;
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
if (_scrollController.position.pixels !=
_scrollController.position.maxScrollExtent) {
return null;
}
}
});
return builddoc(doc);
},

Flutter: live stream with getx has a problem, it shows data without updating

I use Getx package in my apps, it works beautiful, but when I tried to get the data from firestore in stream it gets the data but without stream, so my UI hasn't updated until I go to another page and reenter the page which has the data!!!
Actually it's a big problem to bring this data without streaming, each time I have to exit and reopen to update!!!
Here my controller class:
class AddSubscriberController extends GetxController {
final CollectionReference _subscribersCollectionRef =
FirebaseFirestore.instance.collection('Subscribers');
List<SubscriberModel> get subscriberModel => _subscriberModel;
final List<SubscriberModel> _subscriberModel = [];
RxList<SubscriberModel> subscribers = RxList([]);
#override
void onInit() {
super.onInit();
subscribers.bindStream(getSubscribers());
}
Stream<List<SubscriberModel>> getSubscribers() {
return _subscribersCollectionRef
.orderBy('Register date', descending: false)
.snapshots()
.map((QuerySnapshot query) {
for (var element in query.docs) {
subscriberModel.add(SubscriberModel.fromMap(element));
}
return subscriberModel;
});
}
}
And here is my UI class:
Obx(() {
AddSubscriberController controller =
Get.put(AddSubscriberController());
return ListView.builder(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: controller.subscribers.length,
itemBuilder: (context, index) {
return buildSubInfo(
name: controller.subscribers[index].firstName,
lastName: controller.subscribers[index].lastName,
father: controller.subscribers[index].father,
area: controller.subscribers[index].area,
counterNo: controller.subscribers[index].counterNumber,
date: controller.subscribers[index].date,
);
},
);
}), // Obx
How can I solve this??
Any help will be appreciated.
Update:
Somebody give me a solution here in the answers, and it worked good, but I made some edits on it, like this:
getSubscriber() {
return _subscribersCollectionRef
.orderBy('Register date', descending: true)
.snapshots()
.listen((event) {
subscriberModel.clear();
for (var element in event.docs) {
subscriberModel.add(SubscriberModel.fromMap(element));
}
});
}
class AddSubscriberController extends GetxController {
final CollectionReference _subscribersCollectionRef =
FirebaseFirestore.instance.collection('Subscribers');
final List<SubscriberModel> subscriberModel = <SubscriberModel>[].obs;
#override
void onInit() {
getSubscribers();
super.onInit();
}
getSubscribers() {
return _subscribersCollectionRef
.orderBy('Register date', descending: false)
.snapshots()
.listen((event) {
//// try to simple approach
for (var x in event.docs) {
final Map<String, dynamic> y = x.data();
final tojson = SubscriberModel.fromJson(y);
subscriberModel.add(tojson);
}
});
}
}
for the view
class View extends StatelessWidget {
const View ({super.key});
#override
Widget build(BuildContext context) {
final controller = Get.put(AddSubscriberController());
return Obx(
()=> Scaffold(
body : ListView.builder(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: controller.subscriberModel.length,
itemBuilder: (context, index) {
final item = controller.subscriberModel[index];
return buildSubInfo(
name: item.firstName,
lastName: item.lastName,
father:item.father,
area: item.area,
counterNo: item.counterNumber,
date: item.date,
);
},
),
));
}
try this if this work
Can you try this:
GetX<AddSubscriberController>(builder : (controller) {
return ListView.builder(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: controller.subscribers.length,
itemBuilder: (context, index) {
return buildSubInfo(
name: controller.subscribers[index].firstName,
lastName: controller.subscribers[index].lastName,
father: controller.subscribers[index].father,
area: controller.subscribers[index].area,
counterNo: controller.subscribers[index].counterNumber,
date: controller.subscribers[index].date,
);
},
);
}),

Pagination with Future Builder and List of snapshots, Flutter

I want to implement pagination when calling APIs(without any packages) with my FutureBuilder, that calls 2 APIs at the same time since one depends on the other and I'm not sure how to do that. Here is the code below:
The ListView from FutureBuilder:
final ScrollController _scrollController = ScrollController();
#override
void initState() {
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {}
});
super.initState();
}
FutureBuilder(
future: Future.wait(
[
RepositoryFromAPItoDB().getAllMovies(),
RepositoryFromAPItoDB().getAllGenres()
],
),
builder:
(BuildContext context, AsyncSnapshot<List<dynamic>?> snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
controller: _scrollController,
itemCount: snapshot.data?[0].length,
itemBuilder: (BuildContext context, int index) {
return MoviesListTile();
},
);
}
},
),
API calls for both Lists:
Future<List<Movies?>> getAllMovies() async {
Response response = await Dio().get(Constants().moviesURL);
return (response.data['results'] as List).map((movies) {
return Movies.fromJson(movies);
}).toList();
}
Future<List<Genres?>> getAllGenres() async {
Response response = await Dio().get(Constants().genresURL));
return (response.data['genres'] as List).map((genres) {
return Genres.fromJson(genres);
}).toList();
}
Now that I have call the APIs, my list is populated and everything is working fine, except I can't implement any type of pagination.. And how do I display some sort of CircularProgressIndicator() or anything like that while I scroll the bottom of the list and when it loads again? Thanks in advance for your help!
import 'package:flutter/material.dart';
import 'package:pageview_demo/main.dart';
import 'package:pageview_demo/usersModel.dart';
import 'DataNum.dart';
class School extends StatefulWidget {
const School({Key? key}) : super(key: key);
#override
_SchoolState createState() => _SchoolState();
}
class _SchoolState extends State<School> with AutomaticKeepAliveClientMixin {
ScrollController _scrollController = ScrollController(keepScrollOffset: true);
int pageNo = 1;
bool isLoading = false;
List<UsersModel> _usersModel = [];
List<Datum> data = [];
#override
void initState() {
_scrollController.addListener(() {
final pos = _scrollController.position;
final triggerFetchMoreSize = 0.9 * pos.maxScrollExtent;
if (pos.pixels > triggerFetchMoreSize) {
Future.delayed(const Duration(seconds: 5), () {
_callApi();
});
}
});
_callApi();
super.initState();
}
_callApi() async {
var response = await getHttp();
isLoading = true;
_usersModel.clear();
setState(() {
if (data.isEmpty) {
_usersModel.add(response);
data.addAll(_usersModel[0].data);
isLoading = false;
} else {
_usersModel.add(response);
data.insertAll(data.length, _usersModel[0].data);
isLoading = false;
}
});
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
pageNo = 1;
isLoading = true;
_usersModel.clear();
data.clear();
_callApi();
},
child: ListView.builder(
padding: EdgeInsets.all(10),
shrinkWrap: true,
scrollDirection: Axis.vertical,
controller: _scrollController,
physics: const BouncingScrollPhysics(),
itemCount: isLoading ? 0 : data.length,
itemBuilder: (context, indx) => indx == data.length - 1
? Center(child: LinearProgressIndicator())
: ListTile(
onTap: (){},
leading: Text("${data[indx].id}"),
title: Text("${data[indx].name}"),
subtitle: Text("${data[indx].email}"),
),
),
);
}
#override
bool get wantKeepAlive => true;
}

The method 'when' isn't defined for the type 'Object'

I created this StateNotifier with Riverpod in Flutter which Returns the Object DocumentsList
class TripStateNotifier extends StateNotifier<List<DocumentList>> {
TripStateNotifier() : super([]);
void getDocuments() async {
final res = await db.listDocuments(collectionId: '6286c0f1e7b7a5760baa');
state = res as List<DocumentList>;
}
}
final TripState = StateNotifierProvider((ref) => TripStateNotifier());
And this ConsumerWidget whicht gets the data
ref.watch(TripState)!.when(
data: (list) {
//Handeling if no data is found
if(list == null || (list.documents?.isEmpty ?? true)) return Center(child: Text("Yet you didn´t add any destinations to your trip", style: TextStyle(color: Colors.black.withOpacity(0.5)))); return ListView.builder(
itemCount: list.total,
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (context, index) {
return mytrip_card(
location: list.documents[index].data['location'], date: list.documents[index].data['date']
);
},
);
},
error: (e, s) => Text(e.toString()),
loading: () => const CircularProgressIndicator(),
),
My Problem is that my IDE Outputs following Error in regards to the when in ref.watch(TripState)!.when
The method 'when' isn't defined for the type 'Object'.
Intrestingly enought my Old Soulution with Future Provider worked why does this not?
Old solution:
final TripProvider = FutureProvider((ref)
async {
debugPrint('test');
final res = await db.listDocuments(collectionId: '6286c0f1e7b7a5760baa');
return res;
});
The method when is only available for the object type AsyncValue which can be provided by a FutureProvider or a StreamProvider.
Now that you are using a StateNotifierProvider, you won't be able to use when anymore, I would recommend you to create a "state" class to use with your TripStateNotifier.
Code sample:
final tripStateProvider = StateNotifierProvider<TripStateNotifier, TripState>(
(ref) => TripStateNotifier());
class TripStateNotifier extends StateNotifier<TripState> {
TripStateNotifier() : super(const TripState());
void getDocuments() async {
// Trigger the loading state
state = state.copyWith(isLoading: true);
try {
final res = await db.listDocuments(collectionId: '6286c0f1e7b7a5760baa');
// Update the state with your newly fetched results
state = state.copyWith(
isLoading: false,
documents: res as List<DocumentList>,
);
} catch (e) {
// Manage received error
state = state.copyWith(
isLoading: false,
hasError: true,
);
}
}
}
#immutable
class TripState {
final bool isLoading;
final bool hasError;
final List<DocumentList> documents;
const TripState({
this.documents = const [],
this.isLoading = false,
this.hasError = false,
});
int get total => documents.length;
TripState copyWith({
List<DocumentList>? documents,
bool? isLoading,
bool? hasError,
}) {
return TripState(
documents: documents ?? this.documents,
isLoading: isLoading ?? this.isLoading,
hasError: hasError ?? this.hasError,
);
}
}
class MyWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final currentState = ref.watch(tripStateProvider);
final controller = ref.read(tripStateProvider.notifier);
if (currentState.isLoading) return const CircularProgressIndicator();
if (currentState.documents.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Yet you didn´t add any destinations to your trip",
style: TextStyle(
color: Colors.black.withOpacity(0.5),
),
),
TextButton(
onPressed: controller.getDocuments,
child: const Text('Refresh'),
),
],
),
);
}
return ListView.builder(
itemCount: currentState.total,
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (context, index) {
return MyTripCard(
location: currentState.documents[index].data['location'],
date: currentState.documents[index].data['date'],
);
},
);
}
}
Try the full example on DartPad
Define StateNotifier like this
final tripState = StateNotifierProvider<TripStateNotifier, List<DocumentList>>((ref) {
return TripStateNotifier();
});
Access to documents like this
List<DocumentList> trips = ref.watch(tripState);
return ListView.builder(
itemCount: trips.length,
itemBuilder: (context, index) {
...
}
);
See StateNotifierProvider for details.

flutter widget not being updated when called from a list

i previously asked a question about widgets not being updated here:
flutter slider not updating widget variables
i got a great answer which explained to me more about how states work and i experimented a little further and now have an issue where my widget inside a list is not being updated even though i update the state in a setstate.
The Widget in question not being updated is the TestBoxNumber widget in the testBoxList list after it has been added to the list. I realize that if i change the builder to return the widget itself rather than from the list it works, and i'm not sure why this is the case!
Once again any help would be greatly appreciated and i hope this helps someone facing the same issue as well :)
Main Page Code
class TestPage extends StatefulWidget {
static const id = "test_page";
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List testBoxList = [];
List testSlideList = [];
List testParamList = [];
void updateFunc(ind, newVal) {
setState(() {
testParamList[ind] = newVal;
});
}
void addSlider() {
setState(() {
double slideValue = 0;
testParamList.add(slideValue);
int boxIndex = testParamList.length - 1;
testBoxList.add(TestBoxNumber(
numberDisplay: testParamList,
boxIndex: boxIndex,
));
testSlideList.add(TestSlider(
testValue: testParamList,
updateFunc: updateFunc,
boxIndex: boxIndex,
));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
addSlider();
},
),
body: Padding(
padding: const EdgeInsets.all(30.0),
child: ListView(
children: [
Text("Test Page"),
// Builder for viewers
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testBoxList.length,
itemBuilder: (BuildContext ctx, int index) {
return testBoxList[index];
// return Text(testParamList[index].toString());
// return TestBoxNumber(
// numberDisplay: testParamList, boxIndex: index);
},
),
// Builder for sliders
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testSlideList.length,
itemBuilder: (BuildContext ctx, int index) {
return testSlideList[index];
},
),
],
),
),
);
}
}
TestBoxNumber Widget
class TestBoxNumber extends StatelessWidget {
final List numberDisplay;
final int boxIndex;
TestBoxNumber({required this.numberDisplay, required this.boxIndex});
Widget build(BuildContext context) {
return Text(this.numberDisplay[this.boxIndex].toString());
}
}
Slider Widget
class TestSlider extends StatefulWidget {
List testValue;
dynamic updateFunc;
int boxIndex;
TestSlider({
required this.testValue,
required this.updateFunc,
required this.boxIndex,
});
#override
_TestSliderState createState() => _TestSliderState();
}
class _TestSliderState extends State<TestSlider> {
// double curValue = widget.testValue;
#override
Widget build(BuildContext context) {
double curValue = widget.testValue[widget.boxIndex];
return Slider(
activeColor: themeData.primaryColorLight,
value: curValue,
min: 0,
max: 100,
divisions: 50,
label: curValue.round().toString(),
onChanged: (double value) {
setState(() {
curValue = value;
});
widget.updateFunc(widget.boxIndex, value);
},
);
}
}
Me again )
Ok, so what is wrong right now is that you are using widgets, stored in the list instead of creating ones again:
You should not do this:
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testBoxList.length,
itemBuilder: (BuildContext ctx, int index) {
return testBoxList[index];
// return Text(testParamList[index].toString());
// return TestBoxNumber(
// numberDisplay: testParamList, boxIndex: index);
},
)
but return new TestBoxNumber widgets (you actually has it commented, not sure why you did that):
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testBoxList.length,
itemBuilder: (BuildContext ctx, int index) {
return TestBoxNumber(numberDisplay: testParamList, boxIndex: index);
},
)
so you will render widgets from scratch instead of pulling it from memory (list) and causing some weird things. Flutter is pretty optimized for such re-rendering.
So summarizing all of above: just pass data into widgets in build method. Do not store widgets in memory to reuse later.
UPD: also you can just pass double (let's call it yourDoubleValue) into TestBoxNumber instead of list and index. And then use Text('$yourDoubleValue');