How do I properly initialize a late variable - flutter

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',
),

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);
}
...

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

Flutter ThemeExtension: What to to if lerp is not support by property

I am Trying to work with flutter ThemeExtension and I don't have quit understood how lerp works.
Here a example ThemeExtension:
enum MyButtonVariant {
solid,
soft,
outlined,
text,
}
class MyButtonStyle extends ThemeExtension<MyButtonStyle> {
const MyButtonStyle({
this.variant,
this.margin,
});
final ButtonVariant? variant;
final EdgeInsets? margin;
#override
ThemeExtension<MyButtonStyle> copyWith({
MyButtonVariant? variant,
EdgeInsets? margin,
}) =>
MyButtonStyle(
variant: variant ?? this.variant,
margin: margin ?? this.margin,
);
#override
ThemeExtension<MyButtonStyle> lerp(
ThemeExtension<MyButtonStyle>? other, double t) {
if (other is! MyButtonStyle) {
return this;
}
return MyButtonStyle(
variant: WHAT SHOULD BE HERE? ,
margin: EdgeInsets.lerp(margin, other.margin, t),
);
}
}
What should be used in the lerp function for variant?
I was thinking that something like this could work:
variant: t < 0.5 ? variant : other.variant,
Is there a better solution?

What does " int? get priority => 1;" do?

I was reading a flutter code as below:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:test_get_app/user_controller.dart';
class AuthMiddleware extends GetMiddleware {
final authService = UserController.findOrInitialize; // Here is error, this line can't find UserController
#override
int? get priority => 1;
bool isAuthenticated = false;
#override
RouteSettings? redirect(String? route) {
isAuthenticated = true;
if (isAuthenticated == false) {
return const RouteSettings(name: '/login');
}
return null;
}
}
When I reached to the following line, I couldn't understand it's syntax and how does it work?
int? get priority => 1;
int? Means it is an int but the int can be null
=> 1 Means () {return 1;}
This is a so-called getter. Getters can be used to provide read access to class properties.
They can also return values directly, like in your case.
They are accessed like properties of the class they are declared in:
final middleWare = AuthMiddleware();
final priority = middleWare.priority;
In your case the getter probably must or can be implemented (see the #override annotation), since all implementations of a middleware must declare their priority, I guess. Since the declared type is int? it may also return null instead of an integer.
Getters can be declared using an expression. Like in your case. Using a block body does also work:
int? get priority {
return 1;
}

The method '>=' was called on null. Receiver: null

So, I figured out that the mistake producing the error is the double _bmi inside the calculateBMI() method. However, I would like to know why including double produces this error? What is the logical process?
import 'dart:math';
class CalculatorBrain {
CalculatorBrain({this.height, this.weight});
final int height;
final int weight;
double _bmi;
String calculateBMI() {
double _bmi = height / pow(height / 100, 2);
return _bmi.toStringAsFixed(1);
}
String getResult() {
if (_bmi >= 25.0) {
return 'Overweight';
} else if (_bmi > 18.5) {
return 'Normal';
} else {
return 'Underweight';
}
}
}
Fron the calculateBMI() function, you have redeclared the _bmi variable double _bmi = height / pow(height / 100, 2);.
It's supposed to be:
_bmi = height / pow(height / 100, 2);

Can I pass a setter as an argument to another widget?

I have a widget (slider) that changes a value.
Right now, I am passing a VoidCallback to each slider, so they can update the individual setter of the provider.
Is it possible to pass the setter in the constructor of the slider, so it can call the setter directly, thus simplifying the construction of the slider (no VoidCallbacks needed)?
You can by wrapping the setter in a function. So a callback is still needed but it's a lean wrapper around the setter. Not much boilerplate to handle here. I made an example on gist/dartpad:
https://dartpad.dev/?id=38f013e47f8c85057c44a0a326df33dd
void main() async {
final a = A(value: 2);
print('A=${a.value}');
final setter = (int i) => a.data = i;
final b = B(callback: setter);
b.exec(10);
print('A=${a.value}');
}
class A {
int value;
A({required this.value});
set data(int newValue) => value = newValue;
}
class B {
final void Function(int) callback;
B({required this.callback});
void exec(int i){
callback(i);
}
}

The following NoSuchMethodError was thrown building Builder(dirty): The method '>=' was called on null. Receiver: null The relevant error-causing was:

So, I want to pass data to a new screen, by calling methods and passing it into a String. The first method calc.calculateBMI() was passed in successfully into bmiResult.. But I got the error below for calc.getInterpretation
First Screen's Code.
ButtomButton(
buttonTitle: 'CALCULATE',
onTap: (){
CalculatorBrain calc = CalculatorBrain(height: height, weight: weight);
Navigator.push(context, MaterialPageRoute(builder: (context){
return ResultsPage(
bmiResult: calc.calculateBMI(),
interpretation: calc.getInterpretation(),
);
}));
},
),
import 'dart:math';
class CalculatorBrain {
CalculatorBrain({this.height, this.weight});
final int height;
final int weight;
double _bmi;
String calculateBMI() {
double _bmi = weight / pow(height/100, 2);
return _bmi.toStringAsFixed(1);
}
String getInterpretation() {
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';
}
}
}
The Error I got
======== Exception caught by widgets library =======================================================
The following NoSuchMethodError was thrown building Builder(dirty):
The method '>=' was called on null.
Receiver: null
Tried calling: >=(27)
The relevant error-causing widget was:
MaterialApp file:///C:/Users/MICHEAL/AndroidStudioProjects/bmi_calculator/lib/main.dart:9:12
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 CalculatorBrain.getInterpretation (package:bmi_calculator/calculator_brain.dart:27:14)
#2 _InputPageState.build.<anonymous closure>.<anonymous closure> (package:bmi_calculator/screens/input_page.dart:214:40)
#3 MaterialPageRoute.buildContent (package:flutter/src/material/page.dart:55:55)
#4 MaterialRouteTransitionMixin.buildPage (package:flutter/src/material/page.dart:108:27)
...
====================================================================================================
The error in the code above is caused by the fact that we're not initializing the _bmi variable inside the CalculatorBrain class.
To do so we can proceed by using the following code:
import 'dart:math';
class CalculatorBrain {
CalculatorBrain({this.height, this.weight}) {
_bmi = weight / pow(height/100, 2);
}
final int height;
final int weight;
double _bmi;
String calculateBMI() =>
_bmi.toStringAsFixed(1);
String getInterpretation() {
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';
}
}
}
The same snippet with null-safety would be:
import 'dart:math';
class CalculatorBrain {
CalculatorBrain({required this.height, required this.weight}) {
_bmi = weight / pow(height / 100, 2);
}
final int height;
final int weight;
late double _bmi;
String calculateBMI() => _bmi.toStringAsFixed(1);
String getInterpretation() {
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';
}
}
}