This is the third part of Flutter tutorial series where we are building an Expenses Manager app. In this post we will refactor our existing work and also build an icon picker functionality
Introduction
In our previous post of the Flutter tutorial series, we created a static list of expense categories. We also setup an OfflineDbProvider
for SQLite database engine and a service implementation for expense category.
In this post, we will re-factor our classes to make things cleaner. We will then add a screen to create a new category.
Re-factoring Classes
So far we have been writing all of our code in the main.dart
file. Not a clean approach right? Let’s move the classes to separate files.
Inside the lib folder create a folder “screens“. This folder will contain all the routes that we will create.
Move Out HomePage
Add a new file “home_page.dart” and move the content of HomePage
and _HomePageState
classes from main.dart file to this file. Your main.dart file should look clean like this:
import 'package:expense_manager/screens/home_page.dart';
import 'package:flutter/material.dart';
import 'package:expense_manager/db/offline_db_provider.dart';
void main() {
OfflineDbProvider.provider.initDB();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: HomePage(),
);
}
}
Move Out Category
Now create another file “category.dart” inside the screens folder. Move the content for Category widget from home_page.dart to this file creating a Stateful Category
widget . At the end, your HomePage
should look like this:
import 'package:expense_manager/screens/category_page.dart';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
TabController _tabController;
List<String> _tabs = ["Home", "Category", "Report"];
@override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: _tabs.length);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Expense Manager"),
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.home)),
Tab(icon: Icon(Icons.category)),
Tab(icon: Icon(Icons.report)),
],
),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Center(
child: Text(
"Home",
style: Theme.of(context).textTheme.display1,
)),
CategoryPage(),
Center(
child: Text(
"Reports",
style: Theme.of(context).textTheme.display1,
))
],
));
}
}
And the Category
widget:
import 'package:expense_manager/models/category_model.dart';
import 'package:flutter/material.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
List<CategoryModel> _lsCateogies = List<CategoryModel>();
@override
initState() {
super.initState();
_initCategories();
}
_initCategories() {
var cat1 = CategoryModel().rebuild((b) => b
..id = 0
..title = "Home Utils"
..desc = "Home utility related expenses"
..iconCodePoint = Icons.home.codePoint);
_lsCateogies.add(cat1);
var cat2 = CategoryModel().rebuild((b) => b
..id = 0
..title = "Grocery"
..desc = "Grocery related expenses"
..iconCodePoint = Icons.local_grocery_store.codePoint);
_lsCateogies.add(cat2);
var cat3 = CategoryModel().rebuild((b) => b
..id = 0
..title = "Food"
..desc = "Food related expenses"
..iconCodePoint = Icons.fastfood.codePoint);
_lsCateogies.add(cat3);
var cat4 = CategoryModel().rebuild((b) => b
..id = 0
..title = "Auto"
..desc = "Car/Bike related expenses"
..iconCodePoint = Icons.directions_bike.codePoint);
_lsCateogies.add(cat4);
}
@override
Widget build(BuildContext context) {
return _getCategoryTab();
}
Widget _getCategoryTab() {
return ListView.builder(
itemCount: _lsCateogies.length,
itemBuilder: (BuildContext ctxt, int index) {
var category = _lsCateogies[index];
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
border: new Border.all(
width: 1.0, style: BorderStyle.solid, color: Colors.white)),
margin: EdgeInsets.all(12.0),
child: ListTile(
onTap: () {},
leading: Icon(
IconData(category.iconCodePoint, fontFamily: 'MaterialIcons'),
color: Theme.of(context).accentColor,
),
title: Text(category.title, style: Theme.of(context).textTheme.body2.copyWith(
color: Theme.of(context).accentColor
),),
subtitle: Text(category.desc, ),
),
);
},
);
}
}
Create New Category
Now that we have organized our code a little, let’s start development again. Add a button to create a new category.
Widget _getCategoryTab() {
return Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(12.0),
width: 200.0,
child: RaisedButton(
child: Text("Add New"),
onPressed: () {
//todo: implement navigation
},
),
),
Expanded(
child: ListView.builder(
...
We have updated the _getCategoryTab so that it now contains a button which is wrapped alongside the ListView
in a Column
.
The ListView
is wrapped inside the Expanded
widget so that it can take up the remaining space in the column.
The “Add New” button should redirect to create category page. To create this page, add a new folder “routes” inside the lib folder and add a file “add_category.dart“.
import 'package:flutter/material.dart';
class AddCategory extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Add New Category"),
),
body: Container(
padding: EdgeInsets.all(12.0),
child: Column(
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: "Title"),
),
TextField(
decoration: InputDecoration(labelText: "Description"),
maxLines: 2,
),
//todo: icon picker
],
),
),
);
}
}
The AddCategory
widget has a basic layout for now. We will make more changes here as we go along. But first, add a navigation to this page from Category screen.
child: RaisedButton(
child: Text("Add New"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddCategory()),
);
},
In Flutter, there are many options for using navigation and routing. You can learn more about navigation in our Introduction To Navigation And Routing.
Icon Picker Functionality
In Flutter, an icon is created out of IconData. Since we want users to be able to set icons for each category label, we need to create a list of icons to pick from. A user can select icons from the list and assign it to the category.
_showIconGrid() {
var ls = [
Icons.web_asset,
Icons.weekend,
Icons.whatshot,
Icons.widgets,
Icons.wifi,
Icons.wifi_lock,
Icons.wifi_tethering,
Icons.work,
Icons.wrap_text,
Icons.youtube_searched_for,
Icons.zoom_in,
Icons.zoom_out,
Icons.zoom_out_map,
Icons.restaurant_menu,
Icons.restore,
Icons.restore_from_trash,
Icons.restore_page,
Icons.ring_volume,
Icons.room,
Icons.exposure_zero,
Icons.extension,
Icons.face,
Icons.fast_forward,
Icons.fast_rewind,
Icons.fastfood,
Icons.favorite,
Icons.favorite_border,
];
return GridView.count(
crossAxisCount: 8,
children: List.generate(ls.length, (index) {
var iconData = ls[index];
return IconButton(
onPressed: () {
},
icon: Icon(
iconData,
));
}),
);
}
Here, we have created a list of random icons to work with. The icons will be shown in a GridView which can arrange the icons in a tile. Since, we want the users to be able to select icon, we have used IconButton
.
Add _showIconGrid
inside the column:
...
child: Column(
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: "Title"),
),
TextField(
decoration: InputDecoration(labelText: "Description"),
maxLines: 2,
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: _showIconGrid()))
],
...

Wrapping Up
In this part of our Flutter tutorial series, we didn’t do a whole lot. We organized the classes to separate files and folders. Then we created a “Add New Category” screen and implemented an icon picker functionality.
Now in the next part we shall look into making this page functional.
You must be logged in to post a comment.