برنامه نویسی

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

آیا تا به حال لیستی از قیمت های زنده را پیمایش کرده اید – استوک (مانند اپل یا تسلا) ، جفت های رمزنگاری 💰 (مانند 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 هستید ، شاید این بیت ها بتوانند به شما کمک کنند – یا ایده بهتری را برانگیزند. به من اطلاع دهید که شما چه فکر می کنید!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا