Chapter 04

Flutterスマホアプリ・MultiPart以外(ファイル項目、画像項目がない)の例-1/4

kazpgm
kazpgm
2022.09.24に更新

ファイル項目、画像項目がない例

■商品情報登録画面 、商品情報更新画面 shohin_register_amend.dart

flutter_app\lib\affairs\shohin\shohin_register_amend.dart

hohin_register_amend.dart
/// 商品情報登録更新画面
class ShohinRegisterAmend extends StatefulWidget {
  final String title;

username:ログインユーザー名
cookies:HTTPで取得したcookies
headers:HTTPで取得したheaders
resData:HTTPで取得したresData

  final String username;
  final Map<String, String> cookies;
  final Map<String, String> headers;
  final String resData;

  const ShohinRegisterAmend({Key? key, required this.title, required this.username,
          required this.headers, required this.cookies, required this.resData}) : super(key: key);
  
  State<ShohinRegisterAmend> createState() => _ShohinRegisterAmendState();
}

class _ShohinRegisterAmendState extends State<ShohinRegisterAmend> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  String _errorSuccessMsg = "";
  late ShohinForm _shohinForm;

商品名の入力項目(TextFormField)は、入力値表示のためTextEditingControllerが必要。

  final TextEditingController _productsnameController = TextEditingController();

