image
author

Thomas Wilfred

Front-end Mobile Engineer


# All source is available here, you can either download 
# or follow the tutorial below to understand 
# each and every component individually 
 
https://github.com/codersera-repo/expensesapp-flutter.git
Flutte Header Logo

In this Flutter Tutorial, we will help you build and install your very first flutter app that will be Personal Expense with rich UI and complete functionalities. After this tutorial, you can make your own apps and can publish them on App Store and Play Store.

So What Is Flutter?

Flutter is Google’s mobile app SDK for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source. Flutter works on one codebase but results in building native apps for both Android and iOS.

Basic Requirements Before Starting With Flutter

  • Knowledge of Dart, which is essential for developing Flutter Applications. Learn more about Dart from https://dart.dev/guides.

Architecture of Flutter

flutter architecture

Everything is a widget in Flutter App. Flutter gives you a plethora of widgets for building rich UI with powerful functionalities. There may be more than one way of achieving the same UI or functionality in Flutter. Flutter embrace Platform Differences, that means if you want to make your app function in one way in Android but differently in iOS and even if you want to make your app function differently in different device sizes. Flutter works on One Codebase but results in having an app for both iOS and Android, it is just same as Learn Once Write Anywhere.

Comparison between the two giants

flutter vs react native
flutter vs reactnative comp

Important Resources

Let’s start with installing Flutter into your system

Reach to website https://flutter.dev/docs/get-started/install

1. Click on your system’s OS, and then download the Flutter SDK.

2. If you are Mac or Linux user:-

3. Extract the file in the desired location, for example:


cd ~/development
#flutter version might be different in your case
tar xf ~/Downloads/flutter_linux_v1.7.8+hotfix.4-stable.tar.xz
  • Open (or create) $HOME/.bash_profile. The file path and filename might be different on your machine.
  • Add the following line and change [PATH_TO_FLUTTER_GIT_DIRECTORY] to be the path where you cloned Flutter’s git repo:
 
export PATH=$PATH:[PATH_TO_FLUTTER_GIT_DIRECTORY]/flutter/bin
  • Run source $HOME/.bash_profile to refresh the current window.
  • Verify that the flutter/bin the directory is now in your PATH by running:

 echo $PATH

4. If you are Windows User:-

  • Extract the zip file and place the contained flutter in the desired installation location for the Flutter SDK (for example, C:\src\flutter; do not install Flutter in a directory like C:\Program Files\ that requires elevated privileges).
  • Locate the file flutter_console.bat inside the flutter directory. Start it by double-clicking.
  • From the Start search bar, type ‘env’ and select Edit environment variables for your account.
  • Under User variables check if there is an entry called Path:
    • If the entry does exist, append the full path to flutter\bin using ; as a separator from existing values.
    • If the entry does not exist, create a new user variable named Path with the full path to flutter\bin as its value.

Note that you have to close and reopen any existing console windows for these changes to take effect.

5. Run flutter doctor

From a console window that has the Flutter directory in the path (see above), run the following command to see if there are any platform dependencies you need to complete the setup:content_copy


C:\src\flutter>flutter doctor

This command checks your environment and displays a report of the status of your Flutter installation. Check the output carefully for other software you might need to install or further tasks to perform (shown in bold text).

6. You need to setup either Android Emulator or iOS simulator into your device, which is really easy. You can download them from here :-

To install Android Studio:-

  1. Download and install Android Studio.
  2. Start Android Studio, and go through the ‘Android Studio Setup Wizard’. This installs the latest Android SDK, Android SDK Platform-Tools, and Android SDK Build-Tools, which are required by Flutter when developing for Android.

7. Install VS Code in your System and install extensions named Dart and Flutter

Let’s get started with our Personal Expenses App Tutorial

  1. Run flutter create expenses, to create to new flutter project named expenses.

#to create new flutter project in your favorite location
flutter create expenses
#get into the directory
cd expenses

#run flutter emulators to know about the emulators in your system
flutter emulators
#to open emulator, you can get to know about emulator id from above command
flutter emulators --launch <emulator id>
#to open project in emulator
flutter run

2. Important concepts, widgets and layouts which we are gonna use in this project!!!

flutter layout 1
Flutter Layout info 1
Flutter Layout info 2
Flutter Layout 2
Flutter Layout info 3
Flutter Layout 4
Flutter Layout info 4
Flutter Layout 5
Flutter Layout info 5
Flutter Layout info 6
Flutter Layout 7
Flutter Layout info 7
Flutter Layout 8
Flutter Layout 9
Flutter Layout 10
Flutter Layout 11
Flutter Layout 12

3. We have to just work on lib directory and some work on pubspec.yaml, rest of the directories and files are necessary for flutter and it’s usage for building native apps for both android and iOS from one codebase.

