How Flutter will make me Rich $$

Pedro Eugénio
7 min readFeb 16, 2021

--

Since the beginning of my journey, thinking about money and personal finance, that the idea of money sitting on a bank account doing nothing, bothered me. Being a student I hadn’t much money lying around, therefore it never had time to get dust. With that in mind, I made a promise to myself, when my working life began also my investing journey follows along.

I started investing in the stock market about one and a half years ago and with that, my journey through the personal finance world and the basic principles needed to start investing into the stock market on my own.

Rapidly decided that I will be putting my effort into dividend investing and creating an asset to generating passive income. I always loved the possibility of money generating more money, even when sleeping. So with that in mind, I started to look for the best stocks to buy with an attractive dividend yield to reach my goal.

As an electrical engineer, I am always interested in programming, so it seems a great opportunity to learn a new language/framework to build a simple web app to track and search for new stocks to reach my goal.

I followed Flutter since its launch. Being able to create cross-platform apps learning only one tool and one programming language, seduced me to try it. I never built an app before and these were perfect incentives to change that! This article won’t be very deep into the technicalities, instead, the main purpose is to show the tools that I used to build a simple app with no experience with Flutter.

Disclaimer

I’m not an expert in Flutter, and I’m starting now to interact with this world of web apps and using third-party APIs. My background was C/C++ very oriented to low-level stuff. The challenge was this: Learn new things, that possibly can help me with something. Try something new and learn with the process.

Set up environment

To set up the environment, I used flutter documentation. It is pretty straightforward, and I won’t go into detail about this process. With everything is done correctly, you should see something familiar, the famous Flutter counter button!

Let’s get dirty

All the code will be at the end of this article. Follow along.

Draw something to work with

What is the simplest thing that we can do to search for stock information?

  • Input Text Field
  • Button to activate the search
  • Card to display the result

The following GIF is the final result of this experiment, and I will break down the technicalities over the next sections.

HTTP Request

After getting a basic UI to handle input search and display the results, I needed to request Alphavantage API (https://www.alphavantage.co) so that I was able to get real data from the stocks inserted into the search bar. To be able to make these HTTP requests to the API, I used a helpful package for making HTTP requests with Dart.

So with all that done, I was able to get a Future response, from an asynchronous function, to handle the response from the API with the given stock symbol. Using HTTP requests in your flutter app is pretty straightforward, and the links in the Resources section will get you there too.

State Management

With the data available, a new problem surges. How to manage the state of my app, and how can I pass data from one widget to another? The answer to my questions and the one that I chose was the Provider package. I know that are more tools to work with, but Provider package seems to be a good challenge and a tool that could be useful in other projects, so I decided to learn to use it.

I simply follow the tutorial on state management in the Resources section and adapt to my needs. It is powerful and easy to use, definitely recommend you to check it out, will probably resolve your problems with state management and shared information through widgets.

Conclusions

This project is far to be complete and probably it will need to be refactored. The code was constantly changing with all the experiments to get what I wanted, and probably didn’t followed all the best practices of Flutter and Dart. The main purpose was to build something that works and learn how to make it.

The code that I will show you next isn’t pretty or clean, but I wanted to share it as raw as it be, everyone started with something and you shouldn’t be afraid to share with others! Sharing knowledge and experiences makes us grow, learn and improve what we already knew.

Probably, in a few months, I will be laughing with this implementation, but still, you should start with something! I will be working on a better and cleaner version of this, but for now, I challenge you to make something, to learn a new skill, and begin a new project!

Code

stock.dart

class Stock {
final String symbol;
final String name;
final double price;
final double analystTargetPrice;
final double dividendYield;
final double dividendPerShare;
final double payoutRatio;
final DateTime dividendDate;
final DateTime exDividend;
Stock({this.symbol, this.name, this.price, this.analystTargetPrice, this.dividendYield, this.dividendPerShare, this.payoutRatio, this.dividendDate, this.exDividend}); factory Stock.fromJSON(Map<String, dynamic> json)
{
return Stock(
symbol: json['Symbol'] as String,
name: json['Name'] as String,
price: double.parse(json['50DayMovingAverage']),
analystTargetPrice: double.parse(json['AnalystTargetPrice']),
dividendYield: double.parse(json['DividendYield']),
dividendPerShare: double.parse(json['DividendPerShare']),
payoutRatio: double.parse(json['PayoutRatio']),
dividendDate: DateTime.parse(json['DividendDate']),
exDividend: DateTime.parse(json['ExDividendDate']),
);
}
}

stock_card.dart

import 'package:dividend_calculator_flutter/modules/stock_card_state.dart';
import 'package:dividend_calculator_flutter/modules/stock.dart';
import 'package:flip_card/flip_card.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class StockCard extends StatefulWidget {
@override
_StockCardState createState() => _StockCardState();
}

class _StockCardState extends State<StockCard> {
final double cardWidth = 400;
final double cardHeight = 300;
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Consumer<StockCardState>(
builder: (context, schedule, _) => Container(
child: Container(
width: (screenWidth < 500) ? screenWidth * 0.9 : cardWidth,
height: cardHeight,
child: FlipCard(
direction: FlipDirection.HORIZONTAL,
front: createFrontCardStock(schedule.stock, cardWidth, cardHeight),
back: createBackCardStock(schedule.stock, cardWidth, cardHeight),
),
),
),
);
}
}