_scrollController:画面を先頭に戻す(エラー・成功メッセージ行)ため使用する
_thisTite:initStateメソッドでタイトル(商品情報登録または。商品情報更新)設定してる。

  late ScrollController _scrollController;
  late String _thisTitle;

  
  Widget build(BuildContext context) {

日付は日本にしておく。

    initializeDateFormatting("ja_JP");
    return Scaffold(

      appBar: AppBar(
        title: Text(_thisTitle,
            overflow: TextOverflow.clip,
            style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        centerTitle:true,
        backgroundColor: Colors.green,
        // ログアウト
        actions: <Widget>[
          IconButton(
            onPressed: () async {

ログアウトはCommUtils.onSignOutCommメソッドを使用する。

              String? _msg = await CommUtils.onSignOutComm(
                  context, _scrollController,
                  widget.headers, widget.cookies);
              if (_msg != "") {
                setState(() {
                  _errorSuccessMsg = _msg;
                });
              }
            },
            icon: const Icon(Icons.exit_to_app),
          ),
        ],
      ),
      // 画面に収まりきれないので、ListViewを使う
      body: Form(
        key: _formKey,
        child : ListView(

controller: _scrollControllerとし、画面を先頭に戻す(エラー・成功メッセージ行)ため使用する。

          controller: _scrollController,

bodyはListViewとし、children:_makeWidgetsメソッドとする。
_makeWidgetsメソッドは List<Widget> を戻している。

          children: _makeWidgets(),
       ),
      ),
     );
  }

  // 表示するWidgets
  List<Widget> _makeWidgets() {
    var contentWidgets = List<Widget>.empty(growable: true);
    //var contentWidgets = <Widget>[];
    if (_errorSuccessMsg.isNotEmpty) {
      contentWidgets.add(
        // エラーメッセージ
        Container(

          padding: const EdgeInsets.fromLTRB(20.0, 10, 20.0, 5.0),
          child:Text(_errorSuccessMsg,
            textAlign: TextAlign.center,
            overflow: TextOverflow.clip,
            style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.red),),
        ),
      );
    } else {

      contentWidgets.add(const SizedBox(
        height : 10.0,
      ));
    }
    contentWidgets.add(
      // 商品名
      Container(
        margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
        padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
        decoration: CommUtils.commBoxDecoration(),
        child:Column(

          children:<Widget>[TextFormField(
            controller: _productsnameController,
              decoration: const InputDecoration(
                labelText: "商品名  *",
                hintText: '全角50文字以内',
              ),
            ),
            Text(

HTTPでサーバーから戻ってきた項目エラーを表示する。正常時は高さ0になるので表示されない。

              // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
              _shohinForm.shohinErrForm.productsnameErr.join("\n"),
              textAlign: TextAlign.left,
              overflow: TextOverflow.clip,
              style: TextStyle(height:_shohinForm.shohinErrForm.productsnameErr.isEmpty?0:1.2, fontSize: 12, fontWeight: FontWeight.normal, color: Colors.red),
            ),
          ],
        ),
      )
    );
    contentWidgets.add(Container(
      margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
      padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
      decoration: CommUtils.commBoxDecoration(),
      child:Column (
        children: <Widget>[
          //Wrapによって折り返しするが、SizeBox使って左寄せする
          SizedBox(
            width: double.infinity,
            child:Wrap(
               direction: Axis.horizontal,
              children: <Widget>[

                const Text("業種ID  *",
                  overflow: TextOverflow.clip,
                ),
                DropdownButton(

_shohinForm.publicDbELEMENTSにDBエレメントが入っている。

                  items: CommUtils.getItemsFromELEMENTS(_shohinForm.publicDbELEMENTS(_shohinForm.dbELEMENTS, 'biztypeCd', '---未選択---'), _shohinForm.biztypeCd),
                  value: _shohinForm.biztypeCd,
                  icon: const Icon(Icons.arrow_downward),
                  elevation: 16,
                  style: const TextStyle(color: Colors.deepPurple),
                  underline: Container(
                    height: 2,
                    color: Colors.deepPurpleAccent,
                  ),
                  onChanged: (value) {
                    setState(() {

value as String:value as Stringとしないと、Stringに入れることができないので。

                      _shohinForm.biztypeCd = value as String;
                    });
                  },
                ),
              ]
            ),
          ),
          Text(
            // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
            _shohinForm.shohinErrForm.biztypeCdErr.join("\n"),
            textAlign: TextAlign.left,
            overflow: TextOverflow.clip,
            style: TextStyle(height:(_shohinForm.shohinErrForm.biztypeCdErr.isEmpty)?0:1.2,
                fontSize: 12, fontWeight: FontWeight.normal, color: Colors.red),
          ),
        ]),
      ),
    );
    contentWidgets.add(Container(
      margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
      padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
      decoration: CommUtils.commBoxDecoration(),
      child:Column (
        children: <Widget>[
          //Wrapによって折り返しするが、SizeBox使って左寄せする
          SizedBox(
            width: double.infinity,
            child:Wrap(
               direction: Axis.horizontal,
              children: <Widget>[

                const Text("種類・大分類  *",
                  overflow: TextOverflow.clip,
                ),
                DropdownButton(

_shohinForm.publicDbELEMENTSにDBエレメントが入っている。

                  items: CommUtils.getItemsFromELEMENTS(_shohinForm.publicDbELEMENTS(_shohinForm.dbELEMENTS, 'categoryCd', '---未選択---'), _shohinForm.categoryCd),
                  value: _shohinForm.categoryCd,
                  icon: const Icon(Icons.arrow_downward),
                  elevation: 16,
                  style: const TextStyle(color: Colors.deepPurple),
                  underline: Container(
                    height: 2,
                    color: Colors.deepPurpleAccent,
                  ),
                  onChanged: (value)  async {

入力値が既存のcategoryCdと違う場合、CommUtils .createLrgMidChangeを使用してsubcategoryCdのDBエレメントを取得している。

                    if (_shohinForm.categoryCd != value as String) {
                      // httpアクセス
                      Map<String, dynamic>? map = await CommUtils
                          .createLrgMidChange(
                          context,
                          value,
                          '',
                          '2',
                          '/elements/elem',
                          'ary_lrgmidsml_category',
                          widget.headers,
                          widget.cookies);
                      setState(() {
                        _shohinForm.categoryCd = value;
                        _shohinForm.subcategoryCd = '';
                        _shohinForm.extracategoryCd = '';
                        _shohinForm.dbELEMENTS!['subcategoryCd'] = map ?? {};
                        _shohinForm.dbELEMENTS!['extracategoryCd'] = {};
                      });
                    }
                  },
                ),
              ]
            ),
          ),
          Text(
            // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
            _shohinForm.shohinErrForm.categoryCdErr.join("\n"),
            textAlign: TextAlign.left,
            overflow: TextOverflow.clip,
            style: TextStyle(height:(_shohinForm.shohinErrForm.categoryCdErr.isEmpty)?0:1.2,
                fontSize: 12, fontWeight: FontWeight.normal, color: Colors.red),
          ),
        ]),
      ),
    );
    contentWidgets.add(Container(
      margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
      padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
      decoration: CommUtils.commBoxDecoration(),
      child:Column (
        children: <Widget>[
          //Wrapによって折り返しするが、SizeBox使って左寄せする
          SizedBox(
            width: double.infinity,
            child:Wrap(
               direction: Axis.horizontal,
              children: <Widget>[

                const Text("種類・中分類  *",
                  overflow: TextOverflow.clip,
                ),
                DropdownButton(

_shohinForm.publicDbELEMENTSにDBエレメントが入っている。

                  items: CommUtils.getItemsFromELEMENTS(_shohinForm.publicDbELEMENTS(_shohinForm.dbELEMENTS, 'subcategoryCd', '---未選択---'), _shohinForm.subcategoryCd),
                  value: _shohinForm.subcategoryCd,
                  icon: const Icon(Icons.arrow_downward),
                  elevation: 16,
                  style: const TextStyle(color: Colors.deepPurple),
                  underline: Container(
                    height: 2,
                    color: Colors.deepPurpleAccent,
                  ),
                  onChanged: (value) async {

入力値が既存のsubcategoryCdと違う場合、CommUtils .createLrgMidChangeを使用してextracategoryCd のDBエレメントを取得している。

                    if (_shohinForm.subcategoryCd != value as String) {
                      // httpアクセス
                      Map<String, dynamic>? map = await CommUtils
                          .createLrgMidChange(
                          context,
                          _shohinForm.categoryCd,
                          value,
                          '2',
                          '/elements/elem',
                          'ary_lrgmidsml_category',
                          widget.headers,
                          widget.cookies);
                      setState(() {
                        _shohinForm.subcategoryCd = value;
                        _shohinForm.extracategoryCd = '';
                        _shohinForm.dbELEMENTS!['extracategoryCd'] = map ?? {};
                      });
                    }
                  },
                ),
              ]
            ),
          ),
          Text(
            // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
            _shohinForm.shohinErrForm.subcategoryCdErr.join("\n"),
            textAlign: TextAlign.left,
            overflow: TextOverflow.clip,
            style: TextStyle(height:(_shohinForm.shohinErrForm.subcategoryCdErr.isEmpty)?0:1.2,
                fontSize: 12, fontWeight: FontWeight.normal, color: Colors.red),
          ),
        ]),
      ),
    );
    contentWidgets.add(Container(
      margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
      padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
      decoration: CommUtils.commBoxDecoration(),
      child:Column (
        children: <Widget>[
          //Wrapによって折り返しするが、SizeBox使って左寄せする
          SizedBox(
            width: double.infinity,
            child:Wrap(
              direction: Axis.horizontal,
              children: <Widget>[

                const Text("種類・小分類  *",
                  overflow: TextOverflow.clip,
                ),
                DropdownButton(

_shohinForm.publicDbELEMENTSにDBエレメントが入っている。

                  items: CommUtils.getItemsFromELEMENTS(_shohinForm.publicDbELEMENTS(_shohinForm.dbELEMENTS, 'extracategoryCd', '---未選択---'), _shohinForm.extracategoryCd),
                  value: _shohinForm.extracategoryCd,
                  icon: const Icon(Icons.arrow_downward),
                  elevation: 16,
                  style: const TextStyle(color: Colors.deepPurple),
                  underline: Container(
                    height: 2,
                    color: Colors.deepPurpleAccent,
                  ),
                  onChanged: (value) {
                    setState(() {
                      _shohinForm.extracategoryCd= value as String;
                    });
                  },
                ),
              ]
            ),
          ),
          Text(
            // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
            _shohinForm.shohinErrForm.extracategoryCdErr.join("\n"),
            textAlign: TextAlign.left,
            overflow: TextOverflow.clip,
            style: TextStyle(height:(_shohinForm.shohinErrForm.extracategoryCdErr.isEmpty)?0:1.2,
                fontSize: 12, fontWeight: FontWeight.normal, color: Colors.red),
          ),
        ]),
      ),
    );
    contentWidgets.add(
      // 公開区分
      Container(
      margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
      padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
      decoration: CommUtils.commBoxDecoration(),
      child:Column (
        children: <Widget>[
          //Wrapによって折り返しするが、SizeBox使って左寄せする
          SizedBox(
            width: double.infinity,
            child:Wrap(
               direction: Axis.horizontal,
              children: <Widget>[

                const Text("公開区分  *",
                  overflow: TextOverflow.clip,
                ),
                DropdownButton(

getItemsFromFixELEMENTSに固定エレメントが入っている。

                  items: CommUtils.getItemsFromFixELEMENTS('OPN_KBN', '---未選択---', _shohinForm.openkbn1),
                  value: _shohinForm.openkbn1,
                  icon: const Icon(Icons.arrow_downward),
                  elevation: 16,
                  style: const TextStyle(color: Colors.deepPurple),
                  underline: Container(
                    height: 2,
                    color: Colors.deepPurpleAccent,
                  ),
                  onChanged: (value) {
                    setState(() {
                      _shohinForm.openkbn1 = value as String;
                    });
                  },
                ),
              ]
            ),
          ),
          Text(
            // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
            _shohinForm.shohinErrForm.openkbn1Err.join("\n") + 
            _shohinForm.shohinErrForm.checkOpenkbn1Err.join("\n"),
            textAlign: TextAlign.left,
            overflow: TextOverflow.clip,
            style: TextStyle(height:(_shohinForm.shohinErrForm.openkbn1Err==[] ||
                _shohinForm.shohinErrForm.checkOpenkbn1Err.isEmpty)?0:1.2,
                fontSize: 12, fontWeight: FontWeight.normal, color: Colors.red),
          ),
        ]),
      ),
    );

modeで登録画面、更新画面を切り替えている。

    if (_shohinForm.messageForm.mode == 'ins' || _shohinForm.messageForm.mode == 'ins_do') {
      contentWidgets.add(
        Container(
          margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
          padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
          child: ElevatedButton(
            onPressed: (){httpForInfo('ins_do_or_upd_do');},
            style: ElevatedButton.styleFrom(
              primary: Colors.blue,
            ),

            child: const Text("登録"),
          ),
        ),
      );
    } else {
      contentWidgets.add(
        Row (
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5),
              padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0),
              child: ElevatedButton(
                onPressed: (){httpForInfo('ins_do_or_upd_do');},
                style: ElevatedButton.styleFrom(
                  primary: Colors.blue,
                ),

                child: const Text("登録"),
              ),
            ),
            Container(
              margin: const EdgeInsets.fromLTRB(5.0, 0, 5.0,5),
              padding: const EdgeInsets.fromLTRB(5.0, 0, 5.0, 0),
              child: ElevatedButton(
                onPressed: (){httpForInfo('list_back');},
                style: ElevatedButton.styleFrom(
                  primary: Colors.blue,
                ),
                child: const Text("一覧に戻る"),
              ),
            ),
          ]
        ),
      );
    }

