• THMOD02: โปรดระมัดระวังอย่าให้ข้อมูลที่เป็นส่วนตัวแก่สมาชิกใดๆ หลีกเลี่ยงการรับชำระเงินผ่านธนาคารไทย หลีกเลี่ยงการสนทนาจากผู้ที่ต้องการนัดพบเจอ! เราไม่สนับสนุนดีลที่ไม่ปลอดภัย! หากคุณมีหลักฐานสามารถรายงานมายังเจ้าหน้าที่ เราจะดำเนินการแบน User อย่างถาวร! เราพยายามปกป้องความเสี่ยงของสมาชิกที่ได้รับการยืนยันจากเรา

ดึงข้อมูล Bitkub Crypto API มาแสดงบน Flutter App (Flutter Dev)

APIPAYEZ

APIPAYEZ

สมาชิกใหม่
สมาชิกรุ่นแรก
สมาชิก
LV
0
 
คะแนนธุระกิจ
0
เครดิต
$0.02

เนื้อหาบทความนี้ แบ่งออกเป็น​

  • ทำความรู้จัก Bitkub
  • ทดสอบเรียก Bitkub RESTful API ด้วย Postman tool
  • เขียน Flutter App ต่อเรียกข้อมูล Bitkub API

ทำความรู้จัก Bitkub

Bitkub ก่อตั้งเมื่อเดือนกุมภาพันธ์ พ.ศ. 2561 และเป็น Exchange Platform รุ่นใหม่สำหรับการซื้อขาย Cryptocurrency และสินทรัพย์ Digital โดยให้บริการเหนือระดับแก่บุคคลทั่วไปให้สามารถซื้อ, ขายและเก็บ Cryptocurrency ได้ตามต้องการ บริษัทเราได้จดทะเบียนอย่างถูกต้องตามกฎหมายด้วยทุนจดทะเบียน 80 ล้านบาท และมีที่ตั้งสำนักงานอยู่ในกรุงเทพฯ ประเทศไทย

Bitkub มี Public API ให้เรียกใช้งาน​

Bitkub มีบริการ API ให้เหล่านักพัฒนาได้ลองเรียกใช้งาน จะเป็นแบบ RESful API สามารถเข้าไปดูได้ที่ https://www.bitkub.com/publicapi แบ่งตัว API 2 กลุ่ม คือ
  1. แบบ Non-Secure Endpoint API คือ Public API ที่ไม่ต้องใช้ authentication และเป็น http method แบบ GET ตัวอย่างข้อมูล เช่น
  1. แบบ Secure Endpoint API คือ Secure API ที่ต้องมีการส่ง Server Time ,API Key, API Secret โดยต้องมีการ encode request payload ด้วย Function Hash อย่าง HMAC SHA-256 ในส่วของ Request Body ก่อนการเรียกใช้งาน Secure API ตรงส่วนนี้ ตัวอย่างข้อมูล เช่น

ทดสอบเรียก Public API ผ่าน REST Client Tool (Postman)​

ลองเรียก API endpoint ที่เป็นแบบ Non-secure (GET) เรียกข้อมูล crypto symbols ของแต่ละเหรียญ

1*TOZwMAS3rSxrc979krZLGg.png

มาเริ่มเขียน Flutter App ให้เรียก Bitkub API กัน​

  1. สมัครสมาชิก bitkub.com ทำการยืนยันการสมัครสมาชิกให้เรียบร้อย
  2. เข้าไปสร้าง bitkub public APIกำหนด permission role ตามที่ต้องการ หรือจะใส่ค่า default ก็ได้ เพราะเราจะ read data อย่างเดียว
1*SRYQsecAv73T0ItFCx-LZQ.png


api authenticate management
3. สร้าง Flutter Project กำหนดโครงสร้าง Project ตามข้างล่างนี้

1*CIuHWCVB8cmY-cuFpq1xmQ.png


