플러터 강좌: 14.6 앱에서 Future 출력하기

본 강좌에서는 Flutter에서 Future를 생성하고 출력하는 방법에 대해 상세히 설명하겠습니다. Future는 Dart의 비동기 프로그래밍을 위한 핵심 개념으로, 비동기 작업이 완료될 때 값이나 오류를 반환하는 객체입니다. 이를 통해 우리는 UI를 블로킹하지 않고도 시간이 오래 걸리는 작업을 수행할 수 있습니다.

1. Future란?

Future는 비동기 프로그래밍에서 미래의 결과를 나타내는 객체로, 비동기 작업이 완료될 때까지 기다렸다가 결과를 받을 수 있게 해줍니다. 예를 들어, HTTP 요청을 통해 데이터를 받아오는 경우, 요청이 끝날 때까지 기다리지 않고 다른 UI 작업을 진행할 수 있습니다.

1.1 Future의 상태

  • 대기 중 (Pending): 작업이 완료되지 않은 상태입니다.
  • 완료 (Completed): 작업이 완료되어 결과 값을 반환한 상태입니다.
  • 오류 (Error): 작업 중 오류가 발생한 상태입니다.

2. Future 생성하기

Future를 생성하는 방법은 크게 두 가지입니다. 첫 번째는 내장 메소드를 사용하는 것이고, 두 번째는 사용자 정의 함수를 통해 생성하는 것입니다.

2.1 내장 메소드 사용하기

Future.delayed 메소드를 통해 일정 시간 후에 작업이 완료되는 Future 객체를 생성할 수 있습니다. 다음은 2초 후에 메시지를 반환하는 예제입니다.

Future fetchData() {
        return Future.delayed(Duration(seconds: 2), () {
            return '데이터 로드 완료';
        });
    }

2.2 사용자 정의 함수 만들기

Future를 반환하는 함수를 직접 만들어 데이터베이스나 API 요청을 처리할 수도 있습니다. 예를 들어, API에서 사용자 정보를 가져오는 함수를 생성해 보겠습니다.

Future fetchUser(int userId) async {
        final response = await http.get('https://api.example.com/user/$userId');
        if (response.statusCode == 200) {
            return User.fromJson(json.decode(response.body));
        } else {
            throw Exception('사용자 로드 실패');
        }
    }

3. Future 출력하기

이제 우리가 생성한 Future 객체의 값을 Flutter 앱에서 출력하는 방법을 살펴보겠습니다. 이를 위해 FutureBuilder 위젯을 사용합니다. FutureBuilderFuture의 상태에 따라 UI를 동적으로 업데이트해 줍니다.

3.1 FutureBuilder 사용하기

FutureBuilder를 사용하기 위해서는 futurebuilder 매개변수를 정의해야 합니다. future에는 비동기 작업을 수행할 Future 객체를 지정하고, builder는 비동기 작업의 상태에 따라 UI를 구성하는 함수를 정의합니다.

class UserProfile extends StatelessWidget {
        final int userId;
        UserProfile(this.userId);
        
        @override
        Widget build(BuildContext context) {
            return FutureBuilder(
                future: fetchUser(userId),
                builder: (context, snapshot) {
                    if (snapshot.connectionState == ConnectionState.waiting) {
                        return CircularProgressIndicator();
                    } else if (snapshot.hasError) {
                        return Text('Error: ${snapshot.error}');
                    } else {
                        return Text('사용자 이름: ${snapshot.data.name}');
                    }
                },
            );
        }
    }

3.2 예외 처리

비동기 작업에서 오류가 발생할 수 있으므로, FutureBuilder에서 snapshot.hasError를 사용해 오류를 처리하는 것이 중요합니다. 올바른 예외 처리를 통해 사용자에게 명확한 피드백을 제공할 수 있습니다.

4. 전체 코드 예제

이제까지 배운 내용을 바탕으로 전체 예제를 살펴보겠습니다. 이 예제는 사용자 정보를 가져오는 API를 호출하고, 이를 화면에 출력하는 Flutter 앱입니다.

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

class User {
    final String name;
    
    User({required this.name});
    
    factory User.fromJson(Map json) {
        return User(name: json['name']);
    }
}

Future fetchUser(int userId) async {
    final response = await http.get('https://api.example.com/user/$userId');
    if (response.statusCode == 200) {
        return User.fromJson(json.decode(response.body));
    } else {
        throw Exception('사용자 로드 실패');
    }
}

class UserProfile extends StatelessWidget {
    final int userId;
    UserProfile(this.userId);
    
    @override
    Widget build(BuildContext context) {
        return FutureBuilder(
            future: fetchUser(userId),
            builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                    return CircularProgressIndicator();
                } else if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                } else {
                    return Text('사용자 이름: ${snapshot.data.name}');
                }
            },
        );
    }
}

void main() => runApp(MaterialApp(home: Scaffold(body: UserProfile(1))));

5. 결론

이번 강좌에서는 Flutter에서 Future를 생성하고, 이를 사용하여 비동기 작업을 효율적으로 처리하는 방법에 대해 배웠습니다. FutureBuilder를 활용한 UI 구성은 데이터 로드 상태에 따라 동적으로 변할 수 있어 매우 유용합니다. 이러한 비동기 프로그래밍 기술은 복잡한 앱에서도 사용자의 경험을 개선하는 데 큰 도움이 됩니다.

팁: 비동기 프로그래밍을 더 심화하고 싶다면 Streamasync/await 개념도 함께 학습해 보세요.