FlutterでPOSTデータを投げてデータ登録する

前回、FlutterでAPIを叩いてデータを取得するという記事を掲載しました。今回は、FlutterでPOSTデータを送信してデータ登録を行うところをやってみたいと思います。

前回のソースからの続きなので、前回分も参照して行われると分かりやすいと思います。

=> 前回の記事はこちらから

 

1.main.dart の変更

まずは、データ登録のための画面遷移を行いたいと思いますので、前回のソース(main.dart)を改修します。

import 'dart:async'; //非同期処理用
import 'dart:convert'; //httpレスポンスをJSON形式に変換用

import 'package:flutter/material.dart'; //google提供のUIデザイン
import 'package:http/http.dart' as http;

// 変更①
import 'post.dart';

void main() {
  runApp(MaterialApp(
    // 変更②router追加
    initialRoute: '/',
    routes: {
      '/': (context) => HomePage(),
      '/postpage': (context) => PostPage(),
    },
  ));
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late Map data;
  late List userData;

  Future getData() async {
    final response =
        await http.get(Uri.parse("<GET API URI>"));

    final body = json.decode(response.body);
    final articles = body.map((dynamic item) => item).toList();
    setState(() {
      userData = articles;
    });
  }

  @override
  void initState() {
    super.initState();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("sample API"),
          backgroundColor: Colors.green,
        ),
        body: ListView.builder(
          itemCount: userData == null ? 0 : userData.length,
          itemBuilder: (BuildContext context, int index) {
            return Card(
              child: Row(
                children: <Widget>[
                  CircleAvatar(
                    child: Text(
// 変更③
                      "${userData[index]["event_name"]}",
                      style: TextStyle(color: Colors.black),
                    ),
                  ),
 // 変更④
                  Text(
                    "${userData[index]["event_name"]} ${userData[index]["date_and_time"]}",
                    textAlign: TextAlign.center,
                  ),
                  // 変更⑤画面遷移用のボタンを追加
                  RaisedButton(
                    onPressed: () {
                      Navigator.pushNamed(context, '/postpage');
                    },
                    child: Text(
                      '参加する',
                    ),
                    color: Colors.green,
                  ),
                ],
              ),
            );
          },
        ));
  }
}す

少し解説していきます。

 

// 変更① 
import 'post.dart';

この部分は、新たに作成するページ(CLASS)を読み込んでいます。

 

// 変更②router追加 
initialRoute: '/', 
routes: 
{ '/': (context) => HomePage(), 
  '/postpage': (context) => PostPage(), 
}, 
)
);

この部分は見ればなんとなくわかるかと思いますが、routerの役割に変更しました。

 

child: Text(
                      "${userData[index]["event_name"]}",
                      style: TextStyle(color: Colors.black),
                    ),

変更③と④は、出力データを変更しました(見栄えの問題なのであまり気にしなくてもいいかも)。

// 画面遷移用のボタンを追加
                  RaisedButton(
                    onPressed: () {
                      Navigator.pushNamed(context, '/postpage');
                    },
                    child: Text(
                      '参加する',
                    ),
                    color: Colors.green,
                  ),

変更⑤の部分はボタンクリックで画面遷移を行うように変更しました。

ここでちょっとだけ覚えておきたいのは、routerを変更①のように記述した場合には、Navigator.pushではなく、Navigator.pushNamedにする必要があるということです。

 

一旦このようにmain.dartを変更したら、変更①のあたりでエラーが生じていると思います。

それもそのはずで、まだpost.dartにclassを作成していないからです。

それではここかからが本題で、内容に入っていきたいと思います。

 

2.post.dartを作成

まず、post.dartというファイルを作成します。

 

お約束ですが、importするモジュールを呼び出します。

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

これはmain.dartと一緒です。

 

先にソース全体をお見せしたいと思います。

import 'dart:convert';

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

class PostPage extends StatelessWidget {
  // Formを使うために必ず必要
  final _formKey = GlobalKey<FormState>();
  // データを取得するために必要
  // String email = '';
  final EmailFieldController = TextEditingController();

