本文同步發佈於毛毛的踩坑人生
昨天有點草草斷在一個奇怪的地方
今天來把資料流的最後一段完成
至少要完成到可以看到價格跳動~
完成了這部分
後面的功能都算是相同的技術
不同的邏輯而已
距離一個下單介面越來越近了
之前一直不斷強調
為了達到高效能,我們捨棄 JSON
而選用 Protobuf
所以沒有道理前面那段使用了 Protobuf
而從 Golang -> Flutter
這段反而走回頭路用 JSON
或者字串
先來看一下這個資料最一開始從 Python
是怎麼傳的
def future_quote_callback_v1(self, _, tick: sj.TickFOPv1):
rabbit = self.pika_queue.get(block=True)
try:
rabbit.channel.basic_publish(
exchange=self.exchange,
routing_key=f"future_tick:{tick.code}",
body=mq_pb2.FutureRealTimeTickMessage(
code=tick.code,
date_time=datetime.strftime(tick.datetime, "%Y-%m-%d %H:%M:%S.%f"),
open=tick.open,
underlying_price=tick.underlying_price,
bid_side_total_vol=tick.bid_side_total_vol,
ask_side_total_vol=tick.ask_side_total_vol,
avg_price=tick.avg_price,
close=tick.close,
high=tick.high,
low=tick.low,
amount=tick.amount,
total_amount=tick.total_amount,
volume=tick.volume,
total_volume=tick.total_volume,
tick_type=tick.tick_type,
chg_type=tick.chg_type,
price_chg=tick.price_chg,
pct_chg=tick.pct_chg,
simtrade=tick.simtrade,
).SerializeToString(),
)
except Exception as err:
logger.error("future_quote_callback_v1 error %s", err)
self.pika_queue.put(rabbit)
這是券商資料來的時候
會被使用的 Callback
可以看到是用 mq_pb2.FutureRealTimeTickMessage
往 RabbitMQ
送
而在 Golang 這邊
我們先暫時原封不動往下送
當然是被序列化過的,所以還是 []byte
delivery, bErr := rabbit.BindAndConsume("future_tick:MXFJ3")
if bErr != nil {
logger.Fatalf("could not bind and consume: %v", bErr)
}
for {
d, opened := <-delivery
if !opened {
return
}
priceChan <- d.Body
}
昨天停留在定義了一個在 Flutter
使用的 class
忘記說為什麼不乾脆直接用 Protobuf
的格式就好
因為在 Golang
那邊完全什麼都沒處理就送出來
所以現在會在 Flutter
這邊
把數據調整一下
[]byte
怎麼辦?剛剛提到的 WebSocket
裡面
資料型態都是 []byte
在這邊就會 List<int>
這邊預計還是使用 FutureBuilder
所以先把昨天定義的結構 RealTimeFutureTick
宣告出來
一樣會是一個 Future
// 一樣要初始化為空值
Future<RealTimeFutureTick?> lastTick = Future.value();
void initialWS() {
_channel = IOWebSocketChannel.connect(Uri.parse('ws://127.0.0.1:8080/ws/price'), pingInterval: const Duration(seconds: 1));
_channel!.stream.listen(
(message) {
if (!mounted) {
return;
}
setState(() {
final msg = pb.FutureRealTimeTickMessage.fromBuffer(message as List<int>);
lastTick = Future<RealTimeFutureTick>.value(RealTimeFutureTick.fromProto(msg));
});
},
onDone: () {
if (mounted) {
Future.delayed(const Duration(milliseconds: 1000)).then((value) {
_channel!.sink.close();
initialWS();
});
}
},
onError: (error) {},
);
}
在 Flutter
的 Protobuf
會自帶一個 fromBuffer
的方法
可以看成 Golang
當中的 Unmarshal
在解釋一點點
這邊先 fromBuffer
之後,在用 RealTimeFutureTick
的類方法
fromProto
把 Protobuf
的資料轉換成我們定義的 RealTimeFutureTick
最重要的還是要 setState
讓畫面可以隨著這個資料變化
這邊先借用之前的畫面
只是改一下字
變成 Realtime Price
然後把 FutureBuilder
裡的資料變成 RealTimeFutureTick
到這邊就可以試試看把 APP
跑起來
沒有意外,畫面上的數字應該是一直在跳動的
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Realtime Price'),
actions: [
IconButton(
icon: const Icon(Icons.timer),
onPressed: sendReq,
),
],
),
body: FutureBuilder<RealTimeFutureTick?>(
future: lastTick,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: Text(
snapshot.data!.close.toString(),
style: const TextStyle(fontSize: 20),
),
);
}
return const Center(
child: CircularProgressIndicator(
color: Colors.black,
),
);
},
),
),
);
}
這邊我們順便把漲跌也加進來
也順便讓他有顏色
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
snapshot.data!.close!.toInt().toString(),
style: const TextStyle(fontSize: 50),
),
Text(
'${snapshot.data!.changeType}',
style: const TextStyle(fontSize: 30),
),
Text(
snapshot.data!.priceChg!.abs().toString(),
style: TextStyle(
fontSize: 25,
color: snapshot.data!.priceChg! > 0 ? Colors.red : Colors.green,
),
),
],
),
);
這邊用上了 Row
這個 Widget
就不多做解釋
他是一個橫向排列的容器
畫面就會變成下變這樣
是不是順眼多了
就差兩顆按鈕就可以下單了🤣
還要再額外說明一下,畫面上的 ↗️、↘️
是什麼時後加上去的呢
在 Row
裡面沒有看到啊
答案在昨天新增的 RealTimeFutureTick
裡面新增了 changeType
並且利用 priceChg
來給他箭頭
if (tick.priceChg > 0) {
changeType = '↗️';
} else if (tick.priceChg < 0) {
changeType = '↘️';
} else {
changeType = '';
}
總算到今天這步了
看到資料在跑
有沒有覺得很興奮啊
剩下時間不多了
接下來把交易的功能實作完
把畫面擺一擺,就是一個下單介面~