3.1 เพิ่ม dependencies ในไฟล์ pubspec.yaml

YAML:
name: medium_bitkub
description: Example App Call RESTful API from Bitkub

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2

  # https://pub.dev/packages/http#-installing-tab-
  http: ^0.12.0+2

  # https://pub.dev/packages/json_serializable#-installing-tab-
  json_serializable: ^3.2.3

  # https://pub.dev/packages/crypto#-installing-tab-
  crypto: ^2.1.3

dev_dependencies:
  flutter_test:
    sdk: flutter


# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - config.json

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware.

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  fonts:
    - family: Varela Round
      fonts:
        - asset: fonts/VarelaRound-Regular.ttf
          #- asset: fonts/Schyler-Italic.ttf
          #style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

  • http: ^0.12.0+2 : สำหรับใช้เป็น lib จัดการเรื่อง http request ,response ใน code การเรียก API
  • json_serializable: ^3.2.3 : parser JSON Data to Object Class
  • crypto: ^2.1.3 : เพื่อใช้ function HMAC
3.2 สร้าง Flutter widget โดยส่วนหลัก จะมี 3 pages

  • BitkubMenu เพื่อใช้เป็น widget เมนูที่จะมี button 2 buttons
Code:
import 'package:flutter/material.dart';
import 'package:medium_bitkub/pages/non_secure_api.dart';
import 'package:medium_bitkub/pages/secure_api.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Call Bitkub RESTful API',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        fontFamily: 'Varela Round'
      ),
      home: BitkubMenu(),
    );
  }
}