  // 画面を作っていきます。
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Post Page'),
        ),
        body: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              TextFormField(
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  filled: true,
                  icon: Icon(Icons.person),
                  hintText: '名前を入力してください。',
                  labelText: '名前',
                ),
                controller: EmailFieldController,
                // validator: (value) {
                //   if (value is Null) {
                //     return '必須です。';
                //   }
                //   return null;
                // },
                // 三項演算子だとこうなる
                validator: (value) =>
                    value is Null || value.isEmpty ? '必須です' : null,
                // onSaved: (value) => () {
                //   // var email = value!;
                //   print('$value');
                // },
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 16.0),
                child: RaisedButton(
                  onPressed: () async {
                    if (_formKey.currentState!.validate()) {
                      // post APIを投げる
                      final String url = "<YOUR POST API URI>";
                      Map<String, String> headers = {
                        'content-type': 'application/json'
                      };
                      String body =
                          json.encode({'email': EmailFieldController.text});
                      http.Response resp = await http.post(Uri.parse(url),
                          headers: headers, body: body);
                      if (resp.statusCode != 201) {
                        int statusCode = resp.statusCode;
                        var content = "Failed to post $statusCode";
                        return;
                      }
                      var content = resp.body;
                    }
                  },
                  child: Text('Submit'),
                ),
              ),
            ],
          ),
        ));
  }
}

 

初学の頃に陥りやすいところも記述しています。

・フォームの入力値の取得

例えば、

入力値を取得するためにプロパティを設定する以下の部分

// データを取得するために必要
  // String email = '';

こんな感じで記述をすることが多いのではないでしょうか。

これだと入力値を取得することができません。

入力値を取得するためには、

final EmailFieldController = TextEditingController();

と記述して、TextEditingControllerを使用するのです。そうすると、値を取得する時には、

EmailFieldController.text

として、変数名.textで取得することができるようになります。

※ EmailなのにTextEditingControllerで取得しています。ツッコミはあると思いますが、適宜の方法に置き換えてください。

・validation部分の書き方

// validator: (value) {
                //   if (value is Null) {
                //     return '必須です。';
                //   }
                //   return null;
                // },

 

公式サイトでもこのような記述がされています。

ですが、このくらいであれば三項演算子で記述した方がソースが短くて可読性も上がるかと思います。

 

・レスポンスの受け取り

if (resp.statusCode != 201) {
                        int statusCode = resp.statusCode;
                        var content = "Failed to post $statusCode";
                        return;
                      }
                      var content = resp.body;

レスポンスの受け取りでステータスをどのように返却するのかは仕様によって変わってくると思います。その場合は、ステータスコードを書き換える必要があると思います。

 

3.その他注意点

開発するときのバージョンには注意してください。

現在のdart、Flutterのバージョン(2021/6/27)ではNull Safetyにより、以下の書き方はエラーとなります。

validator: (value){
                  if (value.isEmpty){
                    return '必須です';
                  }
                  return null;
                },

この場合、valueがnullの可能性があるのです。そのため、if文の中の条件式を以下のようにする必要があります。

validator: (value) {
                   if (value is Null) {
                     return '必須です。';
                   }
                   return null;
                 },

 

 

同じことがonPressの際にも生じます。

