1

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

This is the current output

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

Toxicless
  • 23
  • 4
  • Cant you just add the foodlist from database in the initState? And How are you getting the data from the database? Do you use stream and firestore? Do you get them with api? Obviously you have problems with the foodlist from your database. So you should provide more information about it, like from where, how you provide/deliver it. If you show me this, I will give you the right answer. – MrOrhan May 05 '23 at 05:31
  • So what is the error message when you run it with the not static List? Can you update your question with the error message? :) – MrOrhan May 05 '23 at 06:11
  • Undefined name 'foodList'. Try correcting the name to one that is defined, or defining the name. so I just dont know how to update the code to make it work from the FoodModel – Toxicless May 05 '23 at 06:31
  • final List _foodlist = foodList; final FoodModel _currFood = _foodlist[_currentIndex]; this is how I call the data I want but the main problem is the foodlist instance _currFood.foodNameEn! – Toxicless May 05 '23 at 06:31
  • and when I try to make it late this happens LateInitializationError: Field '_foodlist@2091089814' has not been initialized. – Toxicless May 05 '23 at 06:37
  • How are you getting the footlist? I have to see it, otherwise I cant help you. Are you getting the foodlist from a provider during the runtime? are you getting it from your widget? There are easy solutions to this but I can't help If I dont know how you getting the data. For example if you are getting it from the widget, you can add it in the widget state. If you providing it with provider, you just have to add a nullchecker/haschild checker and so on. where are you getting the data from? – MrOrhan May 05 '23 at 07:04
  • class FoodSliderBuilder extends StatefulWidget { const FoodSliderBuilder({required foodlist, super.key}); final List foodlist // <--------here? @override State createState() => _FoodSliderBuilderState(); } ... Or as a provider? – MrOrhan May 05 '23 at 07:04
  • I would like to try on here at first. But can you try to update your question with the full script code of FoodSliderBuilder and the version where you try it with late? Or push it on git? – MrOrhan May 05 '23 at 07:38
  • First of all, like you can see, U wasn't providing yourself the data.Then your new problem is solvable. Here watch this answer of mine. https://stackoverflow.com/a/76062477/7242912 – MrOrhan May 07 '23 at 15:06

0 Answers0