Categories
Flutter

Flutter Tutorial: Building An Expense Manager App – 6

This is the sixth part of Expenses Manager app Flutter tutorial series where we are building an Expenses Manager app.

Introduction

In the previous post, we completed all the functionalities of creating Category. In this part of our Flutter tutorial, we will add features for working with expenses. We will design the home page and also create a category selector using ChoiceChip widget.

Designing Dashboard Page

The home tab of our Expense Manager app will be a dashboard page. In this page, we will have:

  • Link to add new expense.
  • A Date Switcher.
  • List of expenses for a selected date.

Designing A Date Switcher

We will start by adding a date switcher for our app. Basically, we want to show current date and add buttons to change current date. The buttons should allow user to go back and forth by a day.

With the date switcher, the dashboard can start to look like this:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class DashboardPage extends StatelessWidget {
  String getStringDate(DateTime dt) {
    return "${dt.year}/${dt.month}/${dt.day}";
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(children: <Widget>[
        Container(
            padding: EdgeInsets.all(12.0),
            width: double.infinity,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                IconButton(
                  onPressed: () {},
                  icon: Icon(Icons.arrow_back),
                ),
                Container(
                  margin: EdgeInsets.symmetric(horizontal: 12.0),
                  child: Text(
                    getStringDate(DateTime.now()),
                    style: Theme.of(context).textTheme.title,
                  ),
                ),
                IconButton(
                  onPressed: () {},
                  icon: Icon(Icons.arrow_forward),
                )
              ],
            ))
      ]),
    );
  }
}

We have yet to implement onPressed functionality for icon buttons.

Add FloatingActionButton To Add Expenses

Next, we will add a button to add expenses. A FloatingActionButton would be the right choice for this purpose since it will be distinctly visible.

...
floatingActionButton: FloatingActionButton(
        onPressed: (){},
        child: Icon(Icons.add)
      ),
...

Add ListView To Show Expenses

Finally, we will have a list of expenses for the selected date shown. For this, we will use a ListView with sample expenses for now.

Widget _getExpenses() {
    var expense1 = ExpenseModel().rebuild((b) => b
      ..id = 1
      ..title = "Coffee"
      ..notes = "Coffee at peepalbot"
      ..amount = 129.00);

    var expense2 = ExpenseModel().rebuild((b) => b
      ..id = 2
      ..title = "Lunch"
      ..notes = "Momos at dilli bazar"
      ..amount = 150.00);

    var expense3 = ExpenseModel().rebuild((b) => b
      ..id = 3
      ..title = "Pants"
      ..notes = "Bought a pair of pants from Dbmg"
      ..amount = 2500.00);

    var ls = [expense1, expense2, expense3];

    return ListView.builder(
      itemCount: ls.length,
      itemBuilder: (context, index) {

        var expense = ls[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: () {},            
            trailing: IconButton(
              icon: Icon(Icons.delete),
              color: Theme.of(context).primaryColorLight,
              onPressed: () {},
            ),
            title: Text(
              expense.title + " - Rs." + expense.amount.toString(),
              style: Theme.of(context)
                  .textTheme
                  .body2
                  .copyWith(color: Theme.of(context).accentColor),
            ),
            subtitle: Text(
              expense.notes,
            ),
          ),
        );
      },
    );
  }

Our design so far should look like this:

Expense Manager Dashboard Design
Expense Manager Dashboard Design

Adding New Expense

Now let’s add functionality to the dashboard page. First we will add functionality to add new expense. Similar to what we have done for category, we will create a separate route for adding expenses.

A product business logic component will handle UI interactions as well as expenses data stream. Also the CategoryBloc shall be used to fetch list of categories as well.

Since, most of the functionalities for creating new expense is going to be similar to that of creating category, we will not repeat everything here. You can refer to the previous posts regarding:

  • BLoC pattern implementation and
  • Working with StreamBuilder and data stream.

Rather, here we will build a Category selector.

Using ChoiceChip Widget In Flutter

Since, every expense will fall under a category, when creating a new expense we will have to select a category. For this selection we can make use of the ChoiceChip widget. This widget can enable to select one item from a list of chip widgets.

Get Category List By Listening To Category Stream

Use the StreamBuilder like before to listed to category list stream.