if (_formKey.currentState.validate()) {
                      // post APIを投げる

これだと、_formKey.currentStateでエラーになると思います。

条件式を以下のように変更します。

if (_formKey.currentState!.validate()) {
                      // post APIを投げる

単純に「!」を入れただけですが、これでNull Safetyになるということです。

swiftを経験されたことがあればなんとなくわかるかと思います。

 

 

 

こうすると、うまくいくかと思います。

後は、登録時の挙動(画面遷移など)を記述していけば問題なく出来上がりますね。

 

4.全体APP

こんな感じで画面が出ていればOKかなと思います。

株式会社ハードコアFlutter GETデータ画面
株式会社ハードコアFlutter POSTデータ画面
株式会社ハードコアFlutter POSTデータ画面
株式会社ハードコアFlutter POSTデータ画面

 

 

Flutter を使ったアプリ開発(APIでデータ取得とその表示)

IOS、Androidアプリを一括で開発するという要求に応えてくれるFlutter。

弊社ではこのFlutterを使用してスマホ対応のアプリ開発を行っています。

 

今回は、そんなFlutterを使用したアプリ開発、特にAPIでデータを取得し表示するところ(いわゆるget)の部分をご紹介していきます。

 

まずは、必要なライブラリーを入れていきます。

公式サイトはこちら

 

 

私の場合は、flutterへのパスを通していないので、展開先から呼び出します。

/Users/<USRNAME>/development/flutter/bin/flutter pub add http

pabspec.ymlに以下が追加されていれば成功です。

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  http: ^0.13.3

次に、必要なモジュール群を読み込みます。

import 'package:flutter/material.dart'; 
import 'package:http/http.dart' as http; // API通信
import 'dart:async'; //非同期処理用
import 'dart:convert'; //httpレスポンスをJSON形式に変換用

 

そしたら後は書いていくだけです。今回はmain.dartに記述していきます。

まずは、お決まりの

void main() {
  // main.dartは基本これだけ
  runApp(MaterialApp(
    home: HomePage(),
  ));
}

ルーティングについては別箇所に記述する方法もありますが、今回はAPIを試すだけなので一旦これで。

 

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

void mainで呼ばれているHomePageクラスを定義しています。ここでは更に_HomePageStateを見にいくようにしました。

class _HomePageState extends State<HomePage> {
  late Map data;
  late List userData;

  Future getData() async {
    //Future xxx async{} という記法
    final response =
        await http.get(Uri.parse("<リクエストを投げるURL>"));
    final body = json.decode(response.body);
        // Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'Map<dynamic, dynamic>' エラーに対処
    final articles = body.map((dynamic item) => item).toList();
    setState(() {
      userData = articles;
    });
  }

  // 非同期処理は、デフォルトでは呼び出し元は処理の完了を待ちませんが、
  // await キーワードをつけると完了を待つことができる。
  @override
  void initState() {
    super.initState();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("sample API"),
          backgroundColor: Colors.green,
        ),
        body: ListView.builder(
          //スクロール可能な可変リストを作る
          itemCount: userData == null ? 0 : userData.length, //受け取る数の定義
          itemBuilder: (BuildContext context, int index) {
            //ここに表示したい内容をindexに応じて表示させる
            return Card(
              //cardデザインを定義:material_design
              child: Row(
                children: <Widget>[
                  CircleAvatar(
                      child: Text(
// 今回はevent_nameとdate_and_timeを表示 ApI responseに応じて変換してください。
                    "${userData[index]["event_name"]} ${userData[index]["date_and_time"]}",
                    style: TextStyle(color: Colors.black),
                  )
                      )
                ],
              ),
            );
          },
        ));
  }
}

 

 

長くなってしまいましたが、こんな感じで進めていきます。

+++ 補足 +++

API開発をしていると、日本語が文字化けをするという問題が生じてしまうことがあります。

この問題を解決するためには、APIレスポンスを受け取る側(今回はFlutter側)で対応するケースと、API側(今回はDjangoのRestFrameWork)で対応するケースとがあります。

Flutter側で対応しようとすると毎度同じようなことでソースを書かないといけないので、API側で対応するのが良いかと思います。

ちなみに、DjangoのRestFrameWorkで日本語も文字化けが起こる時には、以下のように対応します。

from rest_framework.renderers import JSONRenderer

class UTF8CharsetJSONRenderer(JSONRenderer):
    charset = 'utf-8'

こんなファイルをアプリ側などにおき、

settings.pyに以下を追加あるいは変更します。

'DEFAULT_RENDERER_CLASSES': (
    '<YOUR APP PATH>.<上記のファイル名>.UTF8CharsetJSONRenderer',
)

 

これで日本語の文字化けもなく表示されるようになります。

 

 

今回は、FlutterからAPIで情報を取得する方法について書いていきました。

次回は、FlutterからAPI経由でPOSTするケースについて書いていこうと思います。