[読書会]インターネット経由でデータを更新する

(当頁はdocs.flutter.dev(https://docs.flutter.dev/cookbook/networking/update-data)のリファレンスブログです。)

 アプリの多くは、インターネットを通じてデータを更新する機能が必要です。
http パッケージを使用してこの操作を簡単に実現できます。

1.httpパッケージの追加

 (前述「[読書会]インターネット経由でデータを更新する」と同様)

2.インターネット経由でデータを更新する

 ここでは、http.put() メソッドを使用してサーバ上のアルバムタイトルを更新する方法を示しています。
(具体的には、updateAlbum() メソッドを通じて新しいタイトル JSONPlaceholder API送信します。)

2.インターネット経由でデータを更新する:
//サーバー上の既存アルバムデータを更新する関数
Future<Album> updateAlbum(String title) async {
  final response = await http.put(
    //データ更新用の API エンドポイント
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      //JSON データを送信することを指定
      'Content-Type': 'application/json; charset=UTF-8',
    },
    //更新するタイトルを JSON に変換して送信
    body: jsonEncode(<String, String>{
      'title': title,
    }),
  );

  //statusCode==200 の場合
  //( レスポンスをパースして Album オブジェクトを生成 )
  if (response.statusCode == 200) {
    return Album.fromJson(
      jsonDecode(response.body) as Map<String, dynamic>
    );
  } else {
    //statusCode==200 以外の場合
    //( 例外をスロー )
    throw Exception('Failed to update album.');
  }
}
  1. 非同期処理の管理
    • http.put() は非同期操作であるため、結果は Future として返されます。
      Future を利用して、操作が完了したときにデータを取得したり、エラーを処理したりします。
  2. リソースの更新
    • http.put() を使うと、既存のデータ(リソース)を完全に置き換えることができます。
      更新対象のリソース(アルバム)の URL を指定し、新しいデータ(タイトル)を送信します。
  3. JSON データの送信
    • 更新するデータは JSON 形式で送信します。
      jsonEncode を使用して Dart のマップオブジェクトを JSON に変換しています。

3.http.Response をカスタムDart オブジェクトに変換する

 (前述「[読書会]インターネット経由でデータを更新する」と同様)

4.ユーザ入力から既存のタイトルを更新する

 ここでは、ユーザーが入力したタイトルを取得してサーバー上のデータを更新する仕組みを解説します。
以下の要素を使って実現します。

  1. TextField
    • ユーザが新しいタイトルを入力するための入力欄。
  2. ElevatedButton
    • 入力データをサーバに送信するためのボタン。
  3. TextEditingController
    • ユーザが入力した値を管理し、TextField からデータを取得します。
4.ユーザ入力から既存のタイトルを更新する:
//データが正常に取得された場合
return Column(
  mainAxisAlignment: MainAxisAlignment.center, //縦方向の中央揃え
  children: <Widget>[
    Text(snapshot.data!.title), //現在のアルバムタイトルを表示
      TextField(
        controller: _controller, //ユーザー入力を管理するコントローラー
        decoration: const InputDecoration(
        hintText: 'Enter Title', //入力欄のプレースホルダー
      ),
    ),
    ElevatedButton(
      onPressed: () {
        //ボタンが押されたときに更新処理を実行
        setState(() {
          //入力されたタイトルを送信
          _futureAlbum = updateAlbum(_controller.text);
        });
      },
      child: const Text('Update Data'), //ボタンのラベル
    ),
  ],
);

5.応答画面を表示する

 FutureBuilder ウィジェットを使用して、非同期データ(サーバーからのレスポンス)を画面に表示します。
このウィジェットを使うことで、データの状態(ロード中、成功、エラー)に応じた適切な UI を簡単に構築できます。

 (詳細は、前述「[読書会]インターネット経由でデータを更新する」と同様)

5.応答画面を表示する:
FutureBuilder<Album>(
  future: _futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    } else if (snapshot.hasError) {
      return Text('${snapshot.error}');
    }

    return const CircularProgressIndicator();
  },
);

6.サンプルコード

Dart
import 'dart:async'; //非同期処理(Future)をサポートするライブラリ
import 'dart:convert'; //JSON データを扱うためのライブラリ

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; //HTTP リクエストライブラリ

