Home > Mobile >  How do I properly initialize a late variable
How do I properly initialize a late variable

Time:04-13

I have a late property(_bmi) set to null by default and then I initialize it in a method calcBMI(), after its initialization, I use its value in two more methods, result and interpretation to fulfill some bool conditions, but I run into a lateinitialisationerror, because the first method (result()) doesn't recognize the value I initialized in calcBMI(), it rather defaults to the initial null value that the _bmi property had, however the interpretation() method recognizes the initialized value. How I know it's only the first method that doesn't recognize it is coz when I pass a value to the _bmi property when I'm setting it, the app doesn't throw the error, it works, but then it uses the set value for the first method, but the initialised value from calcBMI() for the second method, what am I doing wrong, here is the code.

class CalculatorBrain {
  CalculatorBrain({required this.weight, required this.height});

  int weight;
  int height;
  late double _bmi = 30; // when I set this value, result() works with it, but interpretation() works with the value passed in calcBMI();

  String calcBMI() {
    _bmi = weight / pow(height / 100, 2);
    return _bmi.toStringAsFixed(1);
  }

  String result() {
    if(_bmi >= 25) {
      return 'overweight';
    } else if(_bmi > 18.5) {
      return 'normal';
    } else {
      return 'underweight';
    }
  }

  String interpretation() {
    if(_bmi >= 25) {
      return 'You have a higher than normal body weight. Try to exercise more.';
    } else if(_bmi > 18.5) {
      return 'You have a normal body weight. Good job.';
    } else {
      return 'You have a lower than normal body weight. You can eat a bit more.';
    }
  }

}

this is where I use my class

BottomButton(
              onTap: () {
                CalculatorBrain calc = CalculatorBrain(
                  weight: weight,
                  height: height,
                );
                setState(() {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (context) {
                          return ResultsPage(result: calc.result(), bMI: calc.calcBMI(), interpretation: calc.interpretation());
                        }
                    ),
                  );
                });
              },
              title: 'CALCULATE',
            ),

CodePudding user response:

If you got a LateInitializationError before, it's not that "[result()] doesn't recognize the value I initialized in calcBMI()", it's that you called result() before you called calcBMI().

Giving _bmi an initial value avoids the LateInitializationError, but you still have the same fundamental problem: you're reading _bmi before you call calcBMI() to assign it the value you actually want.

In particular, you have:

return ResultsPage(result: calc.result(), bMI: calc.calcBMI(), interpretation: calc.interpretation());

Dart evaluates function arguments in left-to-right order, so you'll call calc.result() first, then calc.calcBMI(), and then calc.interpretation(). Changing the order should fix your problem:

return ResultsPage(
  bMI: calc.calcBMI(),
  result: calc.result(),
  interpretation: calc.interpretation(),
);

However, relying on argument evaluation order would be poor style. It would not be obvious to readers (including your future self) that argument order matters. It would be much better to explicitly order operations that are order-dependent:

var bmi = calc.calcBMI();
return ResultsPage(
  result: calc.result(),
  bMI: bmi,
  interpretation: calc.interpretation(),
);

Note that this has nothing to do with _bmi being late. Declaring _bmi as late provides no purpose in your code as currently written, and you could just remove it. But you also should consider rewriting your code to make CalculatorBrain less dependent on consumers calling its methods in a particular order. Some possibilities:

Compute _bmi dynamically

You could make _bmi a getter that computes the correct value for weight/height on every access:

class CalculatorBrain {
  CalculatorBrain({required this.weight, required this.height});

  int weight;
  int height;

  double get _bmi => weight / pow(height / 100, 2);

  String calcBMI() => _bmi.toStringAsFixed(1);

  String result() {
    final _bmi = this._bmi;
    if(_bmi >= 25) {
      return 'overweight';
    } else if(_bmi > 18.5) {
      return 'normal';
    } else {
      return 'underweight';
    }
  }

  ...

Compute _bmi exactly once

If you make weight/height final, then you can compute _bmi once and be done:

class CalculatorBrain {
  CalculatorBrain({required this.weight, required this.height});

  final int weight;
  final int height;

  // `late` computes `_bmi` lazily, which is necessary because it depends on
  // `weight` and `height`.
  late final double _bmi = weight / pow(height / 100, 2);

  ...

Update _bmi automatically if weight or height changes

If you weight/height must be mutable, then you could create setters so that _bmi is always updated automatically:

class CalculatorBrain {
  CalculatorBrain({required int weight, required int height})
    : _weight = weight,
      _height = height {
    _updateBmi();
  }

  late double _bmi;

  int _weight;
  int get weight => _weight;
  set weight(int value) {
    _weight = value;
    _updateBmi();
  }

  int _height;
  int get height => _height;
  set height(int value) {
    _height = value;
    _updateBmi();
  }

  void _updateBmi() {
    _bmi => weight / pow(height / 100, 2);
  }

  ...

CodePudding user response:

late modifier means that you will at some point define it, which you are doing in calcBMI(), after which you call interpretation() where _bmi already has a value. Your code would crash if you just call result() since _bmi still isn't defined.

  • Related