꿈꾸는 시스템 디자이너

Flutter 강좌 - [Networking] 인터넷에서 데이터 가져오는 방법 본문

Development/Flutter

Flutter 강좌 - [Networking] 인터넷에서 데이터 가져오는 방법

독행소년 2019. 7. 8. 14:18

Flutter 강좌 목록 : https://here4you.tistory.com/120

 

 

이번 강좌에서는 Http 프로토콜을 이용해서 특정 URL의 데이터를 가져오는 방법에 대해서 알아본다.

이 강좌에서 사용할 URL은 https://jsonplaceholder.typicode.com/posts/1 이며 웹브라우저를 통해 접속해보면 다음과 같은 JSON 포맷의 데이터를 반환한다.

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

앱에서는 위의 데이터를 수신하여 title 항목에 해당하는 데이터를 화면에 출력할 것이다.

1. 외부 패키지 추가

Flutter에서 Http 프로토콜을 이용하기 위해서는 http 패키지를 이용해야 하므로 pubspec.yaml 파일의 dependencies 항목을 다음과 같이 수정한 후 Packages get 명령을 실행한다.

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  http: ^0.12.0+2

 

2. 라이브러리/패지키 선언

사용할 라이브러리와 패키지들은 다음과 같이 선언된다.

import 'package:flutter/material.dart';

import 'dart:async'; // async/await 를 이용하기 위한 라이브러리이나 선언하지 않아도 이용가능
import 'dart:convert'; // json 데이터 변환용 라이브러리
import 'package:http/http.dart' as http; // Http 프로토콜을 이용하기 위한 패키지

async 라이브러리의 경우 async와 await를 제공하는 라이브러리 인데 명시적으로 선언하지 않아도 기본적으로 사용할 수 있다. aync로 선언된 클래스 내부에 await로 구현된 메소드는 그 로직이 완료되거나 타임아웃이 발생할 때까지 비동기적으로 처리된다.

conver는 JSON 객체를 다루기 위한 라이브러리로 온라인에서 수신한 응답 메시지를 JSON 포맷의 데이터를 디코딩 할 때 사용한다.

Http 패키지는 Http 프로토콜과 관련한 API를 제공하는 패키지로 'as http'로 선언되여 코드 내에서 http 약칭으로 이용할 수 있다.

 

3. Post 클래스 구현

Post 클래스는 Http 요청 메시지의 응답 메시지를 저장하기 위해 사용할 클래스로 다음과 같이 구현한다.

class Post {
  final int userId;
  final int id;
  final String title;
  final String body;

  Post({this.userId, this.id, this.title, this.body});

  // factory 생성자. Post 타입의 인스턴스를 반환
  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
        userId: json['userId'],
        id: json['id'],
        title: json['title'],
        body: json['body']);
  }
}

factory 생성자로 Post.fromJson()을 구현하고 있는데, factory란 개발자가 임의로 클래스의 인스턴스를 생성해서 이용하는 패턴이 아닌, 인스턴스를 대신 생성해서 반환해주는 패턴 기법이다. 위 코드에서는 아규먼트로 넘겨 받은 JSON 데이터를 새로운 Post 클래스의 인스턴스로 생성하여 반환한다.

* 여기서 Post는 우편(수신 메시지)이라는 뜻으로 명명한 것이지 Http 프로토콜의 Post 방식을 의미하는 것은 아니다. 실제 앱에서는 Post 방식이 아닌 Get 방식으로 데이터를 수신하고 있다.

 

4. 응답 메시지 처리

Http 프로토콜을 이용해 데이터를 수신하는 방법은 다음과 같다.

// Futures: Dart의 핵심 클래스로서, async 동작은 가지는 작업을 처리하게 위해 사용
// Future 객체는 일정 소요시간 이후에 값이나 에러를 반환한다.
Future<Post> fetchPost() async {
  // 해당 URL의 데이터를 수신.
  // await 처리: 응답 메시지가 도착하거나 타임아웃이 발생할 때까지 대기
  final response =
      await http.get('https://jsonplaceholder.typicode.com/posts/1');

  // 응답의 상태코드가 200인 경우. 정상적으로 응답메시지를 수신한 경우
  if (response.statusCode == 200) {
    // 수신 메시지의 body부분을 JSON 객체로 디코딩한후 Post.fromJson 메소드를 통해 다시 파싱함
    return Post.fromJson(json.decode(response.body));
  }
  // 서버로부터 정상응답을 받지 못한 경우. 어떤  에러가 발생한 경우
  else {
    // 에러 발생
    throw Exception('Failed to load post');
  }
}

