kazpgmの日記

『プログラム自動作成@自動生成』作成の日記

フロント側をFlutter(スマホ)Thymeleaf(PC)、バックエンド側SpringBootの自動作成勉強中

8:23
①昨日の”まず、すでに登録画面を作成済みなのでこれを利用して更新画面を作ってみよう。”のつづき
登録画面にはIDがないので、IDを追加したが、SpringBoot側DBのIDはLongなのでFlutter側はintにした。いいのかな?ってところはあるけど、ネットではDartのintは”Dartのintとdoubleは非常に広い範囲で扱えると考えていいでしょう。”とか出てくるのでよさそう。そして、現在、まだ動いていない。
10:05
①「SocketException: OS Error: No route to host, errno = 113, address = 192.168.1.13, port = 53664」が出てスマホ画面のログインが進まない。
 ・スマホ再起動やったがだめ。Android Studio立ち上げなおしたがダメ。・・・たまにこの例外出るけど、スマホ再起動あたりで動くのだけど。と思ってた(20分ぐらいいろいろやって)ら今動いた。何だろう不思議だ。
②たまに以下の例外が出てスマホが動かなくなる。USBさしなおしてやり直せば動くからいいけど。不思議だ。
”Lost connection to device.
adb.exe: device '35...........97' not found”
15:43
①ユーザー登録画面を利用して、ユーザー更新画面を作ったので、「UserRegister.dart」を「UserRegisterAmend.dart」に名前を変更した。
②とりあえず、ユーザー更新画面が動いた。
■Flutter側スマホ画面

↓ID=3を選んで変更ボタン押下。ユーザー情報更新画面が表示される。


↓登録押下でリスト一覧表示になる。更新OKメッセージも表示される。

↓検索押下で検索画面になる。更新OKメッセージも表示される。

■PC側WEB画面

↓ID=3を選んで変更ボタン押下。ユーザー情報更新画面が表示される。

↓登録押下でリスト一覧表示になる。更新OKメッセージも表示される。

■Flutter側プロフラム
・UserList.dartから抜粋

class UserList extends StatefulWidget {
・・・
class _UserListState extends State<UserList> {
・・・
  Widget build(BuildContext context) {
  ・・・
      // 画面に収まり入れないので、ListViewを使う
      body: Form(
        key: _formKey,
        child : ListView(
          controller: _scrollController,
          children: _makeWidgets(),
       ),
      ),
・・・
  }

  List<Widget> _makeWidgets() {
      if (_selectedIndex == 0) {
        return _makeCndsWidgets();
      } else {
        return _makeListWidgets();
      }
  }
・・・
// 一覧表を表示するWidgets
  List<Widget> _makeListWidgets() {
・・・
              Row (
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
・・・
                  Container(
                    margin: const EdgeInsets.fromLTRB(5, 0, 5, 5),
                    padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
                    child: ElevatedButton(
                      // 情報一覧リストへ、upd、httpアクセス
                      onPressed: (){httpForUpd(element["id"]);},
                      style: ElevatedButton.styleFrom(
                        primary: Colors.blue,
                      ),
                      child: const Text("変更"),
                    ),
                  ),
・・・
                ]
              ),
              ],
            )
        ),
        );
      }
    }
    return contentWidgets;
  }
・・・
  /// 情報一覧リストへ、updで、httpアクセス
  void httpForUpd(int _id) {
    httpForInfo("upd", "http://192.168.1.13:8080/members/admin/user/userA/upd", _id);
  }
