본문 바로가기
Flutter/Flutter Programming

플러터에서 Future와 void, 언제 어떻게 사용해야 할까요?

by Maccrey 2024. 7. 4.
반응형

플러터에서 비동기 작업을 처리할 때는 두 가지 주요 도구, Futurevoid를 활용하게 됩니다.

각각 어떤 역할을 하고 언제 사용해야 하는지 헷갈리는 경우가 많죠.

오늘은 플러터에서 Futurevoid를 사용하는 경우이유를 명확하게 알아보고, 실제 코드 예시와 함께 이해를 돕겠습니다.

 

1. Future: 값을 반환하는 비동기 작업 처리

 

Future는 비동기 작업의 결과 값을 보유하고 제공하는 역할을 합니다.

마치 우편함처럼, 작업이 완료되면 결과 값을 담아 기다리고 있다가, await 키워드를 통해 꺼내 사용할 수 있도록 해줍니다.

 

Future를 사용하는 경우

  • 비동기 작업의 결과 값을 사용해야 할 때: 네트워크 통신으로 데이터를 불러오거나, 로컬 파일을 읽어오는 경우처럼 작업 결과를 활용해야 하는 상황에서 Future를 사용합니다.
  • 비동기 작업 진행 상황을 추적해야 할 때: Future 객체는 .then() 또는 .catchError() 메서드를 통해 작업 진행 상황이나 오류 상황을 처리할 수 있도록 합니다. 예를 들어, 로딩 UI를 표시하거나, 오류 메시지를 출력하는 등의 작업이 가능합니다.
  • 비동기 작업 완료 후 후속 작업을 수행해야 할 때: await 키워드를 통해 Future 객체의 결과 값을 받은 후, 그 값을 기반으로 다음 단계의 작업을 수행할 수 있습니다.
  • 예를 들어, 네크워크에서 데이터를 가져오거나, 저장소에서 파일을 로드하거나, 시간이 걸리는 복잡한 계산을 수앵하는 메서드등이 있습니다.

예시

Dart
Future<String> fetchData() async {
  // 비동기 작업을 수행하여 문자열 결과를 반환합니다.
  final response = await http.get(Uri.parse('https://www.example.com'));
  if (response.statusCode == 200) {
    return response.body;
  } else {
    throw Exception('Failed to fetch data');
  }
}

void main() async {
  // fetchData() 함수를 호출하고 결과 값을 변수에 저장합니다.
  final data = await fetchData();

  // data 변수를 사용하여 후속 작업을 수행합니다.
  print(data);
  // ...
}
 

2. void: 값을 반환하지 않고 단순히 작업만 수행

 

void는 반환 값이 없는 타입으로, 비동기 작업 자체만 수행하고 결과 값은 제공하지 않습니다. 마치 작업을 완료하는 데 집중하는 도구라고 생각하면 됩니다.

 

void를 사용하는 경우

  • 비동기 작업의 결과 값이 중요하지 않은 경우: 데이터 저장, 파일 삭제, 로컬 설정 변경 등 작업 자체만 수행하고 결과 값을 사용하지 않는 경우 void를 사용합니다.
  • 간단하고 가벼운 비동기 작업을 수행할 때: 로딩 애니메이션 표시, UI 업데이트 등 간단한 작업을 수행할 때 void를 사용하면 코드를 간결하게 만들 수 있습니다.
  • 후속 작업이 필요하지 않은 경우: 비동기 작업이 완료된 후 별도의 후속 작업을 수행할 필요가 없는 경우 void를 사용합니다.
  • 예를들어, 사용자가 상호 작용을 처리하거나, 환경설정을 설정하거나, 특정결과를 반화하지 않고 시간이 걸리는 복잡한 계산을 수행하는 메서드 등이 있습니다.

예시

void saveData() async {
  // 비동기 작업을 수행하고 결과 값을 반환하지 않습니다.
  final data = {'name': 'Flutter', 'age': 2};
  await SharedPreferences.getInstance().then((prefs) {
    prefs.setString('data', jsonEncode(data));
  });
}

void main() {
  // saveData() 함수를 호출하여 비동기 작업을 수행합니다.
  saveData();
  print('Data saved');
}
 

Future vs void, 어떤 것을 선택해야 할까요?

 

1. 상황에 맞는 선택 가이드

 

상황Future 사용void 사용설명

 