Http 요청 메시지를 전송하고 그 응답을 수신하기 위해 Post 타입의 Future 인스턴스를 반환하는 메소드 fetchPost를 aync 로 선언하고 있다. 좀 복잡할 수도 있으니 좀 더 자세히 분석해 보자

fetchPost 메소드를 호출하면 Post 클래스의 구조를 가지는 Future 인스턴스가 반환된다. Post 클래스의 구조는 위에서 살펴본 구조다. 그럼 Future는 무엇인가?

Future는 Dart의 핵심 클래스 중 하나로 비동기적으로 동작하는 작업을 처리때 사용한다. Future 객체는 일정 소요시간 이후에 적절한 값이나 에러로 반환된다. 이를 위해서 async 클래스로 선언하고 서버로부터 데이터를 요청/수신하는 http.get 메소르를 await로 호출한다. async 클래스는 await 메소를 가지는 클래스로 await로 선언된 메소드는 그 응답이 처리될때까지 대기하는 비동기식으로 동작한다. 결과적으로 http.get 메소드를 통해 URL에 해당하는 데이터를 수신하여 response 필드에 저장한 후 다음 코드가 계속 진행된다.

response의 상태코드가 정상(200)이면 Post 클래스의 팩토리 생성자인 fromJson을 호출하여 Post 인스턴스를 반환한다. 이 때 수신한 데이터를 JSON 객체로 변환하기 위해 decode 함수가 쓰인다. 만약 정상 상태코드를 수신하지 못한 경우 에러를 발생한다.

* 파리미더에 기술된 response.body 는 Post 클래스의 body를 의미하는 것이 아니다. 여기서의 body는 get 메소드 호출에 의해 수신된 데이터 중 body에 해당하는 데이터 이며 웹브라우저로 접속했을 때 확인했던 JSON 객체 전부를 포함하고 있다.

 

5. 수신 데이터 화면 출력

수신 한 데이터를 화면에 출력하기 위한 코드는 다음과 같다.

class MyApp extends StatelessWidget {
  final Future<Post> post;