・・・
  /// 情報詳細、情報変更、情報削除へのhttpアクセス
  void httpForInfo(String _mode, String _url, int _id) async {
    try {
      UserForm _userForm = UserForm.initData();
      _userForm.id = _id;
      String _userFormJson = _userForm.toJson(_mode, "${widget.cookies['XSRF-TOKEN']}", _userSrchForm.page);
      widget.headers["content-type"]= "application/json; charset=UTF-8";
      widget.headers["X-XSRF-TOKEN"]= "${widget.cookies['XSRF-TOKEN']}";
      final url = Uri.parse(_url);
      http.Response response = await http.post(url,headers: widget.headers, body: _userFormJson);
      if (response.statusCode != 200) {
        setState(() {
          int statusCode = response.statusCode;
          _errorSuccessMsg = "エラーが発生しました $statusCode";
        });
        return;
      }
      CommUtils.updateCookie(response, widget.cookies, widget.headers);
      // response.bodyをutf8でdecodeする。
      String _resData = utf8.decode(response.body.runes.toList());
      if (kDebugMode) {
        print(_resData);
      }
      MessageForm messageForm = MessageForm.initData();
      messageForm.fromJson(_resData);
      if (messageForm.mode == 'detail') {
      } else if (messageForm.mode == 'upd') {
        //情報登録
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) =>
                UserRegisterAmend(title: widget.title,
                    username: widget.username,
                    headers: widget.headers,
                    cookies: widget.cookies,
                    resData: _resData),
          ),
        );
        // _mode=='list_back'(エラーになった場合または、削除OK)を想定
      } else {
        setState(() {
          // 検索結果リスト表示
          _selectedIndex = 1;
          _userSrchForm.fromJson(_resData);
          // From値を、TextFormFieldに設定する。補足:TextFormField以外は直接Formを見ている。
          _userSrchForm.forUserSrchFormController(_nameController,
              _emailController
          );
          _errorSuccessMsg =
              _userSrchForm.errorMessage + _userSrchForm.successMessage;
          // 並び順指定があればドロップダウンリストはそれにする。
          if (_userSrchForm.sortItemName == "") {
            _selectItem = 0;
          } else {
            for (int i = 0; i < _selectItems.length; i++) {
              if (_selectItems[i] == _userSrchForm.sortItemName) {
                _selectItem = i;
                break;
              }
            }
          }
        });
      }
    } on Exception catch (ex) {
      setState(() {
        _errorSuccessMsg = "エラーが発生しました" + ex.toString();
      });
    }
  }

・UserRegisterAmend.dartを全部

import 'dart:convert';
import 'dart:ffi';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/affairs/user/userList.dart';
import 'package:flutter_app/form/messageForm.dart';
import 'package:http/http.dart' as http; //httpリクエスト用
import 'dart:async'; //非同期処理用
import '../../main.dart';
import '../../error.dart';
import '../../utils/elements.dart';
import '../../utils/commUtils.dart';
import '../../form/userForm.dart';

class UserRegisterAmend extends StatefulWidget {
  final String title;
  final String username;
  final Map<String, String> cookies;
  final Map<String, String> headers;
  final String resData;

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

class _UserRegisterAmendState extends State<UserRegisterAmend> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  String _errorSuccessMsg = "";
  late UserForm _userForm;
  // TextFormField用Controller
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final TextEditingController _passwordConfirmController = TextEditingController();

  bool _isPasswordObscure = true;
  bool _isPasswordConfirmObscure = true;
  late ScrollController _scrollController;
  late FocusNode _myFocusNode;

