تا به حال تعجب کرده اید که چگونه می توان لیست قیمت صاف و زنده را برای سهام و رمزنگاری قدرت داد؟

آیا تا به حال لیستی از قیمت های زنده را پیمایش کرده اید – استوک (مانند اپل یا تسلا) ، جفت های رمزنگاری 💰 (مانند BTC/USDT یا ETH/BUSD) – و تعجب کرده اید که چگونه با تمام داده هایی که در آن پرواز می کنند صاف می ماند؟ من هم در مورد آن کنجکاو شده ام. صدها یا حتی هزاران دارایی که در زمان واقعی به روز می شوند ، خواه سرعت ثابت سهام یا نوسانات وحشی Crypto باشد ، مانند یک چالش بزرگ است.
من تصمیم گرفتم که با استفاده از جریان کنه Crypto Binance به عنوان ماسهبازی خود ، در Flutter یک ضربه بزنم. من نمی گویم که من راه حل عالی را ترک کرده ام ، اما در اینجا چگونه سعی کردم به این سؤال پاسخ دهم – امیدوارم که برای شخصی در آنجا مفید باشد. 🙏
مرحله 1: به روزرسانی های انتخابی که برنامه را نمی شکند
چرا این مشکل است: همه چیز را مجدداً انجام ندهید
لیستی از 2000 جفت رمزنگاری را تصور کنید ، و هر قیمتی تیک می زند – مثلاً 10 در ثانیه – همه چیز را مجدداً مورد استفاده قرار می دهد. در Flutter ، یک ساده لوح setState
آیا عملکرد مخزن سریعتر از آنچه می توانید بگویید “تاخیر”. من به به روزرسانی ها نیاز داشتم تا فقط به آنچه تغییر کرده است ضربه بزنم ، نه کل صفحه.
چگونه به آن نزدیک شدم: هدف قرار دادن تغییرات خاص
اول ، من هر جفت رمزنگاری و داده های زنده آن را مدل کردم. اینجا است MarketSymbol
برای ساختار:
class MarketSymbol {
final String id; // e.g., "BTCUSDT"
final String baseAsset; // "BTC"
final String quoteAsset; // "USDT"
final SymbolSnapshot? snapshot; // Live price stats
MarketSymbol({required this.id, required this.baseAsset, required this.quoteAsset, this.snapshot});
}
وت SymbolSnapshot
برای آخرین آمار:
class SymbolSnapshot {
final String symbol;
final double lastPrice; // Latest price
final double priceChangePercent; // 24hr change
// ... openPrice, highPrice, etc.
}
Binance به روزرسانی ها را از طریق یک جریان تیکر ارسال می کند ، که در آن ضبط شده است SymbolTickerEvent
:
class SymbolTickerEvent {
final String eventType; // "24hrTicker"
final int eventTime; // Timestamp
final String symbol; // "BTCUSDT"
final double closePrice; // Latest price
final double openPrice;
final double highPrice;
final double lowPrice;
final double totalTradedBaseAssetVolume; // Base volume
final double totalTradedQuoteAssetVolume; // Quote volume
SymbolSnapshot get snapshot => SymbolSnapshot(
symbol: symbol,
lastPrice: closePrice,
priceChangePercent: (closePrice - openPrice) / openPrice * 100,
// ... other fields
);
}
با استفاده از Riverpod در Flutter ، من نمادها را در یک ذخیره کردم Map
و فقط آنچه را تغییر داد به روز کرد:
void _onSymbolTickerEvent(SymbolTickerEvent event) {
final symbol = state.symbolMap[event.symbol];
if (symbol != null && symbol.snapshot?.lastPrice != event.snapshot.lastPrice) {
state = state.copyWith(
symbolMap: {
...state.symbolMap,
symbol.id: symbol.copyWith(snapshot: event.snapshot),
},
);
}
}
در UI ، من به Riverpod's قلاب می کنم select
برای تماشای فقط آن نماد:
class MarketSymbolRow extends ConsumerWidget {
const MarketSymbolRow({required this.symbolId});
static const double height = 60;
@override
Widget build(BuildContext context, WidgetRef ref) {
final symbol = ref.watch(marketVMProvider.select((state) => state.symbolMap[symbolId]!));
// Returns fancy UI with a height of 60 px
}
}
این فانتزی نیست – فقط راهی برای کوچک نگه داشتن به روزرسانی ها. هنگامی که “btcusdt” یا “ethusdt” تغییر می کند ، فقط ردیف آن تازه می شود ، که مانند یک شروع مناسب و معقول احساس می شود.
مرحله 2: فقط به آنچه می بینید گوش دهید
چرا این مشکل است: داده های زیادی
Binance داده ها را برای هر جفت پخش می کند ، اما چرا اگر 300 ردیف خارج از صفحه باشد ، “XRPUSDT” را بگیرید؟ عضویت در همه جفت های 2000+ شبکه را خفه می کند. من باید روی آنچه قابل مشاهده است تمرکز کنم.
چگونه به آن نزدیک شدم: تماشای منظره
در Flutter ، من از ScrollController
و الف GlobalKey
برای فهمیدن آنچه قابل مشاهده است:
void _updateVisibleSymbols() {
final symbolIds = ref.read(marketVMProvider.select((state) => state.symbolIds));
final viewportSymbolRows = (_viewportKey.height / MarketSymbolRow.height).ceil(); // Visible rows
final firstIndex = max(_scrollController.offset ~/ MarketSymbolRow.height, 0); // Top row
final lastIndex = min(firstIndex + viewportSymbolRows, symbolIds.length - 1); // Bottom row
final newVisibleSymbolIds = symbolIds.toList().sublist(firstIndex, lastIndex + 1).toSet();
if (newVisibleSymbolIds.difference(_visibleSymbolIds).isNotEmpty) {
_visibleSymbolIds = newVisibleSymbolIds;
ref.read(marketVMProvider.notifier).syncPriceTracking(_visibleSymbolIds);
}
}
مدل View اشتراک ها را کنترل می کند ، از نمادهایی که دیگر مورد نیاز نیستند ، اشتراک می کند و فقط در موارد جدید مشترک است. برای جلوگیری از عملیات زائد ، نمادهای همپوشانی (از قبل مشترک) نادیده گرفته می شوند .:
void syncPriceTracking(Set<String> symbolIds) async {
final symbolIdsToUnsubscribe = _currentSubscribedSymbolIds.where((s) => !symbolIds.contains(s)).toList();
final symbolIdsToSubscribe = symbolIds.where((s) => !_currentSubscribedSymbolIds.contains(s)).toList();
if (symbolIdsToUnsubscribe.isNotEmpty) _marketRepository.unsubscribeSymbolMiniTickerStream(symbols: symbolIdsToUnsubscribe);
if (symbolIdsToSubscribe.isNotEmpty) {
await _marketRepository.subscribeSymbolMiniTickerStream(symbols: symbolIdsToSubscribe);
_currentSubscribedSymbolIds = symbolIds;
}
}
این کار ساده بود-ممکن است 15-20 دارایی به جای 500 باشد. این عالی نیست ، اما برای آزمایش کوچک من کار کرد. (جزئیات WebSocket؟ پست دیگر من را ببینید)
مرحله 3: بدون لکنت پیمایش کنید
چرا این مشکل است: پیمایش لاغر
حتی با وجود آن ترفندها ، پیمایش احساس خشن می شد زیرا من اغلب دیدگاه را بررسی می کردم. من می خواستم حتی با تمام داده های زنده ، احساس صافی کند.
چگونه به آن نزدیک شدم: به آن نفس و اندازه ثابت می دهم
من یک تایمر debounce اضافه کردم تا همه چیز را آرام کند:
void _onScroll() {
_debounceTimer?.cancel();
if ((_scrollController.offset - _lastOffset).abs() < 30) return; // Skip small nudges
_debounceTimer = Timer(Duration(milliseconds: 300), () {
_lastOffset = _scrollController.offset;
_updateVisibleSymbols(); // Update after a breather
});
}
و لیست را با یک ثابت ساخت itemExtent
در فلوتر ListView.builder
:
ListView.builder(
key: _viewportKey,
controller: _scrollController,
itemExtent: MarketSymbolRow.height, // Locked at 60px
itemCount: symbolIds.length,
itemBuilder: (context, index) {
final symbolId = symbolIds[index];
return MarketSymbolRow(symbolId: symbolId);
},
)
Debounce پس از توقف پیمایش 300ms صبر می کند ، و itemExtent
طرح را سریع نگه می دارد. این یک ترفند کوچک است ، اما آن را برای من بهتر کرد.
آنچه من بردم
این فقط تلاش من برای کشتی گیری با داده های زمان واقعی در Flutter-Riverpod برای به روزرسانی های کوچک ، بررسی های نمای برای برش داده ها و چند ترفند برای پیمایش نرم تر بود. جریان Binance یک بستر سرگرم کننده بود و اگرچه جواب نهایی نیست ، اما این کار برای من انجام شد. اگر در حال مقابله با لیست های زنده در Flutter هستید ، شاید این بیت ها بتوانند به شما کمک کنند – یا ایده بهتری را برانگیزند. به من اطلاع دهید که شما چه فکر می کنید!