Chapter 03

Flutterのプロジェクトを作る

JboyHashimoto
JboyHashimoto
2022.10.12に更新

サーバー側からデータを取得するアプリを作成👩‍🔧

IDEはお好みのものを使ってください。筆者はAndroid Studioで作成しています。
まずは、プロジェクト作成する。アプリ名はhttp_mock_appにしました。
状態管理は、flutter_riverpodで行なっております。アプリのアーキテクチャーは、modelディレクトリにJSONのデータを取得して表示するためのロジックとなるクラスと関数を定義しています。
domainディレクトリには、modelに定義した関数を呼び出すFutureProviderを定義しています。

ディレクトリのツリー構造

lib
├── domain
│   └── books_provider.dart
├── main.dart
└── model
    └── model.dart

JSONのデータを扱うためのロジックを書いたmodel.dart

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

/// [mocker-serverから、データを取得するクラスを定義]
/// [JSONのデータの中のオブジェクトと同じ名前にする]
class BookList {
  final int id; // idは数字なのでint型
  final String title; // titleは文字なのでstring型
  final String author; // authorは文字なのでstring型

  // コンストラクターを定義
  BookList({required this.id, required this.title, required this.author});

  // 新しいインスタンスを作成し、jsonを解析してデータを
  // 新しいインスタンスに配置します。(公式を翻訳)
  BookList.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        title = json['title'],
        author = json['author'];

  // インスタンスをマップに変換するtoJson()メソッド
  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
        'author': author,
      };
}

/// [BookListを型に使いモックサーバーからデータを取得する関数.]
List<BookList> parseBook(String responseBody) {
  // 引数をキャストしてMap型に変換
  final parsed = convert.jsonDecode(responseBody).cast<Map<String, dynamic>>();
  // 配列をmapメソッドでループさせる。
  return parsed.map<BookList>((json) => BookList.fromJson(json)).toList();
}

// インターネットからデータを取得するメソッド
Future<List<BookList>> fetchPosts() async {
  const endpoint = 'http://localhost:3000/bookList/';
  var url = Uri.parse(endpoint);

  var response = await http.get(url);
  if (response.statusCode == 200) {
    // ステータスコードを表示する
    print('ステータスコード: ${response.statusCode}');
    // JSONのデータを表示する
    print('JSONのデータ:  ${response.body}');
    return compute(parseBook, response.body);
  } else {
    throw Exception('Failed to load book');
  }
}

JSONのデータのオブジェクトと同じ名前の変数をクラスの中に書いて、モデルを定義します。httpのパッケージを使って、JSONのデータをデコード(もとのデータに戻す)して、Widgetで表示できるようにします。
JSONのデータが取得できているか確認できるように、公式のリファレンスに書いてある通りのprint文を書いてデバッグできるようにしました。

// ステータスコードを表示する
print('ステータスコード: ${response.statusCode}');
// JSONのデータを表示する
print('JSONのデータ:  ${response.body}');

buildすると、logにステータスコードの状態と、mock-serverから取得したデータを全て表示することができるようになります。

Syncing files to device iPhone 13...
flutter: ステータスコード: 200
flutter: JSONのデータ:  [
  {
    "id": 1,
    "title": "BookListSample",
    "author": "Kboy"
  },
  {
    "id": 2,
    "title": "フリーランスの生き方",
    "author": "Daigo"
  },
  {
    "id": 3,
    "title": "ノマドになってどうなった?",
    "author": "Taisei"
  },
  {
    "id": 4,
    "title": "Flutter別荘の日常",
    "author": "fen"
  },
  {
    "id": 5,
    "title": "ソフトウェアエンジニアリング",
    "author": "イルカ"
  },
  {
    "id": 6,
    "title": "起業家の生き方",
    "author": "yuto315"
  },
  {
    "id": 7,
    "title": "リードエンジニアとは?",
    "author": "あっぷる中谷"
  },
  {
    "id": 8,
    "title": "人生とは何か?",
    "author": "JboyHashimoto"
  }
]
Application finished.

アプリで使う非同期の関数を呼び出すProvider

今回は、Riverpodで状態管理をしたいと思いましてflutter_riverpodを使いました。非同期のデータを扱いたいときは、FutureProviderというProviderを使います。
model.dartで作ったサーバー側からデータを取得する関数を呼び出します。

https://riverpod.dev/ja/docs/providers/future_provider/

domain/books_provide.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http_mock_app/model/model.dart';

// 自動でデータを再取得するよう設定するFutureProvider.
final booksProvider = FutureProvider<List<BookList>>((ref) async {
  // model.dartの関数を呼び出す.
  return fetchPosts();
});

画面にサーバーのデータを表示する👩‍💻

今回は、FutureProviderとwhenを使って画面にデータを描画します。
whenメソッドについてのリファレンスはこちら
https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValueX/when.html

日本語に翻訳してみたらこんなことが書かれていた。

R の 場合< R >(
{必要な R データ(
T データ
)、
必要な R エラー(
オブジェクト エラー、
スタック トレース
)、
必要な R 読み込み()}
)

AsyncValueの状態に基づいてアクションを実行します。

すべてのケースが必須であり、null 非許容値を返すことができます。

実装の仕方

R when<R>({
  required R Function(T data) data,
  required R Function(Object error, StackTrace stackTrace) error,
  required R Function() loading,
}) {
  return map(
    data: (d) => data(d.value),
    error: (e) => error(e.error, e.stackTrace),
    loading: (l) => loading(),
  );
}

今回は、mapメソッドは別ファイルに書かれているので書かなくて良い.

/// [BookListを型に使いモックサーバーからデータを取得する関数.]
List<BookList> parseBook(String responseBody) {
  // 引数をキャストしてMap型に変換
  final parsed = convert.jsonDecode(responseBody).cast<Map<String, dynamic>>();
  // 配列をmapメソッドでループさせる。
  return parsed.map<BookList>((json) => BookList.fromJson(json)).toList();
}

アプリを実行するmain.dart

ref.watchでProviderを呼び出してmain.dartで使えるようにする。
ListView.builderで取得したJSONのデータを画面に描画して表示します。

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http_mock_app/domain/books_provider.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    // このページで使用するriverpodを呼び出す変数を定義
    final value = ref.watch(booksProvider);

    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('mock-serverからデータを習得する'),
          backgroundColor: Colors.pinkAccent,
        ),
        body: Center(
          child: value.when(
              data: (books) {
                return ListView.builder(
                  // 配列のデータを描画するWidget
                  itemCount: books.length, // 配列の数をカウント
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(books[index].title), // 配列のtitleプロパティを表示
                      subtitle: Text(books[index].author), // 配列のauthorプロパティを表示
                    );
                  },
                );
              },
              // データが習得できなかったら、ローディングされる.
              error: (err, stack) => Center(child: Text(err.toString())),
              loading: () => const Center(child: CircularProgressIndicator())),
        ),
      ),
    );
  }
}

buildして画面に表示されていれば成功です.