Flutter Roman / Arab Number Converter
Hello everyone! Today, we will be creating our first Flutter application. The app's essence is to facilitate the conversion of Arabic numerals to Roman and vice versa.
The Logic
First of all, as always, let's think of the logic behind the app we're going to create. In our app, we'll have two pages, one for Arabic to Roman converter and one for vice versa. On each page, we'll build an input field, a button to convert, and a text to show the result.
To convert the values we'll have two different functions that we're going to call when the button is pressed.
Set our project
Okay, to start, let's organize our project. We'll build two different pages that will be accessed by a navbar at the bottom of the screen.
In our lib folder add a folder called views with two dart files inside: arab_conv.dart & rom_conv.dart.
Next, go into your pubspec.yaml file and add this library so that we can use Google fonts in our app. My current version is 3.0.1 but you can add the latest available:
dependencies:
flutter:
sdk: flutter
google_fonts: ^3.0.1 # add this
main.dart
Let's now edit our main file. In our main file, we first have to import the files in the views folder and add the Google Fonts library:
import 'package:conv_roman/views/rom_conv.dart';
import 'package:conv_roman/views/arab_conv.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
Then, let's edit our main widget (MyApp) in this way:
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Arab/Roman Converter',
theme: ThemeData(
primarySwatch: Colors.red,
fontFamily: GoogleFonts.getFont('Poppins').fontFamily,
),
debugShowCheckedModeBanner: false,
home: const MyHomePage(title: 'Arab/Roman Converter'),
);
}
}
With this code, we simply configure our app and its style. In this case, we use the Google font Poppins as default.
For the Stateful component, we need to specify our navbar using the following code, which can be adapted for use in other projects by adjusting certain file names and text.:
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _currentIndex = 0;
static List<Widget> pages = <Widget>[
const RomConv(),
const ArabConv(),
];
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Arab - Roman Converter'),
centerTitle: true,
),
body: pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: _onItemTapped,
selectedItemColor: Colors.red,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.circle),
label: 'To Roman',
),
BottomNavigationBarItem(
icon: Icon(Icons.circle),
label: 'To Arab',
),
],
),
);
}
}
Let's analyze this code step by step:
Define a current index variable that indicates the page we are viewing;
Define the list of pages available;
Create a function to update the
_currentIndex
every time an item is pressed;Build the default page:
Add a default appBar with a title;
Add the body of the page we are currently viewing
Add the bottomNavbar.
rom_conv.dart
UI
Let's now build the page to convert an Arab number to a Roman one. First of all, add the packages for flutter material and google fonts.
Then create a new StatefulWidget called RomConv and add this code which we'll analyze later:
class RomConv extends StatefulWidget {
const RomConv({super.key});
@override
State<RomConv> createState() => _RomConvState();
}
class _RomConvState extends State<RomConv> {
TextEditingController arabNum = TextEditingController();
var pressed = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const SizedBox(
height: 20,
),
TextField(
controller: arabNum,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: arabNum.clear,
icon: const Icon(Icons.clear),
),
hintText: 'Arab Number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.00),
),
),
),
const SizedBox(
height: 20,
),
ElevatedButton(
child: const Text('Convert'),
onPressed: () {
pressed = true;
setState(() {});
},
),
const SizedBox(
height: 100,
),
Container(
padding: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(
width: 3.5,
color: pressed ? Colors.red : Colors.white,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
pressed
? calcRoman(arabNum.text)
: 'Insert a number',
style: TextStyle(
fontSize: 40,
fontFamily:
GoogleFonts.getFont('Playfair Display').fontFamily),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.help),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Help'),
content: const Text('To convert an arab number to a roman one, insert a number in the text field first.'),
actions: <Widget>[
ElevatedButton(
child: const Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
),
);
}
}
Now, analyze!
Create the StatefulWidget RomConv
Create the TextEditingController for the arabNum input, which will store the value of the Arabic number that we enter.
Create a variable for the state of the button (false/true);
Create the UI of the page:
In the main column create a TextField which will be the input for our Arabic number;
After some whitespace create the button to convert the Arabic number to a Roman one. This button will call a function we'll define later in this post;
Create a container to display the Roman numeral result. In case there is no number to display, it'll show the message 'Insert a number'.
A simple FloatingActionButton to help people do the right things
The function
Let's now write the function to convert an Arabic number to a Roman one.
To begin with, we need to verify that the input is a numerical value. If not, we will display an error message. Next, we will create two lists - one for Arabic numerals and the other for Roman numerals. We will then iterate through the Arabic numeral list and assign the corresponding Roman numeral for each value.
Here's the code to do that:
String calcRoman(input) {
if (int.tryParse(input) == null) {
return 'Error';
}
int value = int.parse(input);
List<int> iNum = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
List<String> rNum = [
'M',
'CM',
'D',
'CD',
'C',
'XC',
'L',
'XL',
'X',
'IX',
'V',
'IV',
'I'
];
var nRoman = '';
for (var i = 0; i < iNum.length; i++) {
while (iNum[i] <= value) {
nRoman += rNum[i];
value -= iNum[i];
}
}
return nRoman;
}
arab_conv.dart
UI
The UI is the same as the rom_conv.dart
but with changes to variable names and texts so I am going to skip that part by passing only the code:
class ArabConv extends StatefulWidget {
const ArabConv({super.key});
@override
State<ArabConv> createState() => _ArabConvState();
}
class _ArabConvState extends State<ArabConv> {
final romanNum = TextEditingController();
var pressed = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const SizedBox(
height: 20,
),
TextField(
controller: romanNum,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: romanNum.clear,
icon: const Icon(Icons.clear),
),
hintText: 'Roman Number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.00),
),
),
),
const SizedBox(
height: 20,
),
ElevatedButton(
child: const Text('Convert'),
onPressed: () {
pressed = true;
setState(() {});
},
),
const SizedBox(
height: 100,
),
Container(
padding: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(
width: 3.5,
color: pressed ? Colors.red : Colors.white,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
pressed ? calcArab(romanNum.text).toString() : 'Insert a number',
style: TextStyle(
fontSize: 40,
fontFamily:
GoogleFonts.getFont('Playfair Display').fontFamily),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.help),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Help'),
content: const Text('To convert a roman number to an arab one, insert a roman number with this letters: \n I - V - X - L - C - D - M.'),
actions: <Widget>[
ElevatedButton(
child: const Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
},
),
);
}
}
The function
In this scenario, the approach will be altered slightly. Firstly, we need to establish a Map (Dictionary) where each Roman letter is paired with its corresponding value. Next, we must address situations such as IV or IX, where we don't simply sum the value of each letter, but instead check if the subsequent value is greater and perform a subtraction:
int calcArab(String romanNumber) {
var romanSymbols = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000,
};
int result = 0;
int lastValue = 0;
bool error = false;
for (int i = romanNumber.length - 1; i >= 0; i--) {
if ('IVXLCDM'.contains(romanNumber[i]) && !error) {
int currentValue = romanSymbols[romanNumber[i]]!;
if (currentValue >= lastValue) {
result += currentValue;
}
else {
result -= currentValue;
}
lastValue = currentValue;
}
else {
error = true;
}
}
if (error) {
return 0;
} else {
return result;
}
}
Final Execution & Resources
We've finally arrived at the end. You should now see your amazing app that converts Arabic to Roman numbers and vice versa. If you get any errors, feel free to comment them on this post. I should respond right away.
You can find the entire project and some screenshots of the app on the GitHub repo at this link. Until a new post, you can read the old ones which are pretty cool too.
That's it for today,
Take care
Subscribe to my newsletter
Read articles from Nick747 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Nick747
Nick747
Hello! I am a teenager and a web/app developer since 2018.