StreamBuilder(
  stream: categoryBloc.categoryListStream,
  builder: (_, AsyncSnapshot<BuiltList<CategoryModel>> snap) {

You can access individual category model using the list index.

var categoryModel = snap.data[index];

Create ChoiceChip Widget

And in your ChoiceChip widget, you can use this model to show text as well as to set selected property.

ChoiceChip(
  selectedColor: Theme.of(context).accentColor,
  selected: categoryModel.id == selectedCategoryId,
  label: Text(categoryModel.title),
  onSelected: (selected) {
	setState(() {
	  selectedCategoryId = categoryModel.id;
	});
  },
)

Complete Implementation Using ChoiceChip Widget

The complete implementation could look like this:

import 'package:built_collection/built_collection.dart';
import 'package:expense_manager/blocs/category_bloc.dart';
import 'package:expense_manager/db/services/category_service.dart';
import 'package:expense_manager/models/category_model.dart';
import 'package:flutter/material.dart';

class AddExpense extends StatefulWidget {
  @override
  _AddExpenseState createState() => _AddExpenseState();
}

class _AddExpenseState extends State<AddExpense> {
  CategoryBloc categoryBloc;

  @override
  void initState() {
    super.initState();
    categoryBloc = CategoryBloc(CategoryService());
  }

  int selectedCategoryId = 0;

  @overrideW
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Add New Expense"),
        ),
        body: Container(
          padding: EdgeInsets.all(12.0),
          child: Column(
            children: <Widget>[
              Container(
                margin: EdgeInsets.only(bottom: 12.0),
                child: Text("Pick Category", style: Theme.of(context).textTheme.title,)),
              Container(                
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(8.0),
                  color: Colors.white,
                ),
                child: StreamBuilder(
                  stream: categoryBloc.categoryListStream,
                  builder: (_, AsyncSnapshot<BuiltList<CategoryModel>> snap) {
                    if (!snap.hasData)
                      return Center(
                        child: CircularProgressIndicator(),
                      );

                    return Wrap(
                        children: List.generate(snap.data.length, (int index) {
                      var categoryModel = snap.data[index];
                      return Container(
                        margin: EdgeInsets.symmetric(horizontal: 2.0,),
                        child: ChoiceChip(
                          selectedColor: Theme.of(context).accentColor,
                          selected: categoryModel.id == selectedCategoryId,
                          label: Text(categoryModel.title),
                          onSelected: (selected) {
                            setState(() {
                              selectedCategoryId = categoryModel.id;
                            });
                          },
                        ),
                      );
                    }));
                  },
                ),
              )
            ],
          ),
        ));
  }
}

Here, we are using the setState function to rebuild the entire page on each category selection. You can also avoid the entire rebuild by putting the value of selected categoryId inside a stream and using a StreamBuilder.

ChoiceChip Category Selector
ChoiceChip Category Selector

Link To Previous Posts

GitHub Link

Checkout the entire project from GitHub.

Similar Tutorial Series:

Build HangMan Game In Flutter

13 replies on “Flutter Tutorial: Building An Expense Manager App – 6”

This was really looking like the best tutorial ever,
but got a bit lost on the last page, it would be good if you had a link to the code or could have shown full code and maybe gone into things a bit more in part six. everything was working up to part six, but now i am stuck, i will try to solve some more, but would be good if you could link to code or update this page a bit.

Thanks.
Paul.

Thank you Paul, I am glad this was helpful to you. I have updated the post with a link to the complete project in github.
Good luck,

Sovit

This is the best tutorial I found in the subject, thank you very much. Please make more parts, I would love to see how you will do the complete app.
Best regards, Ana

Here I am on the last part. Can’t tell you how grateful I am. This is one of the most thorough tutorial I have come across since started learning Flutter. Big THANK YOU to you!!!

Hello Sovit, on the create expense page, there would be two stream builders, one for the category choicechip implementation, another one for the expense create/update. What do I do to bring selectedCategoryId into the expense stream builder? Wouldn’t a future builder be better for the category choiceship?

Hello Sovit, on this dashboard page, I would display the icon within the listtile. On calling CategoryBloc(CategoryService()).getCategories(), it returns null. What would be an effective method to fetch the iconCodePoint information from Category?

Thank you Sovit.

I have been trying to use ‘where’ inside a _expenseBloc.expenseListStream StreamBuilder to filter the expenses when the date switcher pressed. No success yet. Not sure what to do with the returned lazy iterable with ‘where’ applied to the snapshot data. Any suggestion?

Leave a Reply