Future to Stream

2025. 6. 30. 15:49
반응형

제가 참여했던 인턴 프로젝트에서는 백엔드와의 API 통신을 통해 정보를 가져오는 모든 부분이 Future 방식으로 구현되어 있었습니다. Future는 Flutter의 대표적인 비동기 처리 방식으로, 한 번의 API 요청으로 데이터를 가져오는 데는 매우 적합합니다.

하지만 프로젝트를 진행하면서 한 가지 큰 문제점을 발견했습니다. 바로 실시간으로 정보가 업데이트되어야 하는 중요한 기능들까지도 Future를 통해 처리하고 있었다는 점이었습니다.

예를 들어, 실시간으로 변동되는 주문 상태나 알림 같은 정보들이 이에 해당했습니다. 당시에는 이러한 '실시간' 데이터를 가져오기 위해 Future를 **일정 시간마다 반복적으로 호출(폴링)**하는 방식으로 구현되어 있었습니다.

 

이 방식의 문제점은 명확했습니다

  • Future는 한 번 작동하고 나면 다시 요청하기 전까지는 아무런 동작을 하지 않았습니다. 따라서 일정 시간 간격으로 호출되는 방식으로는 데이터가 업데이트되는 순간을 즉시 반영할 수 없었고, 다음 호출 시점까지 필연적인 딜레이가 발생했습니다.
  • 이러한 딜레이는 사용자 경험을 저해했을 뿐만 아니라, 실시간성이 중요한 앱 서비스의 핵심 기능 구동에 문제를 야기했습니다. 예를 들어, 주문 상태가 실시간으로 반영되지 않아 사용자가 혼란을 겪는 상황이 발생할 수 있었습니다.

 

저는 이러한 문제를 해결하고 앱 서비스의 반응성과 효율성을 극대화하기 위해 Stream 방식의 도입이 필수적이라고 판단했습니다.

아래는 Future 함수와 Stream으로 바꾼 두가지의 함수입니다.

  Stream<List<dynamic>> streamGetMyOrders() async* {
    while (true) {
      try {
        final String path = '/order/my';
        print('Fetching my orders from API...');

        final response = await _apiService.get(path);

        print('My orders response : $response');
        yield response;
      } catch (e) {
        print('Error fetching my orders: $e');
        throw Exception('Failed to fetch my orders: $e');
      }
    }
  }

  Future<List<dynamic>> getMyOrders() async {
    try {
      final String path = '/order/my';
      print('Fetching my orders from API...');

      final response = await _apiService.get(path);

      print('My orders response: $response');
      return response;
    } catch (e) {
      print('Error fetching my orders: $e');
      throw Exception('Failed to fetch my orders: $e');
    }
  }

 

이렇게 바꾸면서 배운것이 있습니다.

  1. stream을 사용할때는 async가 아니라 async*를 사용한다는 것.
  2. return이 아니라 yield를 이용한다는 것.
    • return은 데이터를 한번만 던지기 때문에 stream함수로 바꾼상태에서 return을 사용하면 future를 사용하는 것과 같게 됩니다. 그런데 yield는 데이터를 함수가 끝나기 전까지 지속해서 데이터를 던지기 때문에 stream으로 할때는 yield를 사용해야합니다.
  3. 지금 stream으로 바꿀때 while로 한번더 감싸줬는데 이는 지금의 함수에서는 while을 쓰지 않으면 데이터 호출은 한번만하게 되어서 호출을 지속해서 해주기 위해서 while로 함수를 계속해서 호출하는 형식으로 바꾸었습니다.

stream으로 바꿀때는 단순하게 stream을 쓰는 것이 아니라 지금 내가 사용하는 데이터가 무엇인지를 확인하고 어떻게 데이터를 가져오게 구성이 되어있는지를 확인하는 것이 중요합니다.

 

 

Firebase에서의 stream

Future<List<CommentDto>> fetchComments() async{
    try {
      //1. 파이어스토어 인스턴스 가지고오기
      final firestore = FirebaseFirestore.instance;
      //2. 컬렉션 참조 만들기
      final collectionRef = firestore.collection('Comment');
      //3. 값 불러오기
      final result = await collectionRef.get();

      final docs = result.docs;
      return docs.map((doc) {
        final map = doc.data();
        return CommentDto.fromJson(map);
      }).toList();
    } catch (e) {
      log('$e');
      return [];
    }
  }

  @override
  Stream<List<CommentDto>> streamComments() {
      final firestore = FirebaseFirestore.instance;
      final collectionRef = firestore.collection('Comment');

      final stream = collectionRef.snapshots();

      final newStream = stream.map((snapshot){
        final docs = snapshot.docs.map((doc) {
          final map = doc.data();
          return CommentDto.fromJson(map);
        }).toList();
        
        return docs;
      });

      return newStream;
  }

위의 코드는 예전 부트 캠프에서 Firebase를 이용하는 프로젝튼에서 future를 stream으로 바꾼 함수입니다.

여기선 yield와 async*를 사용하지 않고 stream으로 바꿔서 사용하는 모습을 볼 수 있습니다. 이렇게 좀더 간단하게 Stream으로 바꿀 수 있는 이유가 궁금해서 찾아봤습니다.

이유는 Fireabase가 단순하게 백엔드 서버를 제공하는 것이 아니라 실시간 데이터 통신을 제공하기 위한 서비스이므로 Stream을 위한 백엔드 설정이 기본으로 되어있어서 Future에서 Stream으로 바꿀 수 있었던 것입니다.

그런데 인턴 프로젝트에서는 Django를 사용하면서 stream을 위한 설계가 되어있지 않아 flutter 함수로 stream을 구현하기 위해서 함수의 변화가 필요한 것이였습니다.

 

비동기 처리를 위한 Future는 Flutter에서 아주 좋은 방식입니다만 future 함수로는 실시간 통신이 어렵다보니 stream 함수를 사용할 수 있어야합니다. Firebase만 사용했었는데 백엔드 개발자가 있는 프로젝트에 처음 참여해서 해봤던 것도 다른 방식으로 도전해보면서 한가지를 배울 수 있었습니다.

반응형

'Flutter > 한달 인턴' 카테고리의 다른 글

Flutter Mounted에 관하여  (6) 2025.08.09
Navigate with named routes  (1) 2025.06.24

BELATED ARTICLES

more