見出し画像

HTTP GETでREST APIにアクセスする

HTTP GETでREST APIにアクセスするHTTPクライアントクラスを作ります。
サーバがJSON形式のデータを返すことを想定します。
HTTPクライアントはWidgetが共通で利用するためシングルトンクラスにします。

実際に開発する時は、サーバ側も同時並行で開発することがよくあります。
そのため、サーバにアクセスしないでテストするためのスタブも実装します。
スタブとは、一言でいうとダミーデータを返す代用プログラムのことです。

httpパッケージのインストール

まずhttpをインストールします。pubspec.yamlのdependenciesブロックにhttp:を追加します。

⚠yamlファイルはインデントの位置も意味がありますので注意
dependencies:
 flutter:
   sdk: flutter

 cupertino_icons: ^0.1.2
 http:

追加したらターミナルでコマンドを実行してインストールします。またはAndroid Studioの場合はボタンが自動的に表示されるのでそれをクリックするだけでOKです。

flutter packages get

スタブデータの準備

実際にHTTPアクセスするサイトはjsonplaceholderを利用させてもらいました。スタブデータもそれと同じモデルを作成しました。

適当なdartファイルにスタブデータだけを書いておき、利用する側でimportすることにします。

const STUB_MODE = false;

const stubPostsResponse = '''
[
 {
   "userId": 1,
   "id": 1,
   "title": "title1",
   "body": "Test body1."
 },
 {
   "userId": 1,
   "id": 2,
   "title": "title2",
   "body": "Test body2. Test body2."
 },
 {
   "userId": 1,
   "id": 3,
   "title": "title3",
   "body": "Test body3. Test body3. Test body3."
 }
]
''';

HTTPクライアントの実装

HTTPリクエストをコンソールにprintしたいのでhttp.BaseClientを継承してsend()を@overrideします。また、ここでHTTPヘッダを変更することができます。
実際のサーバアクセスもスタブもFuture<http.Response>を返します。
スタブの場合はFuture.delayed()で5秒後にダミーデータを返すようにします。

class SampleService extends http.BaseClient {
 static SampleService _instance;

 final _inner = http.Client();

 factory SampleService() => _instance ??= SampleService._internal();

 SampleService._internal();

 @override
 Future<http.StreamedResponse> send(http.BaseRequest request) async {
   request.headers['User-Agent'] = 'Sample Flutter App.';
   print('----- API REQUEST ------');
   print(request.toString());
   if (request is http.Request && request.body.length > 0) {
     print(request.body);
   }

   return _inner.send(request);
 }

 /// APIコール
 Future<http.Response> getPosts() async {
   if (STUB_MODE) {
     // スタブ
     final res = http.Response(stubPostsResponse, 200, headers: {
       HttpHeaders.contentTypeHeader: 'application/json; charset=utf-8'
     });
     return Future.delayed(const Duration(seconds: 5), () => res);
   } else {
     // APIサーバアクセス
     final url = 'https://jsonplaceholder.typicode.com/posts';
     return get(url);
   }
 }
}

JSONデータのデシリアライズ

JSONデータをList<Post>にデシリアライズしたいので、まずPostクラスを作成します。

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

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

 // Named constructor
 Post.fromJson(Map<String, dynamic> json) {
   userId = json['userId'];
   id = json['id'];
   title = json['title'];
   body = json['body'];
 }
}

使う側ではこんな感じで実装すればList<Post>が作成できますね。

final list = json.decode(response.body);
final posts = list.map((post) => Post.fromJson(post)).toList();

一覧画面の作成

最後にStatefulWidgetのinitState()でデータを取得してデシリアライズする処理を実装します。
List<Post> postsはPostの配列です。
ListView.builder()で一覧画面を実現します。

import 'dart:convert';
import 'dart:io';

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
     ),
     home: PostsListScreen('タイトル'),
   );
 }
}

class PostsListScreen extends StatefulWidget {
 final title;

 PostsListScreen(this.title);

 @override
 _PostsListScreenState createState() => _PostsListScreenState();
}

class _PostsListScreenState extends State<PostsListScreen> {
 List<Post> posts = [];

 @override
 void initState() {
   SampleService().getPosts().then((response) {
     final list = json.decode(response.body);
     if (list is List) {
       setState(() {
         posts = list.map((post) => Post.fromJson(post)).toList();
       });
     }
   });
   super.initState();
 }

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(widget.title),
     ),
     body: Container(
       padding: EdgeInsets.all(10),
       child: ListView.builder(
         padding: EdgeInsets.all(10),
         itemCount: posts.length,
         itemBuilder: (context, int index) {
           return Column(children: <Widget>[
             ListTile(title: Text(posts[index].body)),
             Divider(height: 2.0, color: Colors.grey),
           ]);
         },
       ),
     ),
   );
 }
}

コード全体

まとめ

今回はかんたんにHTTPクライアントを実装してみました。

画面表示はsetState()を使っていますが、State managementの観点で理想の型ではありません。

また、実際の開発現場ではPostのようなモデルクラスをたくさん作成する場合がありますが、json_serializableパッケージを使うと楽に実装できます。それはまた別の機会に。

この記事が気に入ったらサポートをしてみませんか?