  late String _thisTitle;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_thisTitle,
            overflow: TextOverflow.clip,
            style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        centerTitle:true,
        backgroundColor: Colors.green,
    ),
      // 画面に収まり入れないので、ListViewを使う
      body: Form(
        key: _formKey,
        child : ListView(
          controller: _scrollController,
          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,
      ));
    }
    if (_userForm.mode != 'ins' && _userForm.mode != 'ins_do') {
      contentWidgets.add(
        // ID
          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:Text('ID : ' + CommUtils.chgToString(_userForm.id),
              textAlign: TextAlign.left,
              overflow: TextOverflow.clip,
              // 登録の時は表示しない。
              style: TextStyle(height:(_userForm.mode == 'ins' || _userForm.mode == 'ins_do')?0:1.2),
            ),
          )
      );
    }
    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(
              // フォームを含むウィジェットが作成された時点でフォーカスする。
              autofocus: true,
              controller: _nameController,
              focusNode: _myFocusNode,
              decoration: const InputDecoration(
                labelText: "名前",
                hintText: '全角カナ25文字以内',
              ),
            ),
              Text(
                // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
                _userForm.nameErr.join("\n"),
                textAlign: TextAlign.left,
                overflow: TextOverflow.clip,
                style: TextStyle(height:_userForm.nameErr==[]?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>[TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(
                  labelText: "メールアドレス",
                  hintText: '120文字以内',
                ),
              ),
                Text(
                  // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
                  _userForm.emailErr.join("\n"),
                  textAlign: TextAlign.left,
                  overflow: TextOverflow.clip,
                  style: TextStyle(height:_userForm.emailErr==[]?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>[TextFormField(
              controller: _passwordController,
              decoration:  InputDecoration(
                labelText: "パスワード",
                hintText: '半角英数記号16文字以内',
                /* ここからアイコンの設定 */
                suffixIcon: IconButton(
                  icon: Icon(_isPasswordObscure ? Icons.visibility_off : Icons.visibility),
                  // アイコンがタップされたら現在と反対の状態をセットする
                  onPressed: () {
                    setState(() {
                      _isPasswordObscure = !_isPasswordObscure;
                    });
                  },
                ),
                /* ここまで */
              ),
              obscureText: _isPasswordObscure,  // パスワードが見えないようにする
              maxLength: 16,  // 入力可能な文字数
            ),
              Text(
                // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
                _userForm.passwordErr.join("\n"),
                textAlign: TextAlign.left,
                overflow: TextOverflow.clip,
                style: TextStyle(height:_userForm.passwordErr==[]?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>[TextFormField(
              controller: _passwordConfirmController,
              decoration: InputDecoration(
                labelText: "パスワード確認",
                hintText: '半角英数記号16文字以内',
                /* ここからアイコンの設定 */
                suffixIcon: IconButton(
                  icon: Icon(_isPasswordConfirmObscure ? Icons.visibility_off : Icons.visibility),
                  // アイコンがタップされたら現在と反対の状態をセットする
                  onPressed: () {
                    setState(() {
                      _isPasswordConfirmObscure = !_isPasswordConfirmObscure;
                    });
                  },
                ),
                /* ここまで */
              ),
              obscureText: _isPasswordConfirmObscure,  // パスワードが見えないようにする
              maxLength: 16,  // 入力可能な文字数
            ),
              Text(
                // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
                _userForm.passwordConfirmErr.join("\n"),
                textAlign: TextAlign.left,
                overflow: TextOverflow.clip,
                style: TextStyle(height:_userForm.passwordConfirmErr==[]?0:1.2,fontSize: 12, fontWeight: FontWeight.normal, color: Colors.red),
              ),
              Text(
                // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
                _userForm.checkPasswordPasswordConfirmErr.join("\n"),
                textAlign: TextAlign.left,
                overflow: TextOverflow.clip,
                style: TextStyle(height:_userForm.checkPasswordPasswordConfirmErr==[]?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>[Column(
              // エレメントがnullということはないので、!(bang operator)を使用し、コンパイルエラーを取り除いた。
              children: Elements.elements["LOGIN_TYPE"]!.map((e) =>
              //チェックボックス
              CheckboxListTile(
                  activeColor: Colors.orange,
                  title: const Text('ログイン権限'),
                  subtitle: Text(e[1]),
                  controlAffinity: ListTileControlAffinity.leading,
                  value: _userForm.rolesArray.contains(e[0]),
                  onChanged: (value) {
                    setState(() {
                      CommUtils.handleCheckbox(_userForm.rolesArray, e[0]);
                    });
                  }
              )
              ).toList(),
            ),
              Text(
                // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
                _userForm.rolesArrayErr.join("\n"),
                textAlign: TextAlign.left,
                overflow: TextOverflow.clip,
                style: TextStyle(height:_userForm.rolesArrayErr==[]?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>[Column(
              // エレメントがnullということはないので、!(bang operator)を使用し、コンパイルエラーを取り除いた。
              children: Elements.elements["ENABLE_FLG"]!.map((e) =>
              //ラジオボタン
              RadioListTile(
                  title: Text("可否フラグ " + e[1]),
                  value: e[0],
                  groupValue: _userForm.enableFlag.toString(),
                  onChanged: (value) {
                    setState(() {
                      _userForm.enableFlag = e[0];
                    });
                  }
              )
              ).toList(),
            ),
              Text(
                // HTTPで返却されたエラーメッセージを表示する。(エラーなしはText高=0にしている)
                _userForm.enableFlagErr.join("\n"),
                textAlign: TextAlign.left,
                overflow: TextOverflow.clip,
                style: TextStyle(height:_userForm.enableFlagErr==[]?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),
          child: ElevatedButton(
            onPressed: () async {
              try {
                // TextFormField値を、Formに設定。補足:TextFormField以外は直接Formを見ている。
                _userForm.fromUserFormController(_nameController,
                    _emailController,
                    _passwordController,
                    _passwordConfirmController
                );
                String _actionDo = (_userForm.mode == 'ins' || _userForm.mode == 'ins_do')?'ins_do':'upd_do';
                String _userFormJson = _userForm.toJson(_actionDo, "${widget.cookies['XSRF-TOKEN']}");
                widget.headers["content-type"]= "application/json; charset=UTF-8";
                final url = Uri.parse("http://192.168.1.13:8080/members/admin/user/userA/" + _actionDo);
                http.Response response = await http.post(url,headers: widget.headers, body: _userFormJson);
                _isPasswordObscure = true;
                _isPasswordConfirmObscure = true;
                if (response.statusCode != 200) {
                  setState(() {
                    int statusCode = response.statusCode;
                    if (response.statusCode == 401) {
                      _errorSuccessMsg = "ログインしてください";
                    } else {
                      _errorSuccessMsg = "エラーが発生しました $statusCode";
                    }
                  });
                  // 画面を先頭に戻す
                  _scrollController.animateTo(0,
                      duration: const Duration(milliseconds:600),
                      curve: Curves.easeInQuint);
                  return;
                }
                CommUtils.updateCookie(response, widget.cookies, widget.headers);
                // response.bodyをutf8でdecodeする。
                String _resData = utf8.decode(response.body.runes.toList());
                if (kDebugMode) {
                  print(_resData);
                }
                // バックエンドで例外発生の場合MessageFormの値しか戻らないため、ここで確認する
                MessageForm _messageForm = MessageForm.initData();
                _messageForm.fromJson(_resData);
                // SpringBootで例外発生の場合
                if (_messageForm.mode =="SystemError") {
                  // エラー画面
                  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') {
                    // メニュー画面まで戻ってから情報一覧を表示する
                    //Navigator.of(context).pop();
                    int count = 0;
                    Navigator.popUntil(context, (_) => count++ >= 2);
                    //情報一覧
                    Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (context) =>
                            UserList(title: widget.title,
                                username: widget.username,
                                headers: widget.headers,
                                cookies: widget.cookies,
                                resData: _resData),
                      ),
                    );
                  } else {
                    setState(() {
                      _userForm.fromJson(_resData);
                      // From値を、TextFormFieldに設定する。補足:TextFormField以外は直接Formを見ている。
                      _userForm.forUserFormController(_nameController,
                          _emailController,
                          _passwordController,
                          _passwordConfirmController
                      );
                      _errorSuccessMsg =
                          _userForm.errorMessage + _userForm.successMessage;
                    });
                  }
                }
              } on Exception catch (e) {
                setState(() {
                  _errorSuccessMsg = "エラーが発生しました" + e.toString();
                });
              }
              // 画面を先頭に戻す
              _scrollController.animateTo(0,
                  duration: const Duration(milliseconds:600),
                  curve: Curves.easeInQuint);
              _myFocusNode.requestFocus();
            },
            style: ElevatedButton.styleFrom(
              primary: Colors.blue,
            ),
            child: const Text("登録"),
          ),
        )
    );
    return contentWidgets;
  }

  @override
  void initState() {
    super.initState();
    // 画面初期データを設定する
    _userForm = UserForm.initData();
    _userForm.fromJson(widget.resData);
    // From値を、TextFormFieldに設定する
    _userForm.forUserFormController(_nameController,
        _emailController,
        _passwordController,
        _passwordConfirmController
    );
    _scrollController= ScrollController();
    _myFocusNode = FocusNode();
    if (_userForm.mode == 'ins' || _userForm.mode == 'ins_do') {
      _thisTitle = 'ユーザー情報登録';
    } else {
      _thisTitle = 'ユーザー情報更新';
    }
  }

  @override
  void dispose() {
    _scrollController.dispose();
    _myFocusNode.dispose();
    // Clean up the focus node when the Form is disposed.
    super.dispose();
  }
}

■SpringBootバックエンドがわのController抜粋
・PC・スマホ向け共通Controller

public class UserCommController {
・・・
	/**
	 * ユーザー情報登録共通処理(Flutter、PC・WEB共用)
	 * ユーザー情報登録する共通処理
	 *
	 * @param userFForm Flutter向けユーザー情報+ページ番号
	 * @param id ユーザー情報ID
	 * @param page page
	 * @param model モデル
	 * @param flutterFlg true:Flutter用 false:PC・WEB用
	 * @return Flutter用:jsonデータ PC・WEB用:遷移先
	 */
	@SuppressWarnings("unchecked")
	protected Object userUpdComm(UserFForm userFForm, String id, String page, Model model, boolean flutterFlg) {
		model.addAttribute("mode", "upd");
		model.addAttribute("page", page);
		UserForm userForm = new UserForm();
		try {
			//ユーザー情報を取得する
			userForm= userService.findByPkForUserForm(Long.valueOf(id));
		} catch(Exception e){
			if (e.getMessage() != null && e.getMessage().equals("invalid pk")) {
				model.addAttribute("successMessage", "『id』=" + userForm.getId() +"は既に削除されています");
			} else {
		        e.printStackTrace();
		        log.error("エラーが発生しました", e);
				model.addAttribute("errorMessage", "エラーが発生しました");
			}
			Pageable pageable = PageRequest.of(Integer.parseInt(page), pageableDefaultSize);
			userListBackSub(model, pageable);
			if (flutterFlg) {
				UserSrchFForm userSrchFForm=new UserSrchFForm();
				userSrchFForm.set_csrf(userFForm.get_csrf());
				//同一プロパティ(型名まで同じもの)コピー
				BeanUtils.copyProperties(this.sessionUserSrchForm.getUserSrchForm(), userSrchFForm);
				BeanUtils.copyProperties(this.sessionUserSrchOrderForm.getSrchOrderForm(), userSrchFForm);
				Map<String, Object> modelMap = model.asMap();
				userSrchFForm.setPage(((Page)modelMap.get("page")).getNumber() + 1);
				// Flutter向け検索条件+ソート条件+ページ番号
				model.addAttribute("userSrchFForm", userSrchFForm);
				model.addAttribute("mode", "list_back");
			}
			return returnComm("/members/admin/user/userList", model, null, flutterFlg, "userSrchFForm");
		}
		if (flutterFlg) {
			//同一プロパティ(型名まで同じもの)コピー
			BeanUtils.copyProperties(userForm, userFForm);
			model.addAttribute("userFForm", userFForm);
		} else {
			model.addAttribute("userForm", userForm);
		}
		return returnComm("/members/admin/user/userAmend", model, null, flutterFlg, "userFForm");
	}

	/**
	 * ユーザー情報登録共通処理(Flutter、PC・WEB共用)
	 * ユーザー情報登録する共通処理
	 *
	 * @param userFForm Flutter向けユーザー情報+ページ番号
	 * @param userForm ユーザー情報登録
	 * @param result チェック結果
	 * @param page page
	 * @param model モデル
	 * @param flutterFlg true:Flutter用 false:PC・WEB用
	 * @return Flutter用:jsonデータ PC・WEB用:遷移先
	 */
	protected Object userUpdDoComm(UserFForm userFForm, UserForm userForm, BindingResult result, String page, Model model, boolean flutterFlg) {
		//エラーになったときのモードを設定
		model.addAttribute("mode", "upd");
		model.addAttribute("page", page);
		if (flutterFlg) {
			userForm=(UserForm)userFForm;
			// Flutter向け情報+ページ番号
			model.addAttribute("userFForm", userFForm);
		}
		if (result.hasErrors()) {
			model.addAttribute("itemErrorMessages", result.toString());
			return returnComm("/members/admin/user/userAmend", model, result, flutterFlg, "userFForm");
		}
	    
		try {
			//ロールを文字列にする
			String[] roles = userForm.getRolesArray();
			userService.update(userForm.getId(), userForm.getName(), userForm.getEmail(), userForm.getPassword(), roles, userForm.getEnableFlag());
		} catch(Exception e){
			if (e.getMessage() != null && e.getMessage().equals("invalid pk")) {
				result.rejectValue("id", "validation.already-deleted", new String[] {"『id』"}, "");
			} else if (e.getMessage() != null && e.getMessage().equals("invalid email")) {
				result.rejectValue("email", "validation.already-registered", new String[] {"『メールアドレス』"}, "");
			} else {
	            e.printStackTrace();
	            log.error("エラーが発生しました", e);
				model.addAttribute("errorMessage", "エラーが発生しました");
			}
			return returnComm("/members/admin/user/userAmend", model, result, flutterFlg, "userFForm");
		}
		  
		Pageable pageable = PageRequest.of(Integer.parseInt(page), pageableDefaultSize);
		model.addAttribute("successMessage", "ユーザー情報更新(" + "『id』=" + userForm.getId() + ")が完了しました");
		userListBackSub(model, pageable);
		if (flutterFlg) {
			UserSrchFForm userSrchFForm=new UserSrchFForm();
			userSrchFForm.set_csrf(userFForm.get_csrf());
			//同一プロパティ(型名まで同じもの)コピー
			BeanUtils.copyProperties(this.sessionUserSrchForm.getUserSrchForm(), userSrchFForm);
			BeanUtils.copyProperties(this.sessionUserSrchOrderForm.getSrchOrderForm(), userSrchFForm);
			Map<String, Object> modelMap = model.asMap();
			userSrchFForm.setPage(((Page)modelMap.get("page")).getNumber() + 1);
			// Flutter向け検索条件+ソート条件+ページ番号
			model.addAttribute("userSrchFForm", userSrchFForm);
			model.addAttribute("mode", "list_back");
		}
		return returnComm("/members/admin/user/userList", model, result, flutterFlg, "userSrchFForm");
	}

	/**
	 * userListBackSubメソッド
	 * ユーザー情報リスト一覧表示サブ処理
	 * 
	 * @param model モデル
	 * @param pageable ページ
	 */
	protected void userListBackSub(Model model, Pageable pageable) {
		userListSub(this.sessionUserSrchForm.getUserSrchForm(), this.sessionUserSrchOrderForm.getSrchOrderForm(), model, pageable);
		model.addAttribute("userSrchForm", this.sessionUserSrchForm.getUserSrchForm());
		model.addAttribute("srchOrderForm", this.sessionUserSrchOrderForm.getSrchOrderForm());
	}

スマホ向けController

public class UserFlutterController extends UserCommController {
・・・
	//=======================================================
	// ユーザー情報更新
	//=======================================================
	/**
	 * ユーザー情報更新表示処理
	 * ユーザー情報更新を表示する処理
	 *
	 * @param id ユーザー情報ID
	 * @param mode モード
	 * @param page page
	 * @param model モデル
	 * @return jsonデータ
	 */
	@SuppressWarnings("unchecked")
	@PostMapping("/members/admin/user/userA/upd")
	@ResponseBody	
	public Map<String, Object>  userUpdFlutter(@RequestBody UserFForm userFForm,
			Model model) {
		String id = userFForm.getId().toString();
		return (Map<String, Object>)userUpdComm(userFForm, id, userFForm.getPage().toString(), model, true);
	}
	  
	/**
	 * ユーザー情報更新処理
	 * ユーザー情報更新する処理
	 *
	 * @param userFForm Flutter向けユーザー情報+ページ番号
	 * @param result チェック結果
	 * @param model モデル
	 * @return jsonデータ
	 */
	@SuppressWarnings("unchecked")
	@PostMapping("/members/admin/user/userA/upd_do")
	@ResponseBody	
	public  Map<String, Object> userUpdDoFlutter(@RequestBody @Validated(GroupOrder.class) UserFForm userFForm,
			BindingResult result,
			Model model) {
		return ( Map<String, Object>)userUpdDoComm(userFForm, null, result, Integer.toString(userFForm.getPage()-1), model, true);
	}

・PC向けController

public class UserPcController extends UserCommController {
・・・
	//=======================================================
	// ユーザー情報更新
	//=======================================================
	/**
	 * ユーザー情報更新表示処理
	 * ユーザー情報更新を表示する処理
	 *
	 * @param id ユーザー情報ID
	 * @param mode モード
	 * @param page page
	 * @param model モデル
	 * @return 遷移先
	 */
	@PostMapping(params="mode=upd")
	public String userUpd(
			@RequestParam("id") String id,
			@RequestParam("mode") String mode,
			@RequestParam("page") String page,
			Model model) {
		return (String)userUpdComm(null, id, page, model, false);
	}
	  
	/**
	 * ユーザー情報更新処理
	 * ユーザー情報更新する処理
	 *
	 * @param userForm ユーザー情報登録
	 * @param result チェック結果
	 * @param mode モード
	 * @param page page
	 * @param model モデル
	 * @return 遷移先
	 */
	@PostMapping(params="mode=upd_do")
	public String userUpdDo(@Validated(GroupOrder.class)  UserForm userForm,
			BindingResult result,
			@RequestParam("mode") String mode,
			@RequestParam("page") String page,
			Model model) {
		return (String)userUpdDoComm(null, userForm, result, page, model, false);
	}

■2022/06/15に、勉強した成果:『Flutter_JavaSpringプログラム自動作成◎自動生成ツール』をVectorに載せました。Zenn本も書きました。使ってみての感想や間違いの指定や、こうやったほうがいいとかの情報があればメールください。
Vector
www.vector.co.jp
・Zenn本(Flutter_JavaSpringプログラム自動作成)
zenn.dev