//サーバーから既存のアルバムデータを取得する関数
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse(
      //データ取得用の API エンドポイント
      'https://jsonplaceholder.typicode.com/albums/1'
    ),
  );

  if (response.statusCode == 200) {
    //statusCode==200 の場合
    //( レスポンスをパースして Album オブジェクトを生成 )
    return Album.fromJson(
      //response.body:
      //( この取得した全データを jsonDecode で Map型に変換する )
      jsonDecode(response.body) as Map<String, dynamic>
    );
  } else {
    //statusCode==200 以外の場合
    //( 例外をスロー )
    throw Exception('Failed to load album');
  }
}

//サーバー上の既存アルバムデータを更新する関数
Future<Album> updateAlbum(String title) async {
  final response = await http.put(
    //データ更新用の API エンドポイント
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      //JSON データを送信することを指定
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'title': title, //更新する title だけを JSON に変換して送信
    }),
  );

  if (response.statusCode == 200) {
    //statusCode==200 の場合
    //( レスポンスをパースして Album オブジェクトを生成 )
    return Album.fromJson(
      jsonDecode(response.body) as Map<String, dynamic>
    );
  } else {
    //statusCode==200 以外の場合
    //( 例外をスロー )
    throw Exception('Failed to update album.');
  }
}

//アルバムデータを表現するクラス
class Album {
  final int id; //アルバムの ID
  final String title; //アルバムのタイトル

  //コンストラクタ
  const Album({required this.id, required this.title});

  //ファクトリコンストラクタ
  //( JSON データを Dart の Album オブジェクトに変換する。
  //(1)新しいインスタンスを返さない場合がある:
  // ・通常のコンストラクタ(Album(...))は常に新しいインスタンスを作成しますが、
  //    ファクトリコンストラクタは既存のインスタンスを返すこともできます。
  //    例えば、キャッシュから既存のインスタンスを返すといった動作が可能です。
  //(2)カスタムロジックを含められる
  //  ・オブジェクトを作成する前に複雑なロジックを実行できます。
  //    例えば、引数に基づいて異なる型のオブジェクトを返すなどの処理ができます。)
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {
        'id': int id, //JSON の "id" フィールドを取得
        'title': String title, //JSON の "title" フィールドを取得
      } =>
      Album(
        id: id,       //取得した "id" をフィールドに割り当て
        title: title, //取得した "title" をフィールドに割り当て
      ),
      //JSON データが期待する形式でない場合は例外をスロー
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}

//Flutter アプリケーションのエントリーポイント
void main() {
  runApp(const MyApp());
}

//アプリケーションのメインウィジェット
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() {
    return _MyAppState();
  }
}

//MyApp の状態を管理するクラス
class _MyAppState extends State<MyApp> {
  //テキスト入力用のコントローラー
  final TextEditingController _controller = TextEditingController();
  //サーバーから取得したアルバムデータを保持する Future
  late Future<Album> _futureAlbum;

  @override
  void initState() {
    super.initState();
    _futureAlbum = fetchAlbum(); //アプリ起動時にアルバムデータを取得
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Update Data Example', //アプリのタイトル
      theme: ThemeData(
        //テーマカラー
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.deepPurple
        ),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Update Data Example'), //アプリバーのタイトル
        ),
        body: Container(
          alignment: Alignment.center, //中央揃え
          padding: const EdgeInsets.all(8), //コンテナのパディング
          child: FutureBuilder<Album>(
            future: _futureAlbum, //サーバーからのレスポンスを監視
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                if (snapshot.hasData) {
                  //データが正常に取得された場合
                  return Column(
                    //縦方向の中央揃え
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      //現在のアルバムタイトルを表示
                      Text(snapshot.data!.title),
                      TextField(
                        //ユーザー入力を管理するコントローラー
                        controller: _controller,
                        decoration: const InputDecoration(
                          //入力欄のプレースホルダー
                          hintText: 'Enter Title',
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () {
                          //ボタンが押されたときに更新処理を実行
                          setState(() {
                            //入力されたタイトルを送信
                            _futureAlbum = updateAlbum(
                              _controller.text
                            );
                          });
                        },
                        child: const Text('Update Data'), //ボタンのラベル
                      ),
                    ],
                  );
                } else if (snapshot.hasError) {
                  //エラーが発生した場合
                  return Text(
                    //エラーメッセージを表示
                    '${snapshot.error}'
                  );
                }
              }

              //データ取得中はローディングスピナーを表示
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}

コメントを残す