비동기 작업의 결과 값을 사용해야 하는가? O X 네트워크 통신, 파일 입출력, 데이터베이스 작업 등 결과 값을 활용해야 하는 경우 Future를 사용합니다.
비동기 작업 진행 상황을 추적해야 하는가? O X 로딩 UI 표시, 진행률 업데이트 등 작업 진행 상황을 모니터링해야 하는 경우 Future를 사용합니다.
비동기 작업 완료 후 후속 작업을 수행해야 하는가? O X 작업 결과를 기반으로 다음 단계의 작업을 진행해야 하는 경우 Future를 사용합니다.
비동기 작업의 결과 값이 중요하지 않은가? X O 데이터 저장, 파일 삭제, 설정 변경 등 작업 자체만 수행하고 결과 값을 사용하지 않는 경우 void를 사용합니다.
간단하고 가벼운 비동기 작업을 수행하는가? X O 로딩 애니메이션 표시, UI 업데이트 등 간단한 작업을 수행하는 경우 void를 사용하면 코드를 간결하게 만들 수 있습니다.
후속 작업이 필요하지 않은가? X O 비동기 작업이 완료된 후 별도의 후속 작업을 수행할 필요가 없는 경우 void를 사용합니다.
 

2. 실제 코드 예시

 

Future 사용 예시

Future<String> fetchData() async {
  final response = await http.get(Uri.parse('https://www.example.com'));
  if (response.statusCode == 200) {
    return response.body;
  } else {
    throw Exception('Failed to fetch data');
  }
}

void main() async {
  final data = await fetchData();
  print(data);
}
 

void 사용 예시

 
void saveData() async {
  final data = {'name': 'Flutter', 'age': 2};
  await SharedPreferences.getInstance().then((prefs) {
    prefs.setString('data', jsonEncode(data));
  });
}

void main() {
  saveData();
  print('Data saved');
}
 

3. Future: 다양한 활용법

 

3.1 여러 Future 객체 동시 처리

 

Future.wait() 함수를 사용하면 여러 개의 Future 객체를 동시에 처리하고, 모든 작업이 완료된 후 결과 값들을 배열 형태로 받아 처리할 수 있습니다.

Future<List<String>> fetchData() async {
  // 두 개의 비동기 작업을 동시에 수행합니다.
  final data1 = await http.get(Uri.parse('https://www.example.com/data1'));
  final data2 = await http.get(Uri.parse('https://www.example.com/data2'));

  // 두 작업의 결과 값을 배열에 저장합니다.
  final results = [data1.body, data2.body];
  return results;
}
 

3.2 Future 체인

 

then() 메서드를 통해 다음 Future 객체를 연결하여 이전 작업의 결과 값을 다음 작업에 전달하고 활용할 수 있습니다. 마치 작업 흐름을 체인처럼 이어나가는 방식입니다.

Future<String> fetchData() async {
  // 비동기 작업을 수행하고 문자열 결과를 반환합니다.
  final response = await http.get(Uri.parse('https://www.example.com'));
  if (response.statusCode == 200) {
    return response.body;
  } else {
    throw Exception('Failed to fetch data');
  }
}

void main() async {
  // fetchData() 함수를 호출하고 결과 값을 변수에 저장합니다.
  final data = await fetchData();

  // data 변수를 사용하여 다음 작업을 수행합니다.
  final processedData = await processData(data);
  print(processedData);
}
 

3.3 Future 오류 처리

 

catchError() 메서드를 사용하여 비동기 작업 중 발생하는 오류를 처리하고 예외 상황을 관리할 수 있습니다.

Future<String> fetchData() async {
  try {
    // 비동기 작업을 수행하고 문자열 결과를 반환합니다.
    final response = await http.get(Uri.parse('https://www.example.com'));
    if (response.statusCode == 200) {
      return response.body;
    } else {
      throw Exception('Failed to fetch data');
    }
  } catch (error) {
    // 오류가 발생하면 에러 메시지를 출력합니다.
    print(error.toString());
    return '';
  }
}
 

4. void: 고급 기능 활용

 

4.1 Completer

 

Completer 객체를 사용하면 비동기 작업의 진행 상황을 직접 제어하고, 결과 값을 명시적으로 제공할 수 있습니다. 마치 직접 컨트롤러를 잡고 작업을 조율하는 것과 같습니다.

void saveData() async {
  // Completer 객체를 생성합니다.
  final completer = Completer<void>();

  // 비동기 작업을 수행합니다.
  final data = {'name': 'Flutter', 'age': 2};
  await SharedPreferences.getInstance().then((prefs) {
    prefs.setString('data', jsonEncode(data));
    completer.complete(); // 작업 완료를 알립니다.
  });

  // 작업 완료를 기다립니다.
  await completer.future;
}

