Search code examples
fluttervisual-studio-codeflutter-web

Flutter Customer Painter inside a Stepper Causing Infinite Loop


I am having an issue with my CustomePainter in a Stepper. It is constantly reloading after the page is loaded and causing an error I am unsure of how to fix.

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  int currentStep = 0;
  List<LayoutBuilderWidget> listDynamic = [];


  addDynamic() {
    numberofwalls ++;
    listDynamic.add(LayoutBuilderWidget(numberofwalls));
    LengthFT = [];
    LengthIN = [];
    Angle = [];
    setState(() {
      
    });
  }

  submitdata(){
    double MathLengthFT;
    double finalLength;
    LengthFT = [];
    LengthIN = [];
    Angle = [];
    listDynamic.forEach((widget) => LengthFT.add(widget.lengthFT.text));
    listDynamic.forEach((widget) => LengthIN.add(widget.lengthIN.text));
    listDynamic.forEach((widget) => Angle.add(widget.dropdownValue));


    for(int i=0; i < numberofwalls; i++)
    {
      if(LengthIN[i] == null)
        {
          double MathLengthFT = int.parse(LengthFT[i]) * 12;
          FinalLength.add(MathLengthFT.toString());
        }
        if(LengthFT[i] == null)
        {
          finalLength = double.parse(LengthIN[i]);
          FinalLength.add(finalLength.toString());
        } 
        MathLengthFT = int.parse(LengthFT[i]) * 12;
        finalLength = int.parse(LengthIN[i]) + MathLengthFT;
        FinalLength.add(finalLength.toString());
    }

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body:Stepper(
        type: StepperType.horizontal,
      steps: getSteps(),
      currentStep:  currentStep,
      onStepContinue: () {
          final isLastStep = currentStep == getSteps().length - 1;

          if(isLastStep){
            print('Complete');
            //Send Data to Database
          }
          else{
            setState(() {
              currentStep += 1;
            });
          }
      },
      onStepCancel:
        currentStep == 0 ? null : () => setState(() => currentStep -= 1),
    ), 
    );
  }
  List<Step> getSteps() => [
    Step(
      isActive: currentStep >= 0,
      title: const Text('Layout'), 
      content: Container(
          child: Row(
            children: [
              Container(
                child: Column(
                   mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                     Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Container(
                      alignment: Alignment.center,
                      margin: const EdgeInsets.all(20),
                      width: 200,
                      child: Text(
                        'Wall',
                        style: Theme.of(context).textTheme.headline4,
                      ),
                    ),
                      Container(
                        alignment: Alignment.center,
                        margin: const EdgeInsets.all(20),
                        width: 200,
                        child: Text(
                          'Length', 
                          style: Theme.of(context).textTheme.headline4,
                        ),
                      ),
                      Container(
                      alignment: Alignment.center,
                      margin: const EdgeInsets.all(20),
                      width: 200,
                      child: Text(
                        'Angle',
                        style: Theme.of(context).textTheme.headline4,
                      ),
                    ),
                    ],
                  ),
                    Container(
                      height: 600,
                      width: 1000,
                      child: ListView.builder(
                        itemCount: listDynamic.length,
                        itemBuilder: ((_, index) => listDynamic[index])
                      )
                    ),

                   FloatingActionButton.extended(onPressed: () => addDynamic(), label: const Text('Add Wall'), icon:                        const Icon(Icons.add),),
                   FloatingActionButton.extended(onPressed: () {submitdata();}, icon: const Icon(Icons.visibility),                           label: const Text(''),),
                  ],
                ),
              ),
              Column(
                children: [
                  Container(
                    color: Color.fromARGB(255, 241, 241, 241),
                    width: 450,
                    height: 450,
                    child: CustomPaint(
                      foregroundPainter: OpenPainter(),
                    ),
                  ),
                  Container(
                    margin: EdgeInsets.only(top: 20),
                    width: 450,
                    height: 450,
                    child: CustomPaint(
                      painter: CanvasLegend(),
                    ),
                  ),
                ],
              )
            ],
          ),
        ),
      ),
    Step(
      isActive: currentStep >= 1,
      title: const Text('Doors & Options'), 
      content: Container(
    ),
    Step(
      isActive: currentStep >= 2,
      title: const Text('Panel Config'), 
      content: Container(),
    ),
    Step(
      isActive: currentStep >= 3,
      title: const Text('Accessories'), 
      content: Container(),
    ),
    Step(
      isActive: currentStep >= 4,
      title: const Text('Summary & Prices'), 
      content: Container(),
    ),
  ];
}

This snipit consists of the entire stepper method with a layout builder allowing users to add walls to the drawing they display in the painter.

class OpenPainter extends CustomPainter {

  OpenPainter();
  

  @override
  void paint(Canvas canvas, Size size) {
    var line1 = Paint()..strokeWidth = 5;
    var line2 = Paint()..strokeWidth = 5;
    double lastXPosition = 225;
    double lastYPosition = 225;
    double NextXPosition = 225;
    double NextYPosition = 225;
    int i = 0;
    
    if(numberofwalls != 0){
      for(i = 0; i < numberofwalls; i++)
    {
      NextYPosition = lastYPosition + double.parse(FinalLength[i]) * cos(AngleToRadians(double.parse(Angle[i])));
      NextXPosition = lastXPosition + double.parse(FinalLength[i]) * sin(AngleToRadians(double.parse(Angle[i])));

      canvas.drawLine(
      Offset(lastXPosition,lastYPosition),
      Offset(NextXPosition, NextYPosition),

      line1,
    );

      lastYPosition = NextYPosition;
      lastXPosition = NextXPosition;
    }

    i = 0;
    }
  }
 
  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}
 double AngleToRadians(double angle){
      return (pi / 180) * angle;
    }

This snipit consists of the entire CustomerPainter with the loop to run through every wall that is added to the forum and display a line representation for the user.

Application view

The attached picture shows the users view when loading into the website.

The error that is showing up is: Another exception was thrown: RangeError (index): Index out of range: no indices are valid: 0.

This error seems to be infinitely repeating once a button is pressed but no loops seem to have a chance to be infinite


Solution

  • Start lists with 0 in Lists and clear out after first wall has been drawn and move first wall back to [0]. This will allow all functions to run correctly.