Building a Flutter Crypto Trading App: Part 1
Francis
Posted on November 6, 2022
Introduction
Flutter enables us to build beautiful apps with great performance. One of the great thing about flutter is that it has cool packages and plugins that can enable us build most features we need in our applications hence eliminating the time needed to create the features from scratch.
In this article, we will be building a flutter trading app using CoinGecko API and Syncfusion in flutter.
Pre-requisite
In order to follow along with this article, you must have installed flutter and android studio and also setup the necessary environment which will enable you to run your flutter app. Also, you can use any IDE you like but I prefer VS code.
About the app
This is a flutter app that display trading candlesticks in charts which is siimilar to most trading platforms such as Metatrader 4, as well as other trading and exchange platforms etc. This article is the first in the series of two articles that we will address building a trading app in flutter. We will be using CoinGecko API to get our real time trading data and using syncfusion to display this data in charts in our flutter app.
Note: This article is for instructional purpose and will be refactored in later tutorials. No state management package was used for this tutorial.
Building the app
- Let's name the app
cnj_charts
. In your command prompt run
flutter create cnj_charts
- In the
pubspec.yaml
file add the following packages
http: ^0.13.5
syncfusion_flutter_charts: ^19.1.54
- Checking out the API data, we will observe that it returns a list of list of data i.e a List of data containing list of chart data as seen below.
[
[
1667651400000, // time in Epoch milliseconds
21379.78, // open
21379.78, // high
21329.8, // low
21329.8 // close
],
[
1667653200000,
21304.06,
21328.06,
21299.17,
21299.17
]
]
Our folder structure is
models, screens, services, widget
In the models folder, we create
candle.dart
class CandleItem {
final num? time;
final num? open;
final num? high;
final num? low;
final num? close;
CandleItem({
this.time,
this.open,
this.high,
this.low,
this.close,
});
factory CandleItem.fromJson(Map<String, dynamic> json) {
return CandleItem(
time: json['time'],
open: json['open'],
high: json['high'],
low: json['low'],
close: json['close'],
);
}
}
var candleList = [];
List<CandleItem> wickList = [];
- Create api.dart file in the services folder
we create a function fetchData(String cryptocurrency)
with argument cryptocurrency since we will be fetching different coin data from the API. on fetching this, we decode the response and pass the values of the decoded response which is a list to an empty list we created values
. Then for each values[index], we add it to our candleList. Then create a list of name
which holds the name of the data we received from our api and finally we use the Map.fromIterables function to map the name and candlesList
this is the sample data we obtain
{
"time": "1667651400000",
"open": "21379.78",
"high": "21379.78",
"low": "21329.8",
"close": "21329.8"
}
Then we add the json data to our wickList.
import 'dart:convert';
import 'package:cnj_charts/models/candles.dart';
import 'package:http/http.dart' as http;
Future<dynamic> fetchData(String cryptocurrency) async {
var url =
"https://api.coingecko.com/api/v3/coins/$cryptocurrency/ohlc?vs_currency=usd&days=1";
final uri = Uri.parse(url);
final response = await http.get(uri);
if (response.statusCode == 200) {
List<dynamic> values = [];
values = json.decode(response.body);
if (values.isNotEmpty) {
for (int i = 0; i < values.length; i++) {
if (values[i] != null) {
var map = values[i];
candleList.add(map);
List<String> name = ["time", "open", "high", "low", "close"];
final json = Map<String, dynamic>.fromIterables(
name,
map,
);
wickList.add(CandleItem.fromJson(json));
}
}
return wickList;
} else {
throw Exception('Failed to load data');
}
}
}
- In our
home.dart
file, we create a scaffold with body isSfCartesianChart()
widget which returns the chart. Then in the initState, enable trackball behaviour and zoompan behaviour
void initState() {
fetchData(cryptocurrency);
_trackballBehavior = TrackballBehavior(
enable: true,
activationMode: ActivationMode.doubleTap,
);
_zoomPanBehavior = ZoomPanBehavior(
enablePinching: true,
enableDoubleTapZooming: true,
enableSelectionZooming: true,
zoomMode: ZoomMode.x,
enablePanning: true,
);
super.initState();
}
- The values of the series is then passed to the corresponding data from the wick List.
series: <CandleSeries>[
CandleSeries<CandleItem, DateTime>(
dataSource: wickList,
xValueMapper: (CandleItem wick, _) {
int x = int.parse(wick.time.toString());
DateTime time =
DateTime.fromMillisecondsSinceEpoch(x);
return time;
},
lowValueMapper: (CandleItem wick, _) => wick.low,
highValueMapper: (CandleItem wick, _) => wick.high,
openValueMapper: (CandleItem wick, _) => wick.open,
closeValueMapper: (CandleItem wick, _) =>
wick.close,
)
],
- We then define the x and y axis.
primaryXAxis: DateTimeAxis(
visibleMinimum: DateTime.fromMillisecondsSinceEpoch(
int.parse(wickList[0].time.toString())),
visibleMaximum: DateTime.fromMillisecondsSinceEpoch(
int.parse(wickList[wickList.length - 1]
.time
.toString())),
intervalType: DateTimeIntervalType.auto,
minorGridLines: const MinorGridLines(width: 0),
// dateFormat: DateFormat.MMM(),
majorGridLines: const MajorGridLines(width: 0.5),
edgeLabelPlacement: EdgeLabelPlacement.shift),
primaryYAxis: NumericAxis(
opposedPosition: true,
majorGridLines: const MajorGridLines(width: 0),
enableAutoIntervalOnZooming: true,
// interval: 59,
),
The complete code for the home screen
import 'package:cnj_charts/models/candles.dart';
import 'package:cnj_charts/services/api.dart';
import 'package:cnj_charts/widget/drawer.dart';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
String cryptocurrency = 'bitcoin';
late TrackballBehavior _trackballBehavior;
late ZoomPanBehavior _zoomPanBehavior;
@override
void initState() {
fetchData(cryptocurrency);
_trackballBehavior = TrackballBehavior(
enable: true,
activationMode: ActivationMode.doubleTap,
);
_zoomPanBehavior = ZoomPanBehavior(
enablePinching: true,
enableDoubleTapZooming: true,
enableSelectionZooming: true,
zoomMode: ZoomMode.x,
enablePanning: true,
);
super.initState();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
drawer: const CryptoDrawer(),
appBar: AppBar(
centerTitle: true,
title: const Text('CodenJobs charts'),
backgroundColor: Colors.black,
),
body: FutureBuilder(
future: fetchData(cryptocurrency),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const Center(
child: CircularProgressIndicator(
color: Colors.black,
),
);
default:
if (snapshot.hasData) {
return Container(
color: Colors.white,
child: SfCartesianChart(
enableAxisAnimation: true,
plotAreaBorderWidth: 1,
zoomPanBehavior: _zoomPanBehavior,
title: ChartTitle(
text: 'BTC/USD',
textStyle:
const TextStyle(fontWeight: FontWeight.bold),
),
trackballBehavior: _trackballBehavior,
series: <CandleSeries>[
CandleSeries<CandleItem, DateTime>(
dataSource: wickList,
xValueMapper: (CandleItem wick, _) {
int x = int.parse(wick.time.toString());
DateTime time =
DateTime.fromMillisecondsSinceEpoch(x);
return time;
},
lowValueMapper: (CandleItem wick, _) => wick.low,
highValueMapper: (CandleItem wick, _) => wick.high,
openValueMapper: (CandleItem wick, _) => wick.open,
closeValueMapper: (CandleItem wick, _) =>
wick.close,
)
],
primaryXAxis: DateTimeAxis(
visibleMinimum: DateTime.fromMillisecondsSinceEpoch(
int.parse(wickList[0].time.toString())),
visibleMaximum: DateTime.fromMillisecondsSinceEpoch(
int.parse(wickList[wickList.length - 1]
.time
.toString())),
intervalType: DateTimeIntervalType.auto,
minorGridLines: const MinorGridLines(width: 0),
// dateFormat: DateFormat.MMM(),
majorGridLines: const MajorGridLines(width: 0.5),
edgeLabelPlacement: EdgeLabelPlacement.shift),
primaryYAxis: NumericAxis(
opposedPosition: true,
majorGridLines: const MajorGridLines(width: 0),
enableAutoIntervalOnZooming: true,
// interval: 59,
),
),
);
} else {
return Center(
child: ElevatedButton(
onPressed: () {
setState(() {
snapshot.hasData;
});
},
child: const Text("Oops!!! no internet connection"),
),
);
}
}
}),
),
);
}
}
- Also, we create our
drawer.dart
file in widget folder called Crypto drawer and which contains four listTiles which we can use to view the chart for each of the cryptocurrencies bitcoin, ethereum, solana and cardano.
import 'package:cnj_charts/models/candles.dart';
import 'package:cnj_charts/screens/cardano.dart';
import 'package:cnj_charts/screens/ethereum.dart';
import 'package:cnj_charts/screens/home.dart';
import 'package:cnj_charts/screens/solana.dart';
import 'package:flutter/material.dart';
class CryptoDrawer extends StatelessWidget {
const CryptoDrawer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
width: 200,
backgroundColor: Colors.black,
child: ListView(
children: [
ListTile(
title: const Text(
"BITCOIN/USD",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
onTap: () {
wickList = [];
Navigator.push(
context,
MaterialPageRoute(
builder: (c) => const Home(),
),
);
},
),
ListTile(
title: const Text(
"SOLANA/USD",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
onTap: () {
wickList = [];
Navigator.push(
context,
MaterialPageRoute(
builder: (c) => const Solana(),
),
);
},
),
ListTile(
title: const Text(
"ETHEREUM/USD",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
onTap: () {
wickList = [];
Navigator.push(
context,
MaterialPageRoute(
builder: (c) => const Ethereum(),
),
);
},
),
ListTile(
title: const Text(
"CARDANO/USD",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
onTap: () {
wickList = [];
Navigator.push(
context,
MaterialPageRoute(
builder: (c) => const Cardano(),
),
);
},
),
],
),
);
}
}
In the main.dart file add home screen as your home
import 'package:cnj_charts/screens/home.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Home());
}
}
In case you want to build an apk for using the app in release mode you have to include internet and network permissions in your AndroidManifest.xml file
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Finally, we have our app up and running.
Final note: We can refactor the code to return any coin chart data instead of returning screens of specific coins in the drawer. Also, incorporating a state management package will enable us better manage the state of the app especially if we plan on moving it to production. The Date could be better formatted using the intl
package
Download code: Checkout the original article below to get GitHub link
Link to original article at CodenJobs
To know more about codenjobs click [CodenJobs]((https://www.codenjobs.com)
Want to Connect?
This article is also published at codenjobs.com .
Posted on November 6, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.