class BitkubMenu extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = new TextStyle(
            fontSize: 20.0, color: Colors.white,
            fontWeight: FontWeight.bold,
    );

    return Scaffold(
      appBar: AppBar(
        title: Text("Bitkub Api List"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new RaisedButton(
              child: new Text("Non Secure Api (GET)",style: textStyle),
              color: Colors.blue,
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => NonSecureApiPage()),
              ),
            ),
            new RaisedButton(
              child: new Text("Secure Api (POST)",style: textStyle),
              color: Colors.green,
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SecureApiPage()),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

1*HQrfpL9kmu4449c3i7z8pg.png

  • NonSecureApiPage เพื่อใช้เป็น Page ดึง Non Secure API ในที่นี้คือดึง symbol ของ crypto มาแสดง (API ไม่ต้อง authen)
Code:
import 'package:flutter/material.dart';
import '../dto/symbol.dart';
import '../services/bitkub_service.dart';

class NonSecureApiPage extends StatefulWidget {
     @override
     _NonSecureApiState createState() => _NonSecureApiState();
}

class _NonSecureApiState extends State<NonSecureApiPage> {


     Future fetchCoins() async {
          print('fetchCoins');
          return await BitkubService().getSymbols();
     }

     @override
     Widget build(BuildContext context) {
          return Scaffold(
               appBar: AppBar(
                    title: Text("Non Secure API"),
               ),
               body: FutureBuilder(
                    future: fetchCoins(),
                    builder: (BuildContext  contex, AsyncSnapshot tickerSnap) {
                         switch (tickerSnap.connectionState) {
                              case ConnectionState.none:
                                   return new Text('Input a URL to start');
                                   break;
                              case ConnectionState.waiting:
                                   return new Center(child: new CircularProgressIndicator());
                                   break;
                              case ConnectionState.active:
                                   return new Text('');
                                   break;
                              case ConnectionState.done:
                                   if (tickerSnap.hasError) {
                                        return new Text('${tickerSnap.error}', style: TextStyle(color: Colors.red),);
                                   } else {
                                        return ListView.builder(
                                             itemCount: (tickerSnap.data == null) ? 0 : tickerSnap.data.length,
                                             itemBuilder: (contex, index) {
                                                  Ticker ticker = tickerSnap.data[index];
                                                  return Column(
                                                       children: <Widget>[
                                                            ListTile(
                                                                 leading: Icon(Icons.monetization_on),
                                                                 title: Text(ticker.key),
                                                                 subtitle: Text(ticker.baseVolume.toString()),
                                                            ),
                                                       ],
                                                  );
                                             });
                                   }
                                   break;
                              default:
                                   return Container();
                         }
                    },
               ),
          );
     }

}

  • SecureApiPage เพื่อใช้เป็น Page ดึง Secure API ในที่นี้คือดึง crypto balance ของ crypto มาแสดง (API จำเป็นต้องส่งข้อมูล authentication แนบไปด้วย)
Code:
import 'package:flutter/material.dart';

import '../dto/balance.dart';
import '../services/bitkub_service.dart';

class SecureApiPage extends StatefulWidget{
     @override
     _NonSecureApiState createState() => _NonSecureApiState();
}

class _NonSecureApiState extends State<SecureApiPage>{

     Future fetchCoinBalances() async {
          return await BitkubService().getCoinsBalance();
     }

     @override
     Widget build(BuildContext context) {
          return Scaffold(
               appBar: AppBar(
                    title: Text("Secure API"),
               ),
               body: FutureBuilder(
                    future: fetchCoinBalances(),
                    builder: (BuildContext contex, AsyncSnapshot balanceSnap) {
                         switch (balanceSnap.connectionState) {
                              case ConnectionState.none:
                                   return new Text('Input a URL to start');
                              case ConnectionState.waiting:
                                   return new Center(child: new CircularProgressIndicator());
                              case ConnectionState.active:
                                   return new Text('');
                              case ConnectionState.done:
                                   if (balanceSnap.hasError) {
                                        return new Text(
                                             '${balanceSnap.error}',
                                             style: TextStyle(color: Colors.red),
                                        );
                                   } else {
                                        return ListView.builder(
                                             itemCount: (balanceSnap.data == null) ? 0 : balanceSnap.data.length,
                                             itemBuilder: (contex, index) {
                                                  Balance ticker = balanceSnap.data[index];
                                                  return Column(
                                                       children: <Widget>[
                                                            ListTile(
                                                                 leading: Icon(Icons.monetization_on),
                                                                 title: Text(ticker.key),
                                                                 subtitle: Text(ticker.available.toString()),
                                                            ),
                                                       ],
                                                  );
                                             },
                                        );
                                   }
                                   break;
                              default:
                                   return Container();
                         }
                    },
               ),
          );
     }

}

3.3 สร้าง dto (data transfer object) เอาไว้ mapping json to object class 3 files
  • balance.dart
Code:
class Balance {
  final String key;
  final double available;
  final double reserved;

  Balance({this.key, this.available, this.reserved});

  factory Balance.fromJson(String balanceKey, Map<String, dynamic> json){
    return Balance(
      key: balanceKey,
      available: json['available'].toDouble(),
      reserved: json['reserved'].toDouble(),
    );
  }
}

  • symbol.dart
Code:
class Ticker {

  final String key;
  final int id;
  final double last;
  final double lowestAsk;
  final double highestBid;
  final double percentChange;
  final double baseVolume;
  final double quoteVolume;
  final double isFrozen;
  final double high24hr;
  final double low24hr;

  Ticker(
      {this.key,
        this.id,
      this.last,
      this.lowestAsk,
      this.highestBid,
      this.percentChange,
      this.baseVolume,
      this.quoteVolume,
      this.isFrozen,
      this.high24hr,
      this.low24hr});

  factory Ticker.fromJson(String tickerKey,Map<String, dynamic> json) {
    return Ticker(
      key :tickerKey,
      id: json['id'],
      last: json['last'].toDouble(),
      lowestAsk: json['lowestAsk'].toDouble(),
      highestBid: json['highestBid'].toDouble(),
      percentChange: json['percentChange'].toDouble(),
      baseVolume: json['baseVolume'].toDouble(),
      quoteVolume: json['quoteVolume'].toDouble(),
      isFrozen: json['isFrozen'].toDouble(),
      high24hr: json['high24hr'].toDouble(),
      low24hr: json['low24hr'].toDouble(),
    );
  }
}

  • config.dart
Code:
class Config {
  final String bitkubEndpoint;
  final String bitkubApikey;
  final String bitkubApiSecret;

  Config({this.bitkubEndpoint, this.bitkubApikey, this.bitkubApiSecret});

  factory Config.fromJson(Map<String, dynamic> json) {
    return Config(
      bitkubEndpoint: json['bitkub.endpoint'],
      bitkubApikey: json['bitkub.apikey'],
      bitkubApiSecret: json['bitkub.apisecret'],
    );
  }
}

3.4 สร้าง service class เพื่อใช้เป็น service handle http client ต่อ Bitkub API ให้ชื่อว่า bitkub_service.dart

Code:
import 'dart:convert' as convert;
import 'dart:convert' show utf8;
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;

import '../dto/symbol.dart';
import '../dto/balance.dart';
import '../dto/config.dart';

import '../app_config.dart';
import '../util/coin_security.dart';


class BitkubService{

     Map<String, String> headers = {
          "content-type": "application/json",
          "Accept": "application/json",
     };

     Future<String> getServerTime() async {
          Config _config = await ApplicationConfig().loadAsset();
          final response = await http.get(_config.bitkubEndpoint + "/api/servertime", headers: headers);
          if (response.statusCode == 200) {
               return response.body;
          } else {
               throw Exception('Failed to load Server Time');
          }
     }

     Future<List<Balance>> getCoinsBalance() async {
          Config _config = await ApplicationConfig().loadAsset();
          String timestamp = await this.getServerTime();
          print('timestamp ::==$timestamp');
          var payload = {'ts': timestamp};
          print('payload ::==${convert.jsonEncode(payload)}');
          headers['X-BTK-APIKEY'] = _config.bitkubApikey;
          String hamcSing = new CoinSecurity().coinHmacSign(payload, _config.bitkubApiSecret);
          //print('payloadHmac ::==${convert.jsonEncode(hamcSing)}');
          payload['sig'] = hamcSing;
          String payLoadJson = convert.jsonEncode(payload);
          print('payLoadJson ::==$payLoadJson');
          final response =
          await http.post(_config.bitkubEndpoint + '/api/market/balances', headers: headers, body: payLoadJson);
          debugPrint('response headers ::==${convert.jsonEncode(response.headers)}',wrapWidth: 1024);
          debugPrint('response.statusCode :: ${response.statusCode}');
          if (response.statusCode == 200) {
               List<Balance> balances = new List();
               try {
                    String _body = convert.utf8.decode(response.bodyBytes);
                    print('response  _body::==$_body');
                    Map<String, dynamic> mapBodyRes = convert.jsonDecode(_body);
                    if(mapBodyRes['error'] == 1){
                         throw new Exception('error :: ${mapBodyRes['error']}');
                    }else{
                         Map<String,dynamic> mapResult = mapBodyRes['result'];
                         mapResult.forEach((balanceName, balanceDetail) {
                              balances.add(Balance.fromJson(balanceName, balanceDetail));
                         });
                         return balances;
                    }
               } catch (e) {
                    print('e::==$e');
                    throw e;
               }
          } else {
               throw Exception('Failed to load fetchCoinBalances');
          }
     }

     Future<List<Ticker>> getSymbols() async {
          Config _config = await ApplicationConfig().loadAsset();
          print('_config  ::==${_config.bitkubEndpoint}');
          final response = await http.get(_config.bitkubEndpoint + "/api/market/ticker");
          print('headers ::==${convert.jsonEncode(response.headers)}');
          if (response.statusCode == 200) {
               List<Ticker> tickers = new List();
               String _body = utf8.decode(response.bodyBytes);
               print('_body ::==   $_body');
               try {
                    Map<String, dynamic> mapTickets = convert.jsonDecode(_body);
                    mapTickets.forEach((coinName, coinDetail) {
                         //print('coinName :: $coinName');
                         //print('coinDetail :: $coinDetail');
                         tickers.add(Ticker.fromJson(coinName, coinDetail));
                    });
                    return tickers;
               } catch (e) {
                    print('e::==$e');
                    throw e;
               }
          } else {
               throw Exception('Failed to load Coins');
          }
     }
}

3.5 สร้าง coin_security.dart ใช้สำหรับ hash HMAC authen

Code:
import 'package:crypto/crypto.dart';
import 'dart:convert' as convert;
class CoinSecurity {

  String coinHmacSign(var timestamp, String apiSecret) {
    try {
      var timestampEncode = convert.utf8.encode(convert.jsonEncode(timestamp));
      var secretEncode = convert.utf8.encode(apiSecret);

      var hmacSha256 = new Hmac(sha256, secretEncode); // HMAC-SHA256
      var digest = hmacSha256.convert(timestampEncode);
      return digest.toString();
    } catch (e) {
      print('$e');
      throw e;
    }
  }
}

3.6 app_config.dart ใช้เป็น config handle เรื่อง read config file มาใช้ใน project

Code:
import 'dart:convert' as convert;
import 'package:flutter/services.dart' show rootBundle;
import 'dto/config.dart';


class ApplicationConfig{
     Future<Config> loadAsset() async{
          String _json =   await rootBundle.loadString('assets/config.json');
          //print('assets/config json::==$_json');
          return Config.fromJson(convert.jsonDecode(_json));
     }

}

4. อธิบายส่วนการเรียก Bitkub API
  • Non Secure API ส่วนนี้ไม่ยุ่งยากอะไรเพราะมองเป็น API ที่ไม่ต้องใส่ authenticate secure เรียกผ่าน method GET ได้เลย ขั้นตอนส่วนนี้จะอยู่ใน function getSymbols()

1*Ns9NO2u4UpLAz54yHjquxg.png


  • Secure API ส่วนนี้จะมีความยุ่งยากนิดนึง เพราะจำเป็นต้องแนบ authen ไปด้วย
    1. เรียก function getServerTime() เก็บค่า time server ของ API ไว้ก่อน (ใช้ในขั้นตอนถัดไป)
    2. เพิ่ม header “X-BTK-APIKEY” ใส่ value เป็น bitkub api key
    3. เข้ากระบวนการ hash HMAC โดยใช้ข้อมูล servertime (ได้จาก 1.) จะถูกสร้างเป็น request body ที่ชื่อว่า “ts” พร้อมใส่ api secret ผลลัพธ์ที่ได้จะเป็น HASH String 1 ชุด เก็บไว้เป็น request body ที่ชื่อว่า “sig”
    4. ส่งข้อมูล request เหล่านี้ไปแบบ method POST จากนั้นจะได้ response กับมาเป็นข้อมูล market balance ของ crypto ณ ปัจจุบัน code ส่วนนี้จะอยู่ใน function getCoinsBalance()

1*NbAPKJEDIDELFpEHk4OGfw.png


4.1 API Error Code ที่จะคอยบอกเราได้ว่า call API เกิด Error ทางฝั่ง server ว่าอย่างไร มี Error Code จาก server ที่คอยบอกเราได้

1*XHe9ylGZIdB-mhJbrORu1A.png


error code เพิ่มเติม
สำหรับเพื่อน ๆ คนใดอยากได้ sourcecode เพื่อลองเอาไป run ทดสอบเอง ผมจะแปะลิ้ง github ไว้ที่ตรงนี้ bitkubapi-flutter ก่อน run แก้ให้ไปแก้ config.json ตรงส่วน api key , api secret ก่อนรันด้วยนะจร้า
 
แก้ไขล่าสุด:
Top