Usually when I work with listview on stateless widgets this is how I do it
I have a model class for data handling
class CategoriesModel {
String? categoryId;
String? categoryNameEn;
String? categoryNameAr;
String? categoryIcon;
String? createDate;
CategoriesModel(
{this.categoryId,
this.categoryNameEn,
this.categoryNameAr,
this.categoryIcon,
this.createDate});
CategoriesModel.fromJson(Map<String, dynamic> json) {
categoryId = json['category_id'];
categoryNameEn = json['category_name_en'];
categoryNameAr = json['category_name_ar'];
categoryIcon = json['category_icon'];
createDate = json['create_date'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['category_id'] = categoryId;
data['category_name_en'] = categoryNameEn;
data['category_name_ar'] = categoryNameAr;
data['category_icon'] = categoryIcon;
data['create_date'] = createDate;
return data;
}
}
Then I create the get data function using getx controller
@override
void getData() async {
statusRequest = StatusRequest.loading;
var response = await categoriesData.getData();
statusRequest = handlingData(response);
if(StatusRequest.success == statusRequest){
if(response['status'] == "success")
{
categories.addAll(response['exercise_category']);
}
else
{
statusRequest = StatusRequest.taskFailure;
}
}
update();
}
Then I use Getview to create my listview
class ExerciseType extends GetView<CategoriesControllerImp> {
const ExerciseType({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
shape: BoxShape.circle,
),
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
height: 70,
width: 100,
child: ListView.builder(
//separatorBuilder: (context, index) => const SizedBox(width: 10),
itemCount: controller.categories.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Categories(
i: index,
categoriesModel:
CategoriesModel.fromJson(controller.categories[index]),
);
},
),
);
}
}
class Categories extends GetView<CategoriesControllerImp> {
final CategoriesModel categoriesModel;
final int? i;
const Categories({Key? key, required this.categoriesModel, required this.i})
: super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GetBuilder<CategoriesControllerImp>(
builder:(controller) =>
Container(
decoration: controller.catid == i ? const BoxDecoration(
shape: BoxShape.circle,
color: orangeRed,
) : const BoxDecoration(
shape: BoxShape.circle,
color: blue2,
),
height: 50,
width: 100,
child: IconButton(
onPressed: () {
controller.changeType(i!);
},
icon: SvgPicture.network(
"${ApiLinks.categoriesImg}/${categoriesModel.categoryIcon}",
fit: BoxFit.scaleDown,
),
),
),
),
Text(
"${categoriesModel.categoryNameEn}",
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 15),
)
]);
}
}
And then I call the class inside a getbuilder but recently I tried learning something new which page view with an animation I saw on youtube but I couldn't implement it in a way that works with the Model class I created especially that I don't like using stateful widgets so what I want is the right implementation on how to handle the index and inside the pageview builder in the stateful widget
I tried multiple ways I tried injecting the getx controller but it still didnt work he is my implementation inspired by https://www.youtube.com/watch?v=D8bgD9ZkbTg
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/src/widgets/placeholder.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:get/get.dart';
import '../../../data/model/food_model.dart';
class FoodSliderBuilder extends StatefulWidget {
const FoodSliderBuilder({super.key});
@override
State<FoodSliderBuilder> createState() => _FoodSliderBuilderState();
}
class _FoodSliderBuilderState extends State<FoodSliderBuilder> {
late PageController _pageController;
late final FoodModel _foodlist;
double get _currentOffest {
bool inited = _pageController.hasClients &&
_pageController.position.hasContentDimensions;
return inited ? _pageController.page! : _pageController.initialPage * 1.0;
}
int get _currentIndex=>_currentOffest.round( ) %_foodlist.length;
@override
void initState() {
_pageController = PageController (initialPage: 0);
super.initState();
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _pageController,
builder: (context, child) {
return stackBuild();
}
);
}
Stack stackBuild() {
final FoodModel _currFood = _foodlist[_currentIndex];
return Stack(
alignment: Alignment.center,
children: [
Positioned(
top: -Get.width * 0.7,
child: BgImage(
currentIndex: _currentIndex,
food: _currFood,
pageOffset: _currentOffest,
),
),
SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
_currFood.foodNameEn!,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
],
),
),
Center(
child: SizedBox(
height: Get.width * (450 / 375.0),
child: PageView.builder(
controller: _pageController,
itemCount: 3,
itemBuilder: (context, index) {
double _value = 0.0;
double vp = 1;
double scale = max(vp, (_currentOffest - index).abs());
if (_pageController.position.haveDimensions) {
_value = index.toDouble()-(_pageController.page ?? 0);
_value = (_value * 0.7).clamp(-1, 1);
}
return Transform.rotate(
angle: pi * _value,
child: Padding(
padding: EdgeInsets.only(bottom: 200 - scale * 5),
child: Image.asset(_foodlist[index].foodImage!, fit: BoxFit.scaleDown,),
)
);
},
),
)
),
],
);
}
}
class BgImage extends StatefulWidget {
const BgImage({super.key, required this.currentIndex, required this.food, required this.pageOffset});
final int currentIndex;
final FoodModel food;
final double pageOffset;
@override
State<BgImage> createState() => _BgImageState();
}
class _BgImageState extends State<BgImage> {
@override
Widget build(BuildContext context) {
double _value = 0.0;
_value = (widget.pageOffset - widget.currentIndex + 1).abs();
return Opacity(
opacity: 0.6,
child: Transform.rotate (
angle: pi + _value + pi / 180,
child: SizedBox(
height: Get.height,
width: Get.width * (450 / 375.0),
child: Image.asset(
widget.food.foodImage!,
fit: BoxFit.fitWidth,
),
) // Image.asset
, // Container
),
); // Transform. rotate
}
}
and this is my implementation for the FoodModel class
class FoodModel {
String? foodId;
String? foodNameEn;
String? foodNameAr;
String? kcal;
String? fats;
String? carbs;
String? protein;
String? createTime;
String? foodTypeId;
String? foodImage;
FoodModel(
{this.foodId,
this.foodNameEn,
this.foodNameAr,
this.kcal,
this.fats,
this.carbs,
this.protein,
this.createTime,
this.foodTypeId,
this.foodImage});
FoodModel.fromJson(Map<String, dynamic> json) {
foodId = json['food_id'];
foodNameEn = json['food_name_en'];
foodNameAr = json['food_name_ar'];
kcal = json['kcal'];
fats = json['fats'];
carbs = json['carbs'];
protein = json['protein'];
createTime = json['create_time'];
foodTypeId = json['food_type_id'];
foodImage = json['food_image'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['food_id'] = foodId;
data['food_name_en'] = foodNameEn;
data['food_name_ar'] = foodNameAr;
data['kcal'] = kcal;
data['fats'] = fats;
data['carbs'] = carbs;
data['protein'] = protein;
data['create_time'] = createTime;
data['food_type_id'] = foodTypeId;
data['food_image'] = foodImage;
return data;
}
}
The data is being retrieved correctly becuase when I tried to implement the listview and gridview way on getview it worked perfectly fine and it showed the data from the database
UPDATE
I kind of figured half of it
baisically I injected
FoodControllerImp controller = Get.put(FoodControllerImp());
then initialized this
late final FoodModel foodlist = FoodModel.fromJson(controller.food[_currentIndex]);
then I used it like this
child: Image.network(
"${ApiLinks.foodImg}/${foodlist.foodImage}",
fit: BoxFit.scaleDown,
),
so it worked it got the data from the database but its stuck on the first initialized index while the background image is changing based on the index
and when I scroll the background image changes but the front image doesnt
BgImage(
currentIndex: _currentIndex,
food: FoodModel.fromJson(controller.food[_currentIndex]),
pageOffset: _currentOffest,
),
also this is how I'm getting the index
int get _currentIndex=>_currentOffest.round( ) % controller.food.length;
there is an error that occurs for some seconds
═══════ Exception caught by widgets library ═══════════════════════════════════ The following IntegerDivisionByZeroException was thrown building AnimatedBuilder(animation: PageController#be930(no clients), dirty, state: _AnimatedState#d8f6a): IntegerDivisionByZeroException
The relevant error-causing widget was AnimatedBuilder