bodyのchildrenに、List<Widget> を戻している。

    return contentWidgets;
  }

情報登録、情報変更、情報一覧へのhttpアクセス

  /// 情報登録、情報変更、情報一覧へのhttpアクセス
  void httpForInfo(String _modeBase) async {
    try {

_shohinForm.fromShohinFormControllerメソッドで、TextFormField値を、Formに設定。補足:TextFormField以外は直接Formを見ている。

      // TextFormField値を、Formに設定。補足:TextFormField以外は直接Formを見ている。
      _shohinForm.fromShohinFormController(
          _productsnameController
      );
      String _mode;
      http.Response response;
      if (_modeBase == 'list_back') {
        _mode = 'list_back';
        SrchOrderFormInMsgForm srchOrderFromInMsgForm = SrchOrderFormInMsgForm.initData();
        String _shohinSrchFormJson = srchOrderFromInMsgForm.toJson(_mode, "${widget.cookies['XSRF-TOKEN']}", _shohinForm.messageForm.page);
        widget.headers["content-type"]= "application/json; charset=UTF-8";
        widget.headers["X-XSRF-TOKEN"]= "${widget.cookies['XSRF-TOKEN']}";
        final url = Uri.parse("${Consts.myHttpUrl}/members/admin/shohin/shohin/" + _mode);

headersでcsrfとJsessionを渡している。
HTTPで、商品情報一覧画面の情報を取得している。

        response = await http.post(url,headers: widget.headers, body: _shohinSrchFormJson);
      } else {
        // ファイル項目、画像項目がない場合の処理
        _mode = (_shohinForm.messageForm.mode == 'ins' || _shohinForm.messageForm.mode == 'ins_do')?'ins_do':'upd_do';
        String _shohinFormJson = _shohinForm.toJson(_mode, "${widget.cookies['XSRF-TOKEN']}", _shohinForm.messageForm.page);
        widget.headers["content-type"]= "application/json; charset=UTF-8";
        widget.headers["X-XSRF-TOKEN"]= "${widget.cookies['XSRF-TOKEN']}";
        final url = Uri.parse("${Consts.myHttpUrl}/members/admin/shohin/shohin/" + _mode);

HTTPで、商品情報登録画面または、商品情報登録画面の情報を取得している。

        response = await http.post(url,headers: widget.headers, body: _shohinFormJson);
      }
      if (response.statusCode != 200) {
        setState(() {

response.statusCodeが200(正常)以外のとき
・ログインが必要な時は "ログインしてください"
・それ以外のステータスの時は”エラーが発生しました ”を表示している。

          int statusCode = response.statusCode;
          if (response.statusCode == 401 || response.statusCode == 403) {
            _errorSuccessMsg = "ログインしてください";
          } else {
            _errorSuccessMsg = "エラーが発生しました $statusCode";
          }
        });
        // 画面を先頭に戻す
        _scrollController.animateTo(0,
            duration: const Duration(milliseconds:600),
            curve: Curves.easeInQuint);
        return;
      }

HTTPヘッダー内容を保存している

      CommUtils.updateCookie(response, widget.cookies, widget.headers);
      // response.bodyをutf8でdecodeする。

HTTPのresponse.bodyをString形式にしている。

      String _resData = utf8.decode(response.body.runes.toList());
      if (kDebugMode) {
        print(_resData);
      }

バックエンドで例外発生の場合MessageFormの値しか戻らないため、ここで確認する。

      // バックエンドで例外発生の場合MessageFormの値しか戻らないため、ここで確認する
      MessageForm _messageForm = MessageForm.initData();
      _messageForm.fromJson(_resData);
      // SpringBootで例外発生の場合
      if (_messageForm.mode =="SystemError") {

エラー画面(Error)を表示している。

        // エラー画面
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => Error(title: widget.title, username: widget.username,
                headers: widget.headers, cookies: widget.cookies, resData: _resData),
          ),
        );
        // 正常処理
      } else {
        if (_messageForm.mode == 'list_back') {

商品情報更新画面で更新完了の時、mode == 'list_back'が戻ってくる。
Navigator.popUntilでメニュー画面に戻って、情報一覧(ShohinList)を表示している。

          // メニュー画面まで戻ってから情報一覧を表示する
          int count = 0;
          Navigator.popUntil(context, (_) => count++ >= 2);
          //情報一覧
          Navigator.of(context).push(
            MaterialPageRoute(
              builder: (context) =>
                  ShohinList(title: widget.title,
                      username: widget.username,
                      headers: widget.headers,
                      cookies: widget.cookies,
                      resData: _resData),
            ),
          );
        } else {
          setState(() {
            _shohinForm.fromJson(_resData);
            // From値を、TextFormFieldに設定する
            _shohinForm.forShohinFormController(
                _productsnameController
            );

商品情報登録画面OKの時は、登録完了メッセージ、商品情報更新画面NGの時は、エラーメッセージが表示される。画面遷移を行わない。

            _errorSuccessMsg =
                _shohinForm.messageForm.errorMessage + _shohinForm.messageForm.successMessage;
            if (_errorSuccessMsg == '' &&
                  _shohinForm.messageForm.itemErrorMessages != '') {
              _errorSuccessMsg = '項目エラーを確認してください';
            }
          });
        }
      }
    } catch (e) {
      setState(() {
        if (kDebugMode) {
          print("エラーが発生しました" + e.toString());
        }

例外が発生した時エラー表示。

        _errorSuccessMsg = "内部エラーが発生しました";
      });
    }

HTTPで取得した内容表示後画面を先頭に戻している。

    // 画面を先頭に戻す
    _scrollController.animateTo(0,
        duration: const Duration(milliseconds:600),
        curve: Curves.easeInQuint);
  }

  
  void initState() {
    super.initState();
    // 画面初期データを設定する
    _shohinForm = ShohinForm.initData();

HTTPで取得した内容を_shohinFormに設定している。

    _shohinForm.fromJson(widget.resData);

商品名の入力項目(TextFormField)は、入力値表示のための_productsnameController(TextEditingController)に設定している。

    // From値を、TextFormFieldに設定する
    _shohinForm.forShohinFormController(
        _productsnameController
    );
    _scrollController= ScrollController();

タイトル(商品情報登録または。商品情報更新)設定してる。

    if (_shohinForm.messageForm.mode == 'ins' || _shohinForm.messageForm.mode == 'ins_do') {
      _thisTitle = '商品情報登録';
    } else {
      _thisTitle = '商品情報更新';
    }
  }

  
  void dispose() {

画面を先頭に戻す(エラー・成功メッセージ行)ため使用している_scrollControllerをdisposeする

    _scrollController.dispose();
    // Clean up the focus node when the Form is disposed.
    super.dispose();
  }
}