Widget createFrontCardStock(Stock stockData, double width, double height) {
if (stockData != null) {
return Container(
/* width: width,
height: height, */
child: Card(
//shadowColor: Colors.grey[50],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
color: Colors.amber[200],
elevation: 4.0,
//margin: EdgeInsets.all(100.0),
child: InkWell(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(0, 12.0, 0, 12.0),
child: Text(
stockData.symbol,
style: TextStyle(
fontSize: 40.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Text(
stockData.name,
style: TextStyle(
fontSize: 30.0,
),
),
],
),
),
),
);
} else {
return Text("");
}
}

Widget createBackCardStock(Stock stockData, double width, double height) {
if (stockData != null) {
return Container(
/* width: width,
height: height, */
child: Card(
//shadowColor: Colors.grey[50],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
color: Colors.amber[200],
elevation: 12.0,
//margin: EdgeInsets.all(100.0),
child: InkWell(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(0, 12.0, 0, 12.0),
child: Text(
stockData.symbol,
style: TextStyle(
fontSize: 40.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 12.0),
child: Text(
stockData.name,
style: TextStyle(
fontSize: 30.0,
),
),
),
Text(
'Dividend Yield: ' +
(stockData.dividendYield * 100).toStringAsFixed(2) +
'%',
style: TextStyle(
fontSize: 25.0,
),
),
Text(
'Ex-Dividend: ${stockData.exDividend.day}-${stockData.exDividend.month}-${stockData.exDividend.year}',
style: TextStyle(
fontSize: 25.0,
),
),
],
),
),
),
);
} else {
return Text("");
}
}

stock_card_state.dart

import 'package:dividend_calculator_flutter/modules/stock.dart';
import 'package:flutter/foundation.dart';

class StockCardState with ChangeNotifier {
String _stockSymbol = "";
Stock _stock;

Stock get stock => _stock;

String get stockSymbol => _stockSymbol;

set stockSymbol(String newString) {
_stockSymbol = newString;
notifyListeners();
}

set stock(Stock newStock) {
_stock = newStock;
notifyListeners();
}
}

stock_api.dart

import 'package:dividend_calculator_flutter/modules/stock.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;

class StockApi {
String stockSymbol;

StockApi({this.stockSymbol});

Future<Stock> getStockData() async {
var response = await http.get(
'<https://www.alphavantage.co/query?function=OVERVIEW&symbol=>' +
stockSymbol +
'&apikey=YOURKEY');
if (response.statusCode == 200) {
return compute(parseApiStockResponse, response.body);
} else {
print('Request failed with status: ${response.statusCode}.');
return null;
}
}

Stock parseApiStockResponse(String responseBody) {
var jsonResponse = convert.jsonDecode(responseBody);
print(jsonResponse);
return Stock.fromJSON(jsonResponse);
}
}

search_bar.dart

import 'package:dividend_calculator_flutter/modules/stock_api.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'stock_card_state.dart';

class SearchBar extends StatelessWidget {
final inputTextController =
TextEditingController(text: "Insert Stock Symbol");
@override
Widget build(BuildContext context) {
final schedule = Provider.of<StockCardState>(context);

double screenWidth = MediaQuery.of(context).size.width;
return Container(
padding: const EdgeInsets.all(20.0),
margin: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.amber[200],
borderRadius: BorderRadius.circular(12.0),
),
child: Container(
width: (screenWidth < 500) ? screenWidth * 0.9 : screenWidth * 0.4,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 8,
child: TextField(
textCapitalization: TextCapitalization.characters,
autofocus: false,
style: TextStyle(fontSize: 28),
textInputAction: TextInputAction.search,
controller: inputTextController,
onTap: () => {inputTextController.clear()},
onSubmitted: (value) async {
if (inputTextController.value.toString().length > 0 &&
inputTextController.value.toString() !=
schedule.stockSymbol) {
schedule.stockSymbol = inputTextController.value.text;
schedule.stock =
await StockApi(stockSymbol: schedule.stockSymbol)
.getStockData();
}
},
),
),
Expanded(
flex: 2,
child: IconButton(
icon: Icon(Icons.search),
onPressed: () async {
if (inputTextController.value.toString().length > 0 &&
inputTextController.value.toString() !=
schedule.stockSymbol) {
schedule.stockSymbol = inputTextController.value.text;
schedule.stock =
await StockApi(stockSymbol: schedule.stockSymbol)
.getStockData();
}
},
),
)
],
),
),
);
}
}

screen_stock.dart

import 'package:dividend_calculator_flutter/modules/stock_card_state.dart';
import 'package:dividend_calculator_flutter/modules/stock_card.dart';
import 'package:dividend_calculator_flutter/modules/search_bar.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ScreenStock extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => StockCardState(),
child: SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.grey[850],
body: Center(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/chart.jpg"),
fit: BoxFit.cover,
colorFilter:
ColorFilter.mode(Colors.grey, BlendMode.saturation),
),
),
child: Container(
decoration: BoxDecoration(color: Colors.black54),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding:
const EdgeInsets.fromLTRB(6.0, 12.0, 6.0, 12.0),
child: Text(
"Search Stock Information",
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
),
Flexible(
child: SearchBar(),
),
Flexible(
child: StockCard(),
),
],
),
),
),
)),
),
);
}
}

main.dart

import 'package:dividend_calculator_flutter/screens/screen_stock.dart';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Dividend Calculator',
theme: ThemeData(
backgroundColor: Colors.grey[850],
primaryColor: Colors.grey[850],
accentColor: Colors.grey[850],
),
home: ScreenStock(),
),
);
}
}

--

--