  MyApp({Key key, @required this.post}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: Text('Fetch Data Example')),
        body: Center(
          // FutureBuilder: Future 객체를 처리하기 위한 builder
          child: FutureBuilder<Post>(
            // Future 객체 post를 future 항목에 할당
            future: post,
            // builder는 post값의 변화가 발생할 때마다 호출됨
            builder: (context, snapshot) {
              // 정상 데이터 수신 시 해당 데이터의 title 출력
              if (snapshot.hasData) {
                return Text(snapshot.data.title);
              }
              // 에러 수신 시 에러 메시지 출력
              else if (snapshot.hasError) {
                return Text("${snapshot.error}");
              }
              // 상태처리 인디케이터 표시. 앱 초기에 출력
              return CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

클래스의 생성자에서 Future<Post> 타입의 응답 메시지를 전달받아 필드에 저장한다. 이 값을 화면에 출력하기 위해 FutureBuilder 메소드를 사용한다. FutureBuilder의 future 항목에서 post(필드값)을 설정한다. 이로서 post의 값에 변화가 생길 때 마다 builder가 호출되게 된다. 새로 갱신된 snapshot(post)의 내용을 판단하여 데이터가 정상 수신된 경우 해당 데이터의 title 항목을 출력하고, 에러가 밸생한 경우 그 에러 메시지를 출력한다. 앱이 처음 실행되었을 때에는 CircularProgressIndicator가 화면에 출력된다.

 

6. 응답 메시지 수신 전략

Http 요청 메시지를 전송하고 그 응답을 수신할 때 적절한 절차는 다음과 같이 생각해 볼 수 있다.

* StatelessWidget을 이용하는 경우

StatelessWidget으로 화면을 구성할 경우 StatelessWidget 클래스의 생성자 호출 시 fetchPost 함수를 호출결과를 전달해 주는 것이 적당하다. 아래의 코드가 이에 해당한다.

void main() => runApp(MyApp(post: fetchPost()));

MyApp 클래스 생성시 fetchPost 함수의 결과값을 필드 post로 전달하는 것이다. post가 Future 타입이고 FutureBuilder를 통해 화면을 구성하므로 화면 출력 초기에는 인디케이터가 출력되다가 응답 메시지가 수신되면 정상 텍스타나 에러 메시지가 출력되게 된다.

실행 화면은 다음과 같다.

 

* StatefulWidget을 이용하는 경우

StatefulWidget으로 화면을 구성할 경우 상태 클래스의 initState() 메소드 안에서 fechPost 함수가 호출되도록 재정의해서 1번만 통신이 발생하도록 구현한다.

 

전체 소스코드는 다음과 같다.

import 'package:flutter/material.dart';

import 'dart:async'; // async/await 를 이용하기 위한 라이브러리이나 선언하지 않아도 이용가능
import 'dart:convert'; // json 데이터 변환용 라이브러리
import 'package:http/http.dart' as http; // Http 프로토콜을 이용하기 위한 패키지

class Post {
  final int userId;
  final int id;
  final String title;
  final String body;

  Post({this.userId, this.id, this.title, this.body});

  // factory 생성자. Post 타입의 인스턴스를 반환
  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
        userId: json['userId'],
        id: json['id'],
        title: json['title'],
        body: json['body']);
  }
}

// Futures: Dart의 핵심 클래스로서, async 동작은 가지는 작업을 처리하게 위해 사용
// Future 객체는 일정 소요시간 이후에 값이나 에러를 반환한다.
Future<Post> fetchPost() async {
  // 해당 URL의 데이터를 수신.
  // await 처리: 응답 메시지가 도착하거나 타임아웃이 발생할 때까지 대기
  final response =
      await http.get('https://jsonplaceholder.typicode.com/posts/1');

  // 응답의 상태코드가 200인 경우. 정상적으로 응답메시지를 수신한 경우
  if (response.statusCode == 200) {
    // 수신 메시지의 body부분을 JSON 객체로 디코딩한후 Post.fromJson 메소드를 통해 다시 파싱함
    return Post.fromJson(json.decode(response.body));
  }
  // 서버로부터 정상응답을 받지 못한 경우. 어떤  에러가 발생한 경우
  else {
    // 에러 발생
    throw Exception('Failed to load post');
  }
}

void main() => runApp(MyApp(post: fetchPost()));

class MyApp extends StatelessWidget {
  final Future<Post> post;

  MyApp({Key key, @required this.post}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: Text('Fetch Data Example')),
        body: Center(
          // FutureBuilder: Future 객체를 처리하기 위한 builder
          child: FutureBuilder<Post>(
            // Future 객체 post를 future 항목에 할당
            future: post,
            // builder는 post값의 변화가 발생할 때마다 호출됨
            builder: (context, snapshot) {
              // 정상 데이터 수신 시 해당 데이터의 title 출력
              if (snapshot.hasData) {
                return Text(snapshot.data.title);
              }
              // 에러 수신 시 에러 메시지 출력
              else if (snapshot.hasError) {
                return Text("${snapshot.error}");
              }
              // 상태처리 인디케이터 표시. 앱 초기에 출력
              return CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

 

본 강좌를 Flutter 공식 사이트의 문서를 참고하여 작성되었습니다.

https://flutter.dev/docs/cookbook/networking/fetch-data

 

Fetch data from the internet

Fetching data from the internet is necessary for most apps.Luckily, Dart and Flutter provide tools, such as the`http` package, for this type of work.This recipe uses the following steps: 1. Add the `http` package. 2. Make a network request using the `http`

flutter.dev

 


Flutter Code Examples 강좌를 추천합니다.

  • 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.
  • Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.
  • 또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

 

Flutter Code Examples - Google Play 앱

Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.

play.google.com

Comments