4. Open lib -> main.dart file, then click on debug -> Start Without Debugging from the menu of VS Code. You will either be prompt to open emulator in VS Code or if any emulator is open in your system then by default it opens the app in it and it also opens Debug Console in your app and a menu bar, this menu bar will help you sync your code with emulator as you give it action.

Menu Bar Flutter

Initial Application Launch
This is the menu bar which will open after following above step

5. Now let’s remove everything from main.dart file to understand flutter from scratch. Let’s first write the main method, which will be the first thing which flutter run in the whole app. In the main method, we execute flutter’s inbuilt function runApp from flutter’s widget library which inflate the given widget/class and attach it to the screen, and we pass the class’s object as MyApp(), without new keyword like a function which you may familiar from other languages, those parentheses represent constructor.

 
#file: main.dart

#this is the short hand way to write those functions which has just one thing to do in dart
#here we run flutter method runApp which takes a class as argument
#We will soon form class MyApp
void main() => runApp(MyApp());

6. Now let’s form our class MyApp and import material.dart from flutter package. Here we need to extend StatelessWidget for our class so that we can override its methods. Therefore inside class we had overridden build method from StatelessWidget class which is of type Widget and take BuildContext type as argument and return any Widget. So here, we return widget (MaterialApp) from imported material.dart file. You may observe that widgets are simple dart classes. Now as MaterialApp is a widget so it takes named arguments inside its constructor, like title and home so that order don’t matter. We will soon learn how can we make our own class’s constructors which will take named arguments.


#file: main.dart

import 'package:flutter/material.dart';

#we will soon form MyHomePage class in this project
........
class MyApp extends StatelessWidget{
//use of override annotation is not necessary but it is good to have as you can form your own build method, so to differentiate and even for implementing all necessary properties and methods
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FlutterApp', 
      home: MyHomePage(),
    );
  }
}

7. Let’s form MyHomePage class now, it will be our Stateful class,

StatelessWidget will never rebuild by itself (but can from external events). A StatefulWidget can. That is the golden rule.

BUT any kind of widget can be repainted any times.

Stateless only means that all of its properties are immutable and that the only way to change them is to create a new instance of that widget. It doesn’t e.g. lock the widget tree.

Here we had formed private methods and variables, we form private variables, methods and classes by _(underscore) in flutter. Observe carefully how we form state here, this is how we form Stateful Widgets in flutter. We used Flutter’s material.dart provided Widgets here, You can even get to understand these widgets through Widget Catalogue of Flutter and how these works.

createState() -> Creates the mutable state for this widget at a given location in the tree.


#file: main.dart

import 'package:flutter/material.dart';

................
class MyHomePage extends StatefulWidget {

// createState method is coming from StatefulWidget
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  @override
  Widget build(BuildContext context) {
    return Text('I am _MyHomePageState');
  }
}
few Updates

8. Let’s form a simple dart class named Transaction, here we used @required inside argument so that whenever we form objects of this class then we must define all required keys into the constructor, and { } in the argument is for adding these properties in the constructor in the form of key-value pairs with specified names in this class, and to use these we need to import foundation.dart file. This is what we were talking about in step 6.


#file: transaction.dart

import 'package:flutter/foundation.dart';

class Transaction{
  String id;
  String title;
  double amount;
  DateTime date;

  Transaction({@required this.id, @required this.title, @required this.amount, @required this.date});

//this is the alternate way to write constructor in dart file of:- 
//  Transaction({ @required id, @required title, @required amount, @required 
//  date }){
//    this.id = id,
//    this.title = title,
//    this.amount = amount,
//    this.date = date
//}
}

9. Let’s add some fonts and an image in our project which we will use later, and you will learn how to configure your app with different settings.
You need to add file waiting.png in directory [our project]/assets/images/
Your pubspec.yaml file will look like this after removing all comments and adding required fonts and image.


#file: pubspec.yaml

...................
dependencies:
  flutter:
    sdk: flutter
  intl: ^0.15.8

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter


flutter:
  uses-material-design: true

  assets:
    - assets/images/waiting.png

  fonts:
    - family: OpenSans
      fonts:
        - asset: assets/fonts/OpenSans-Regular.ttf
        - asset: assets/fonts/OpenSans-Bold.ttf
          weight: 700
    - family: Quicksand
      fonts:
        - asset: assets/fonts/Quicksand-Regular.ttf
        - asset: assets/fonts/Quicksand-Bold.ttf
          weight: 700

10. Let’s add global theme to our project so that, we don’t require to add same stylings to each widget repeatedly, Flutter gives us this functionality so that we can change our theme in one place as necessary and can even provide user to choose his favorite one.
So, let’s open main.dart file’s MyApp class.


#file: main.dart

...............
Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FlutterApp', 
      home: MyHomePage(),
      theme: ThemeData(
        primarySwatch: Colors.pink,
        accentColor: Colors.black,
        fontFamily: 'Quicksand',
        appBarTheme: AppBarTheme(
          textTheme: ThemeData.light().textTheme.copyWith(
            title: TextStyle(
              fontFamily: 'OpenSans',
              fontSize: 20,
              fontWeight: FontWeight.bold, 
            )
          )
        ),
        textTheme: ThemeData.light().textTheme.copyWith(
          title: TextStyle(
            fontFamily: 'OpenSans',
            fontSize: 18,
            fontWeight: FontWeight.bold,
          )
        )
      ),
    );
  }
................

11. Let’s form a new file named transaction_list.dart which will show the list of transaction, Let’s here we install new package named intl package, intl package helps to format the dates in our project as specified:-

Add this to your package’s pubspec.yaml file:


dependencies:
  intl: ^0.15.2

You can install packages from the command line:

with pub:


$ pub get

#file transaction_list.dart

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

import '../models/transaction.dart';

class TransactionList extends StatelessWidget {
  final List<Transaction> transactions;
  final Function deleteTx;

//constructor
  TransactionList(this.transactions, this.deleteTx);

  @override
  Widget build(BuildContext context) {
    return Container(
        height: 550,
        child: this.transactions.isEmpty ? Column(
          children: <Widget>[
            Text(
              'No Transactions added yet!',
//this is how we use our theme from main.dart file
              style: Theme.of(context).textTheme.title, 
            ),
//Used to give some vertical space
            SizedBox(
              height: 60,
            ),
            Container(
              height: 400,
//Use of our image which we had configured
              child: Image.asset(
                'assets/images/waiting.png',
                fit: BoxFit.cover,
              ),
            ),
          ],
        ) : 
//ListView.builder is a way of constructing the list where children’s (Widgets) are built on demand. However, instead of returning a static widget, it calls a function which can be called multiple times (based on itemCount ) and it’s possible to return different widget at each call.
        ListView.builder(
          itemBuilder: (ctx, index){
            return Card(
              elevation: 5,
              margin: EdgeInsets.symmetric(vertical: 8, horizontal: 5),
                          child: ListTile(
                leading: CircleAvatar(radius: 30, 
                  child: Padding(
                    padding: const EdgeInsets.all(6.0),
                    child: FittedBox(child: Text('\$${transactions[index].amount}')),
                  ),
                ),
                title: Text(
                  transactions[index].title, 
                  style: Theme.of(context).textTheme.title,
                ),
//Use of intl library, DateFormat is coming from intl
                subtitle: Text(
                  DateFormat.yMMMd().format(transactions[index].date),
                ),
                trailing: IconButton(
                  icon: Icon(Icons.delete), 
                  color: Theme.of(context).errorColor,
                  onPressed: () => deleteTx(this.transactions[index].id),
                ),
              ),
            );
          },
          itemCount: transactions.length,
        )  
      );
  }
}

12. Let’s import transaction_list.dart file in main.dart file, and use it.


#file: main.dart

import 'package:flutter/material.dart';

import './widgets/transaction_list.dart';
import './models/transaction.dart';

.................
class _MyHomePageState extends State<MyHomePage> {

//Let's add demo list of transactions into a variable, it must be final
  final List<Transaction> _userTransactions = [
    Transaction(id: '1', title: 'New Shoes', amount: 2.00, date: DateTime.now()),
    Transaction(id: '2', title: 'Watch', amount: 4.00, date: DateTime.now()),
  ];

//adding a function into our file which needs to pass into the constructor of the transactions_list.dart file 
  void _deleteTransaction(String id){
//  setState helps to re-render the class/widget
    setState(() {
      this._userTransactions.removeWhere((tx) => id == tx.id);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Flutter App',
        ),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.add), 
            onPressed: () => {},
          )
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            TransactionList(this._userTransactions, this._deleteTransaction),
          ],
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: () => {}),
    );
  }
}

Showing Transactions List

13. Let’s now add new_transaction.dart file in our project so that user can add new transactions.


#file: new_transaction.dart

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

class NewTransaction extends StatefulWidget {
  final addTx;

  NewTransaction(this.addTx);

  @override
  _NewTransactionState createState() => _NewTransactionState();
}

class _NewTransactionState extends State<NewTransaction> {
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
  DateTime _selectedDate;

  void _submitData() {
    if(_amountController.text.isEmpty){
      return;
    }
    final enteredTitle = this._titleController.text;
    final enteredAmount = double.parse(this._amountController.text);

//adding the validations for form
    if (enteredTitle.isEmpty || enteredAmount <= 0 || _selectedDate == null) {
      return;
    }

//this is how we use the variables from the stateful class into its state class
    widget.addTx(enteredTitle, enteredAmount, _selectedDate);

//we use pop function of Navigator to close this modal after submitting
    Navigator.of(context).pop();
  }

