본문 바로가기
50 year old flutter developer challenge

[50살에 플러터 개발 도전기] Coin Alarm앱에서 오늘은 캔들차트를 그렸다

by Maccrey Coding 2025. 6. 5.
반응형

오늘도 나는 배우고 있다

Coin Alarm과 Watch Over앱을 동시에 만들면서 잠을 거의 못자고 있지만 그래도 요즘 내 삶에서 가장 설레는 시간은 컴퓨터 앞에 앉아 코드를 타이핑할 때다.
50이라는 나이가 무색하게, 나는 매일 새로운 기술을 배우고 도전하고 있다.
오늘은 그중에서도 가장 재미있었던 작업. 바로 캔들차트를 플러터(Flutter)로 구현하는 일이었다.

나는 왜 캔들차트를 구현했는가

주식이나 암호화폐 차트를 보면, 빨간색과 파란색으로 구성된 네모난 막대들이 위아래로 움직이는 걸 본 적이 있을 것이다.
그게 바로 캔들스틱 차트다.
이번 프로젝트에서 나는 사용자의 투자 흐름을 시각적으로 보여줄 수 있는 기능이 필요했고,
그래서 이 캔들차트를 직접 그려보기로 했다.

캔들차트 구현의 핵심 – CustomPainter

플러터에서 커스텀 그래픽을 그릴 땐 CustomPainter를 쓴다.
아래는 내가 직접 만든 캔들차트 페인터 클래스의 일부다.

class CandlestickChartPainter extends CustomPainter {
  final List<CandleData> candles;
  final Color upColor;
  final Color downColor;

  @override
  void paint(Canvas canvas, Size size) {
    if (candles.isEmpty) return;

    double minPrice = double.infinity;
    double maxPrice = -double.infinity;

    for (final candle in candles) {
      minPrice = min(minPrice, candle.low);
      maxPrice = max(maxPrice, candle.high);
    }

    // 캔들 그리기 루프
    for (int i = 0; i < candles.length; i++) {
      final candle = candles[i];
      final x = i * candleWidth - scrollOffset;

      if (x + candleWidth < 0 || x - candleWidth > size.width) continue;

      _drawCandle(canvas, candle, x, size.height, minPrice, maxPrice);
    }
  }

  void _drawCandle(Canvas canvas, CandleData candle, double x, double height, double minPrice, double maxPrice) {
    // 캔들 바디 그리기
    // ...
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

여기서 중요한 건 paint() 메서드 안의 루프다.
보이지 않는 캔들은 그리지 않도록 처리해서 성능도 챙겼다.

캐시도 빠뜨릴 수 없다

차트 데이터를 매번 API로 불러오는 건 비효율적이다.
그래서 나는 캐시 서비스를 만들어 데이터를 임시 저장하도록 했다.

Future<CandleChartData?> getCandleData(String symbol, ChartTimeframe timeframe) async {
  try {
    final cachedData = _cacheService.getCandleChartData(symbol, timeframe);
    if (cachedData != null && !cachedData.isExpired()) {
      return cachedData;
    }

    // 새 데이터 요청 및 캐시 저장
    final data = await api.fetchData(symbol, timeframe);
    await _cacheService.saveCandleChartData(data);
    return data;
  } catch (e) {
    // 에러 처리
    return null;
  }
}

시간 보정도 고민했다

특히 일간(1D)이나 주간(1W) 차트의 경우, 캔들의 기준 시간이 중요한데
이를 위해 시간 보정 함수도 작성했다.

List<CandleData> _adjustTimestamps(List<CandleData> candles, ChartTimeframe timeframe) {
  if (candles.isEmpty) return candles;

  final now = DateTime.now();
  final List<CandleData> adjustedCandles = [];
  final timeDifference = now.difference(candles.last.timestamp).inMinutes;

  final needsAdjustment = timeDifference > 30 ||
      timeframe == ChartTimeframe.days1 ||
      timeframe == ChartTimeframe.days7;

  if (needsAdjustment) {
    DateTime startTime;
    if (timeframe == ChartTimeframe.days1) {
      startTime = DateTime(now.year, now.month, now.day).subtract(Duration(days: candles.length - 1));
    } else {
      final thisMonday = now.subtract(Duration(days: now.weekday - 1));
      startTime = DateTime(thisMonday.year, thisMonday.month, thisMonday.day)
          .subtract(Duration(days: 7 * (candles.length - 1)));
    }

    for (int i = 0; i < candles.length; i++) {
      adjustedCandles.add(candles[i].copyWith(timestamp: startTime.add(Duration(days: i))));
    }

    return adjustedCandles;
  }

  return candles;
}

이 덕분에 데이터가 일정하게 그려졌고, UI가 훨씬 안정되어 보였다.

오늘도 한 걸음

솔직히 말하면, 머리가 터질 것 같았던 날도 많았다.
하지만 한 줄, 한 줄 코드를 이해해 가는 이 기쁨이 나를 계속 걷게 한다.

50세 플러터 개발자의 삶은 오늘도 현재 진행형이다.

반응형