Learn to build your first Flutter app with our step-by-step guide to creating a unit converter application. Begin your mobile app development journey today!
Please contact us if anything is not clearly described, does not work, seems incorrect or if you require support.
Embarking on the journey to build your first Flutter app can be an exciting yet daunting task. Flutter, with its multiplatform support, fast development times, and the hot reload feature, stands out as a powerful toolkit for app development. However, the initial learning curve can seem steep for beginners. This guide aims to simplify the process, guiding you through crafting a unit converter app. This project not only serves as a practical introduction to Flutter but also demonstrates the framework's advantages in a real-world application.
First lets install flutter:
sudo snap install flutter --classic
To verify that flutter is working correctly:
flutter doctor
Begin by creating a new Flutter project. Open your terminal and run:
flutter create unit_converter
This command will create the default flutter app.
First create a new file titled unit.dart in the lib directory in the project.
In the file start by defining an enumeration for unit types:
enum Type { weight, length, time }
Then, introduce a class to represent various units, incorporating a name, type (from the enum), and a value relative to standard SI units:
class Unit {
String name;
Type type;
double valSiUnits;
Unit(this.name, this.valSiUnits, this.type);
}
For unit conversion functionality, add:
double? convert(double? val, Unit to) {
if (type != to.type) {
throw Exception("Trying to convert between incompatible types");
}
if (val == null) return null;
return val * valSiUnits / to.valSiUnits;
}
This method checks that the units are compatible (i.e., of the same type), handles null inputs gracefully by returning null if the input value is null, and performs the conversion using the ratio of the units' SI values.
Since the physical constants we're dealing with are immutable and our application's scope is defined, we can define our units as global constants within unit.dart:
final Map> units = {
Type.weight: [
Unit("Kilogram", 1.0, Type.weight), // SI unit
Unit("Gram", 0.001, Type.weight),
Unit("Pound", 0.453592, Type.weight),
Unit("Stone", 6.35029, Type.weight),
],
Type.length: [
Unit("Meter", 1.0, Type.length), // SI unit
Unit("Centimeter", 0.01, Type.length),
Unit("Inch", 0.0254, Type.length),
Unit("Mile", 1609.34, Type.length),
Unit("Kilometer", 1000, Type.length),
],
Type.time: [
Unit("Second", 1.0, Type.time), // SI unit
Unit("Minute", 60.0, Type.time),
Unit("Hour", 3600.0, Type.time),
Unit("Day", 3600 * 24, Type.time),
Unit("Week", 3600 * 24 * 7, Type.time)
],
};
You can easily expand this by adding new units and unit types.
Navigate back to the main.dart file to start building the user interface.
Ensure unit.dart is imported:
import 'package:unit_converter/unit.dart';
Edit the build function of the MyApp class to customize the app's theme and title. This is where you set the global appearance of your app and its title as displayed in the task switcher.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Unit Converter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightGreen),
useMaterial3: true,
),
home: const MyHomePage(title: 'Unit Converter'),
);
}
You can tinker with this to better see how it changes the app.
In _MyHomePageState, introduce variables to represent the conversion logic, including the units and values involved, and controllers for text input.
Type unitType = Type.weight;
Unit fromUnit = units[Type.weight]![0];
Unit toUnit = units[Type.weight]![1];
double? valueFrom = 0;
double? valueTo = 0;
final controllerFrom = TextEditingController();
final controllerTo = TextEditingController();
We need to provide users with the ability to select unit types. One way to achieve this is by using a PopupMenuButton in the Appbar. Here is a straightforward implementation of it:
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
actions: [
PopupMenuButton(
itemBuilder: (BuildContext context) {
return [
PopupMenuItem(
onTap: () {
unitType = Type.weight;
},
child: const Text("Weight"),
),
PopupMenuItem(
onTap: () {
unitType = Type.weight;
},
child: const Text("Length"),
),
PopupMenuItem(
onTap: () {
unitType = Type.time;
},
child: const Text("Time"),
)
];
},
),
],
),
While this method works well for our small app, to enhance maintainability and scalability, especially as the development progresses and the app grows, it's beneficial to automate the creation of popup menu items. This avoids manual item declaration and makes adding new unit types much simpler:
List items = [];
for (final type in Type.values) {
items.add(PopupMenuItem(
onTap: () {
if (unitType == type) return;
unitType = type;
fromUnit = units[type]![0];
toUnit = units[type]![1];
setVal(valueFrom, from: true);
},
child: Text(type.name),
));
}
return items;
By iterating over Type.values, this approach dynamically generates menu items for each unit type.
The importance of this approach is demonstrated as we update the onTap function to adjust the selected units to match the unit type. With the original method this would have introduced excessive redundancy.
During debugging, be aware that the debug banner may obscure the menu icon. This experience emphasizes the importance of testing your UI in both development and production modes to ensure usability.
While building a unit converter app, it's essential for the text fields to update automatically with every change of the unit quantity or the selected units. This requires a function specifically for managing these updates, making the app more responsive and user-friendly.
void setVal(double? val, {required bool from, bool modifyChanged = true}) {
if (from) {
valueFrom = val;
valueTo = fromUnit.convert(val, toUnit);
} else {
valueTo = val;
valueFrom = toUnit.convert(val, fromUnit);
}
if (modifyChanged || !from) {
controllerFrom.text =
(valueFrom == null) ? "" : valueFrom!.toStringAsFixed(4);
}
if (modifyChanged || from) {
controllerTo.text = (valueTo == null) ? "" : valueTo!.toStringAsFixed(4);
}
setState(() {});
}
This function processes the new unit quantity and a boolean indicating whether we're changing the 'from' or 'to' value. It then updates both the conversion values and their corresponding text fields accordingly.
To implement the unit selection in our converter app, we can use drop-down menus for users to choose the units they're converting to and from. Leveraging the DropdownSearch widget offers a refined user experience with searchable and selectable unit options. Before incorporating DropdownSearch, ensure it's added to your project by running:
flutter pub add dropdown_search
and importing it in your Dart file:
import 'package:dropdown_search/dropdown_search.dart';
To avoid redundancy we craft a function, getUnitSelectorWidget, to dynamically generate these drop-down menus. This function is designed to be adaptable, serving both "from" and "to" unit selection scenarios based on a boolean parameter. The implementation is as follows:
Widget getUnitSelectorWidget({required bool from}) {
return DropdownSearch(
filterFn: (item, filter) {
return item.name.toLowerCase().startsWith(filter.toLowerCase());
},
compareFn: (item1, item2) {
return item1.name == item2.name;
},
popupProps: const PopupPropsMultiSelection.modalBottomSheet(
searchDelay: Duration.zero,
showSelectedItems: true,
showSearchBox: true,
),
onChanged: (var newValue) {
if (newValue == null) return;
if (from) {
fromUnit = newValue;
} else {
toUnit = newValue;
}
// Update the converted values by calling setVal
setVal(valueFrom, from: true);
},
selectedItem: from ? fromUnit : toUnit,
items: units[unitType]!,
itemAsString: (item) => item.name,
);
}
In this setup, filterFn and compareFn ensure that units are easily found and correctly matched during searches. popupProps customize the drop-down's interactive features, such as search delay and display settings. Lastly, the onChanged callback updates the app state based on user selection, maintaining real-time responsiveness. Through this approach, we establish a user-friendly interface for selecting conversion units, enhancing the overall functionality of our app.
Creating a responsive and user-friendly interface for our unit converter app involves allowing users to input quantities for conversion. This step introduces a function to streamline the creation of input fields, making the app's design cohesive and removing the redundancy that would appear from separately defining the "from" and "to" text fields.
Widget getQuantityInputWidget({required bool from, double width = 200}) {
return SizedBox(
width: width,
child: TextField(
keyboardType: TextInputType.number,
controller: from ? controllerFrom : controllerTo,
onChanged: (value) {
double? valueNum = double.tryParse(value);
if (valueNum == null) {
setVal(null, from: from);
}
setVal(valueNum, from: from, modifyChanged: false);
setState(() {});
},
),
);
}
In this setup, we configure a text field to accept only numerical values and link it to the correct controller. Then we ensure that when a new value is entered, it's automatically converted, and the corresponding value in the other text field is updated. To prevent the currently active text field from being reformatted while the user is typing, we use the modifyChanged: false parameter.
With all the essential components created, it's time to assemble our unit converter app within the build function. By organizing our widgets effectively, we create a user-friendly interface. Here's how the layout can be structured inside the Scaffold’s body:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
const Spacer(),
Expanded(child: getUnitSelectorWidget(from: true)),
const SizedBox(width: 20),
Expanded(child: getUnitSelectorWidget(from: false)),
const Spacer(),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
getQuantityInputWidget(from: true),
const SizedBox(width: 20),
getQuantityInputWidget(from: false),
],
),
],
),
),
This layout utilizes a Center widget to align the main column in the middle of the screen. We introduce two rows within this column: the first row hosts our unit selection dropdowns, and the second row contains the quantity input fields. Spacers and sized boxes are strategically placed to ensure there's adequate spacing, making the interface aesthetically pleasing and easy to navigate.
With this setup, we've successfully created a simple unit converter app that efficiently handles unit conversions across various dimensions like mass, length, and time, blending functionality with ease of use.
Are you looking for
Linux Emergency Support,
Linux Consulting for Projects,
Linux Managed Hosting,
Qubes OS Consulting and Support or
Linux Trainings and Workshops?