  void _presentDatePicker() {
    showDatePicker(
            context: context,
            initialDate: DateTime.now(),
            firstDate: DateTime(2019),
            lastDate: DateTime.now())
        .then((pickedDate) {
      if (pickedDate == null) {
        return;
      } else {
        setState(() {
          this._selectedDate = pickedDate;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          TextField(
            decoration: InputDecoration(labelText: 'Title'),
            controller: _titleController,
//we use _(underscore) in argument if we don't want to mess with the parameter which is coming from its call
            onSubmitted: (_) => _submitData(),
          ),
          TextField(
            decoration: InputDecoration(labelText: 'Amount'),
            controller: _amountController,
            keyboardType: TextInputType.numberWithOptions(decimal: true),
            onSubmitted: (_) => _submitData(),
          ),
          Container(
            height: 70,
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Text(
                    _selectedDate == null
                        ? 'No date chosen!'
                        : 'Selected Date: ${DateFormat.yMd().format(_selectedDate)}',
                  ),
                ),
                FlatButton(
                  child: Text(
                    'Choose Date',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  onPressed: _presentDatePicker,
                  textColor: Theme.of(context).primaryColor,
                ),
              ],
            ),
          ),
          RaisedButton(
            child: Text('Add Transaction'),
            color: Theme.of(context).primaryColor,
            textColor: Colors.white,
            onPressed: _submitData,
          ),
        ],
      ),
    );
  }
}

14. Now let’s import the new_transaction file in main.dart and add functionalities like adding form through modal, and add functionality.


#file: main.dart

import 'package:flutter/material.dart';

import './widgets/transaction_list.dart';
import './widgets/new_transaction.dart';
import './models/transaction.dart';

...............

class _MyHomePageState extends State<MyHomePage> {

  final List<Transaction> _userTransactions = [
//commented the dummy transactions
    //  Transaction(id: '1', title: 'New Shoes', amount: 2.00, date: DateTime.now()),
    //  Transaction(id: '2', title: 'Watch', amount: 4.00, date: DateTime.now()),
  ];

  void _addNewTransaction(String title, double amount, DateTime chosenDate){
    final newTx = Transaction(title: title, amount: amount, id: DateTime.now().toString(), date: chosenDate);

    setState(() {
      this._userTransactions.add(newTx);
    });
  }

  void _startAddNewTransaction(BuildContext ctx){
//it's been coming from material.dart
    showModalBottomSheet(
      context: ctx, 
      builder: (_){
      return NewTransaction(this._addNewTransaction);
    });
  }

  void _deleteTransaction(String id){
//List has some pre-defined methods like removeWhere
    setState(() {
      this._userTransactions.removeWhere((tx) => id == tx.id);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Flutter App',
        ),
//Now we are calling our function to open modal, instead of doing nothing
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.add), 
            onPressed: () => _startAddNewTransaction(context),
          )
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            TransactionList(this._userTransactions, this._deleteTransaction),
          ],
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,

      floatingActionButton: FloatingActionButton(child: Icon(Icons.add), onPressed: () => _startAddNewTransaction(context),),
    );
  }
}
Modal form of App

App Complete

That concludes our tutorial, if you have any queries regarding this tutorial comment us on our git repository Click Here

How useful was this post?

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 3

No votes so far! Be the first to rate this post.

Please do Rate Us and Share!

Related Blogs

  • author
    Adam Davidson

    Best Android Emulator for PC

    Android emulator for PC or MACs is one of the best for gamers to focus and improve their gaming skills.There are many reasons for emulating Android on your Windows PC, because with the help of emulation it is much easier to test apps on-screen or desktop than a mobile device. Android developers can debug...

  • author
    Adam Davidson

    Companies that Use Node JS in Production

    Despite of being arrived late on the scene, NodeJS is dominating the entire application development scenario with its great optimal features. This is a well-kept secret for the seamless distribution of their services that some of the top companies that use NodeJS for its web-based applications today. From concurrence to being a lightweight runtime,...

  • author
    Lucas White

    How to Use callBack With setState in React

    Today we are going to explore the callback function in setState and get to know about how we can use it.  First of all, I’d like to explain the ‘callBack’ and ‘setState’. callBack functions is a function that is passed as an argument to another function, to be “called back” at a later time....

image

About The Author

Thomas is a front-end Mobile Engineer with experience working for startups and multinationals across the world. As a certified Scrum Master, Thomas has worked with a team of 10 engineers located in three different countries for eBay.

Try our One-Week Risk Free Trial for Hiring a Coder

Know more Hire a Coder