void main() {
  // saveData() 함수를 호출하고 작업 완료를 기다립니다.
  saveData();
  print('Data saved');
}
 

4.2 Stream

 

Stream 객체는 비동기 작업에서 발생하는 데이터를 실시간으로 스트리밍 방식으로 전달합니다.

마치 끊임없이 흐르는 강물처럼 데이터를 수신하고 처리할 수 있습니다.

Stream<String> fetchData() async {
  // 비동기 작업을 수행하고 데이터를 스트리밍합니다.
  final response = await http.get(Uri.parse('https://www.example.com'));
  if (response.statusCode == 200) {
    return response.body.split('\n').map((line) => line.trim()).where((line) => line.isNotEmpty()).asyncMap((line) async {
      // 각 라인을 처리하고 결과를 스트리밍합니다.
      final processedData = await processData(line);
      return processedData;
    });
  } else {
    throw Exception('Failed to fetch data');
  }
}

void main() async {
  // fetchData() 함수를 호출하고 스트림을 구독합니다.
  final subscription = fetchData().listen((data) {
    print(data); // 각 데이터를 수신하고 처리합니다.
  }, onError: (error) {
    print(error.toString()); // 오류 발생 시 처리합니다.
  }, onDone: () {
    print('Stream completed'); // 스트림 완료 시 처리합니다.
  });

  // 나중에 스트림 구독을 취소할 수 있습니다.
  await subscription.cancel();
}
 

5. async와 await 키워드

 

비동기 작업을 처리할 때 asyncawait 키워드를 함께 사용하면 코드 가독성을 높이고 효율적으로 작업을 처리할 수 있습니다.

  • async: 함수를 비동기 함수로 선언합니다.
  • await: 비동기 작업이 완료될 때까지 기다립니다.
Future<String> fetchData() async {
  // 비동기 작업을 수행하고 문자열 결과를 반환합니다.
  final response = await http.get(Uri.parse('https://www.example.com'));
  if (response.statusCode == 200) {
    return response.body;
  } else {
    throw Exception('Failed to fetch data');
  }
}

void main() async {
  // fetchData() 함수를 호출하고 결과 값을 변수에 저장합니다.
  final data = await fetchData();

  // data 변수를 사용하여 후속 작업을 수행합니다.
  print(data);
}
 

6. 실제 개발에서의 활용 사례

  • 네트워크 통신: 서버로부터 데이터를 요청하고 결과를 처리합니다. (예: API 호출, 데이터 불러오기)
  • 파일 입출력: 로컬 파일을 읽고 쓰거나, 외부 저장공간에 파일을 저장합니다.
  • 데이터베이스 작업: 데이터베이스에 데이터를 저장하고 조회합니다.
  • UI 업데이트: UI를 동적으로 업데이트하고 애니메이션을 실행합니다.
  • 백그라운드 작업: 사용자와 상호 작용하지 않고 수행되는 작업을 처리합니다.

7. 주의 사항 및 권장 사항

  • 비동기 작업 오류 처리: try-catch 또는 catchError()를 사용하여 오류를 처리하고 예외 상황을 관리합니다.
  • 성능 최적화: Future.wait() 또는 Stream과 같은 도구를 사용하여 여러 비동기 작업을 효율적으로 처리합니다.
  • 코드 가독성 유지: 명확한 변수명, 주석 및 코드 구조를 사용하여 코드를 이해하기 쉽게 만듭니다.
  • 테스트 코드 작성: 테스트 코드를 작성하여 코드의 정확성과 안정성을 검증합니다.

8. 마무리

 

플러터에서 Futurevoid는 비동기 작업을 처리하는 데 필수적인 도구입니다. 상황에 맞는 도구를 선택하고 올바르게 사용하여 효율적이고 안정적인 코드를 작성하시기 바랍니다.

본 블로그 포스팅이 플러터의 비동기 프로그래밍 개념을 이해하고 활용하는 데 도움이 되었기를 바랍니다. 궁금한 점이나 더 알아보고 싶은 내용이 있다면 언제든지 질문해주세요!

 

 

 

 

Tester Share [테스터쉐어] - Google Play 앱

Tester Share로 Google Play 앱 등록을 단순화하세요.

play.google.com

 

 

나비일기장 [수발일기장] - Google Play 앱

수형자 수발가족및 수발인을 위한 일기장으로 수형생활시기에 따른 정보를 얻을 수 있습니다.

play.google.com

 

반응형