You have completed the layout of the app, but right now the app is missing the part that converts the values that are based on the user input.
Generally speaking, it's always a good idea to separate the logic of your apps from the UI, and there are great patterns in Flutter that help you achieve this result. You'll use some of those, such as ScopedModel and Business Logic Components (BLoCs), in the following projects, but for now, we can just add the conversion functions into our class.
There are certainly several ways to write the code to perform the conversion between measures for this app. The approach that I find easiest is seeing the formulas that we need to apply as a two-dimensional array of values, also called a matrix. This matrix contains all the possible combinations of choices that the user can perform.
A diagram of this approach is shown here:
So, for example, when you want to convert 100 kilometers into miles, you multiply 100 by the number that you find in the array (in this case, 0.621371). It's a bit like playing Battleships. When the conversion is not possible, the multiplier is 0, so any impossible conversion returns 0.
As you might recall from Chapter 1, Hello Flutter!, in Dart we use List in order to create arrays. In this case, it's a two-dimensional array or matrix, and therefore we'll create an object that contains List's. Let's look at the steps:
- We'll need to convert the Strings of the measure units into numbers. At the top of the MyAppState class, add the following code, using Map:
final Map<String, int> _measuresMap = {
'meters' : 0,
'kilometers' : 1,
'grams' : 2,
'kilograms' : 3,
'feet' : 4,
'miles' : 5,
'pounds (lbs)' : 6,
'ounces' : 7,
};
- Maps allow you to insert key–value pairs, where the first element is the key, and the second is the value. When you need to retrieve a value from Map, you can use the following syntax:
myValue = measures['miles'];
The myValue variable will have a value of 5.
- Next, we'll create a list that contains all of the multipliers that were shown in the previous diagram:
final dynamic _formulas = {
'0':[1,0.001,0,0,3.28084,0.000621371,0,0],
'1':[1000,1,0,0,3280.84,0.621371,0,0],
'2':[0,0,1,0.0001,0,0,0.00220462,0.035274],
'3':[0,0,1000,1,0,0,2.20462,35.274],
'4':[0.3048,0.0003048,0,0,1,0.000189394,0,0],
'5':[1609.34, 1.60934,0,0,5280,1,0,0],
'6':[0,0,453.592,0.453592,0,0,1,16],
'7':[0,0,28.3495,0.0283495,3.28084,0,0.0625, 1],
};
If you don't want to type this code, I've created a Gist file that contains the Conversion class. You'll find the full file at https://gist.github.com/simoales/66af9a23235abcb537621e5bf9540bc6.
- Now that we have created a matrix that contains all of the possible combinations of conversion formulas, we only need to write the method that will convert the values using the formulas and the measures Map. Add the following code at the bottom of the MyAppState class:
void convert(double value, String from, String to) {
int nFrom = _measuresMap[from];
int nTo = _measuresMap[to];
var multiplier = _formulas[nFrom.toString()][nTo];
var result = value * multiplier;
}
The convert() method takes three parameters:
- The number that will be converted (double value)
- The unit of measure in which this value is currently expressed, as a String (String from)
- The unit of measure unit in which the value will be converted, also a String (String to)
For example, if you want to convert 10 meters into feet, 10 is the number, meters is the unit in which the value is currently expressed, and feet is the unit to which the number will be converted.
Let's see in detail how the convert() method has worked so far:
- Inside the convert() method, you find the number associated with the from the measure:
int nFrom = measures[from];
- Then, you do the same with the to measure:
int nTo = measures[to];
- Next, you create a multiplier value that takes the correct conversion formula from the formulas matrix:
var multiplier = formulas[nFrom.toString()][nTo];
- Finally, you calculate the result of the conversion:
double result = value * multiplier;
In this case, if the conversion is not possible, for example, when the user tries to convert a weight measure into a distance measure, this function does not raise any error.
Next, we need to show the result of the conversion to the user:
- Declare a String variable at the top of the MyAppState class:
- In the convert() method, after calculating the result, populate the _resultMessage String, and call the setState() method to notify the framework that an update to the UI is needed:
if (result == 0) {
_resultMessage = 'This conversion cannot be performed';
}
else {
_resultMessage = '${_numberFrom.toString()} $_startMeasure are ${result.toString()} $_convertedMeasure';
}
setState(() {
_resultMessage = _resultMessage;
});
- Finally, we need to call the convert() method when the user taps on the Convert button. Before calling the method, we'll check that every value has been set to prevent potential errors. Edit RaisedButton, as shown here:
RaisedButton(
child: Text('Convert', style: inputStyle),
onPressed: () {
if (_startMeasure.isEmpty || _convertedMeasure.isEmpty || _numberFrom==0) {
return;
}
else {
convert(_numberFrom, _startMeasure, _convertedMeasure);
}
},
),
- In order to show the result, let's also update the Text widget, so that it shows the string that contains the message to the user:
Text((_resultMessage == null) ? '' : _resultMessage,
style: labelStyle),
Congratulations, the app is now complete! If you try it out now, you should see a screen like the one shown here:
As you can see in the preceding screenshot, when we select two compatible measures, you should get the correct result on the screen.