kazpgmの日記

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

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

14:22
2022年2月9日で中断していたので、過去どんなことやったか、この日記で思い出している。
2月9日に「現在自動作成できているPC側WEBのThymeleafを残して、そのSpringBootロジックをそのまま使って、スマホ(Flutter)を作れないかな?するとPC・WEBからもスマホからも同じロジックで処理できる。ので、管理が楽になるはず。」と書いてあるので、これに向かって調査していたことになる。
DELLのノートPCのFlutterプログラムを、どの辺まで作っていたか確認している。
■main.dartはこんな感じに書いていた。←メインプログラム。ログイン画面

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; //httpリクエスト用
import 'dart:async'; //非同期処理用
import 'home.dart';
import 'utils/commUtils.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'あなたのPGMタイトル管理システム',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'あなたのPGMタイトル管理システム'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Map? data= <String, String>{};
  List? userData = <String>[];
  String _errorSuccessMsg = "";
  String _username = "";  // 入力されたメールアドレス
  String _password = "";  // 入力されたパスワード
  bool _isPasswordObscure = true;
  final Map<String, String> _cookies  = {};
  final Map<String, String> _headers  = {"content-type": "text/html"};

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title,
          overflow: TextOverflow.clip,
        ),
        backgroundColor: Colors.green,
      ),
      body: Center(
      // Center is a layout widget. It takes a single child and positions it
      // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // ログイン失敗時のエラーメッセージ
            Container(
              padding: const EdgeInsets.fromLTRB(20.0, 10, 20.0, 5.0),
              child:Text(_errorSuccessMsg,
                textAlign: TextAlign.left,
                overflow: TextOverflow.clip,
                style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.red),),
            ),
            // メールアドレスの入力フォーム
            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:TextFormField(
                  initialValue: _username,
                  decoration: const InputDecoration(
                      labelText: "ユーザーID(メールアドレス)"
                  ),
                  onChanged: (String value) {
                    _username = value;
                  },
                )
            ),

            // パスワードの入力フォーム
            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:TextFormField(
                decoration: InputDecoration(
                  labelText: "パスワード",
                  /* ここからアイコンの設定 */
                  suffixIcon: IconButton(
                     icon: Icon(_isPasswordObscure ? Icons.visibility_off : Icons.visibility),
                     // アイコンがタップされたら現在と反対の状態をセットする
                     onPressed: () {
                       setState(() {
                         _isPasswordObscure = !_isPasswordObscure;
                       });
                     },
                   ),
                   /* ここまで */
                  ),
                obscureText: _isPasswordObscure,  // パスワードが見えないようにする
                maxLength: 16,  // 入力可能な文字数
                onChanged: (String value) {
                  _password= value;
                },
              ),
            ),
            ElevatedButton(
              onPressed: () async {
                getData();
              },
              style: ElevatedButton.styleFrom(
                primary: Colors.blue,
              ),
              child: const Text("ログイン"),
            ),
          ],
        ),
      ),
    );
  }

  Future getData() async{
    try {
      CommUtils.updateCookie(null, _cookies, _headers);
      final url = Uri.parse('http://192.168.1.13:8080/login?username=$_username&password=$_password');
      http.Response response = await http.post(url, headers: _headers);
      if (response.statusCode != 200) {
        setState(() {
          //int statusCode = response.statusCode;
          _errorSuccessMsg = "ユーザーID(メールアドレス)かパスワードが正しくありません";
        });
        return;
      }
      setState(() { //状態が変化した場合によばれる
        CommUtils.updateCookie(response, _cookies, _headers);
        _errorSuccessMsg = "logon OK";
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => Home(title: widget.title, username: _username,
                headers: _headers, cookies: _cookies),
          ),
        );
        //戻ってきた時の処理
        _errorSuccessMsg = "";
        _isPasswordObscure = true;
      });
    } on Exception catch (e) {
      print(e);
      setState(() {
        _errorSuccessMsg = "エラーが発生しました" + e.toString();
      });
    }
  }
}

■commUtils.dartはこんな感じに書いていた。←共通ユーティリティ

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; //httpリクエスト用

/*
 * 共通ユーティリティクラス
 */
class CommUtils {
  //----------------------------------------
  // 画面の共通部品
  //----------------------------------------
  //システム共通BoxDecorationを返却する
  static BoxDecoration commBoxDecoration() {
    return BoxDecoration(
      // 黒線で囲む
      border: Border.all(
        color: const Color(0xff000000),
        width: 1,
      ),
      // 角
      //borderRadius: BorderRadius.circular(8),
    );
  }

  // http.Responseからクッキーを抽出し、cookiesとheadersに値を設定し返却する
  static void updateCookie(http.Response? response,
                        Map<String, String> cookies,
                        Map<String, String> headers) {
    // Flutterからのリクエスト判定用クッキー設定。
    cookies["fromFlutter"] = "fromFlutter";
    if (response != null ) {
      // レスポンスからクッキー設定
      String? rawCookie = response.headers['set-cookie'];
      if (rawCookie != null) {
        var setCookies = rawCookie.split(',');
        for (var setCookie in setCookies) {
          var _cookies = setCookie.split(';');
          for (var _cookie in _cookies) {
            _setCookie(_cookie, cookies);
          }
        }
      }
    }
    headers['cookie'] = _generateCookieHeader(cookies);
  }

  // cookiesに値を設定し返却する。updateCookieのサブ関数
  static void _setCookie(String rawCookie,
                        Map<String, String> cookies) {
    if (rawCookie.isNotEmpty) {
      var _keyValue = rawCookie.split('=');
      if (_keyValue.length == 2) {
        var key = _keyValue[0].trim();
        var value = _keyValue[1];
        // ignore keys that aren't cookies
        if (key == 'path' || key == 'expires') {
          return;
        }
        cookies[key] = value;
      }
    }
  }

  // headers用のcookieを返却する。updateCookieのサブ関数
  static String _generateCookieHeader(Map<String, String> cookies) {
    String _cookie = "";
    for (var key in cookies.keys) {
      if (_cookie.isNotEmpty) {
        _cookie += ";";
      }
      _cookie += key + "=" + cookies[key]!;
    }
    return _cookie;
  }

  // チェックボックスで、選択された要素をリストに保管する
  static void handleCheckbox(List <String>arrayData,String role) {
      // 選択が解除されたらリストから消す
      if (arrayData.contains(role)) {
        arrayData.remove(role);
      } else {
        // 選択されたらリストに追加する
        arrayData.add(role);
      }
      if (arrayData.isEmpty) {
        // 何も選択されていないとき
        arrayData = <String>[];
        return;
      }
  }
}

■elements.dartはこんな感じに書いていた。←プルダウンリスト、チェックボタンなどのエレメント

/*
 * 固定エレメントクラス
 */
class Elements {

  //----------------------------------------
  // 固定エレメント
  //----------------------------------------
  static final Map<String, List<List<String>>> elements = {
    // 可否フラグ
    "ENABLE_FLG": [
      ["true", "有効"],
      ["false", "無効"],
    ],
    // ログイン権限
    "LOGIN_TYPE": [
      ["ROLE_USER", "ROLE_USER"],
      ["ROLE_ADMIN", "ROLE_ADMIN"],
    ],
  };
}

■home.dartはこんな感じに書いていた。←メニュー画面

import 'dart:convert';

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

class Home extends StatefulWidget {
  final String title;
  final String username;
  final Map<String, String> cookies;
  final Map<String, String> headers;
  const Home({Key? key, required this.title, required this.username,
          required this.headers, required this.cookies}) : super(key: key);
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  String _errorSuccessMsg = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('メニュー',
            overflow: TextOverflow.clip,
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        centerTitle:true,
        backgroundColor: Colors.green,
        actions: <Widget>[
          IconButton(
            onPressed: () => _onSignOut(),
            icon: const Icon(Icons.exit_to_app),
          ),
        ],
      ),
      body:ListView(
//      body:Center(
//      child:Column(
//          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            // ログアウト失敗時のエラーメッセージ
            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),),
            ),
            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 {
                    UserForm _userForm = UserForm.initData();
                    String _userFormJson = _userForm.toJson("ins", "${widget.cookies['XSRF-TOKEN']}");
                    widget.headers["content-type"]= "application/json; charset=UTF-8";
                    widget.headers["X-XSRF-TOKEN"]= "${widget.cookies['XSRF-TOKEN']}";
                    final url = Uri.parse("http://192.168.1.13:8080/members/admin/user/userA/ins");
                    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);
                    }
                    //ユーザー情報登録
                    Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (context) => UserRegister(title: widget.title, username: widget.username,
                              headers: widget.headers, cookies: widget.cookies, resData: _resData),
                      ),
                    );
                    setState(() {
                      _errorSuccessMsg = "";
                    });
                  } on Exception catch (e) {
                    setState(() {
                      _errorSuccessMsg = "エラーが発生しました" + e.toString();
                    });
                  }
                },
                child: const Text("ユーザー情報登録"),
              ),
            ),
          ],
        ),
//      ),
    );
  }

  Future _onSignOut() async{
    try {
      widget.headers["content-type"]= "text/html";
      final url = Uri.parse("http://192.168.1.13:8080/logout?_csrf=${widget.cookies['XSRF-TOKEN']}");
      http.Response response = await http.post(url, headers: widget.headers);
      if (response.statusCode != 200) {
        setState(() {
          int statusCode = response.statusCode;
          _errorSuccessMsg = "エラーが発生しました $statusCode";
        });
        return;
      }
      //最初のページ(ログイン画面)に戻る
      Navigator.popUntil(context, (route) => route.isFirst);
    } on Exception catch (e) {
      setState(() {
        _errorSuccessMsg = "エラーが発生しました";
      });
    }
  }
}

■userRegister.dartはこんな感じに書いていた←ユーザー登録画面

import 'dart:convert';
import 'dart:ffi';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/form/messegeForm.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 UserRegister extends StatefulWidget {
  final String title;
  final String username;
  final Map<String, String> cookies;
  final Map<String, String> headers;
  final String resData;

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

class _UserRegisterState extends State<UserRegister> {
  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;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ユーザー情報登録',
            overflow: TextOverflow.clip,
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        centerTitle:true,
        backgroundColor: Colors.green,
    ),
      // 画面に収まり入れないので、ListViewを使う
      body: Form(
        key: _formKey,
        child : ListView(
          controller: _scrollController,
          children: <Widget>[
            // エラーメッセージ
            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),),
            ),
            // 名前の入力フォーム
            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),
                ),
                ],
              ),
            ),
            // メールアドレスの入力フォーム
            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),
                  ),
              ]
              ),
            ),
            // パスワードの入力フォーム
            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),
                ),
                ],
              ),
            ),
            // パスワード確認の入力フォーム
            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),
                  ),
                ],
              ),
            ),
            // ロールの入力フォーム
            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),
                ),
                ],
              ),
            ),
            // 可否フラグの入力フォーム
            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),
                ),
                ],
              ),
            ),
            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.fromController(_nameController,
                        _emailController,
                        _passwordController,
                        _passwordConfirmController
                    );
                    String _userFormJson = _userForm.toJson("ins_do", "${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/ins_do");
                    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 {
                      setState(() {
                        _userForm.fromJson(_resData);
                        // From値を、TextFormFieldに設定する。補足:TextFormField以外は直接Formを見ている。
                        _userForm.forController(_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("登録"),
              ),
            ),
          ],
       ),
      ),
     );
  }

  @override
  void initState() {
    super.initState();
    // 画面初期データを設定する
    _userForm = UserForm.initData();
    _userForm.fromJson(widget.resData);
    // From値を、TextFormFieldに設定する
    _userForm.forController(_nameController,
        _emailController,
        _passwordController,
        _passwordConfirmController
    );
    _scrollController= ScrollController();
    _myFocusNode = FocusNode();
  }

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

}

■messageForm.dartはこんな感じに書いていた。←画面データのbaseクラス

import 'dart:convert';

import 'dart:ffi';

import 'package:flutter/cupertino.dart';

class MessageForm {
  //プロパティ 実行結果メッセージ用
  late String itemErrorMessages; // 項目エラーメッセージ
  late String errorMessage; // 実行結果エラーメッセージ
  late String successMessage; // 実行結果OKメッセージ
  //そのた
  late String mode; // モード

  //コンストラクタ
  // Dartでは「コンストラクタ名.任意名称」で複数のコンストラクタを定義する
  // 「MessageForm(this.name,・・・);」コンストラクタはいらないので,
  // これだけにする。
  // 初期値のクラスを作成している
  MessageForm.initData() {
    //実行結果メッセージ用
    itemErrorMessages = "";
    errorMessage = "";
    successMessage = "";
    //そのた
    mode = "";
  }

  // HTTPレスポンスデータを反映する
  fromJson(String resData) {
    Map<String,dynamic> _resDataMap =
              jsonDecode(resData);
    //実行結果メッセージ用
    itemErrorMessages = _resDataMap["itemErrorMessages"] ?? "";
    errorMessage = _resDataMap["errorMessage"] ?? "";
    successMessage = _resDataMap["successMessage"] ?? "";
    //そのた
    mode = _resDataMap["mode"] ?? "";
  }
}

■userForm.dartはこんな感じに書いていた。←ユーザー画面データ

import 'dart:convert';
import 'dart:ffi';
import 'package:flutter/cupertino.dart';
import 'package:flutter_app/form/messegeForm.dart';

class UserForm extends MessageForm{
  //プロパティ 入出力データ用
  late String name; // 名前
  late String email; // メールアドレス
  late String password; // パスワード
  late String passwordConfirm; // パスワード確認
  late List<String> rolesArray; // パスワード確認
  // バックエンド(SpringBoot)のFormでBooleanだけどStringで処理する
  late String enableFlag; // パスワード確認
  //プロパティ エラーメッセージ用
  late List<String> nameErr; // 名前
  late List<String> emailErr; // メールアドレス
  late List<String> passwordErr; // パスワード
  late List<String> passwordConfirmErr; // パスワード確認
  late List<String> checkPasswordPasswordConfirmErr; // パスワード、パスワード確認の相関チェック
  late List<String> rolesArrayErr; // ロール
  late List<String> enableFlagErr; // 可否フラグ
//  //プロパティ 実行結果メッセージ用
//  late String itemErrorMessages; // 項目エラーメッセージ
//  late String errorMessage; // 実行結果エラーメッセージ
//  late String successMessage; // 実行結果OKメッセージ
//  //そのた
//  late String mode; // モード

  //コンストラクタ
  // Dartでは「コンストラクタ名.任意名称」で複数のコンストラクタを定義する
  // 「UserForm(this.name,・・・);」コンストラクタはいらないので,
  // これだけにする。
  // 初期値のクラスを作成している
  UserForm.initData() : super.initData() {
    //入出力データ用
    name = "";
    email = "";
    password = "";
    passwordConfirm = "";
    rolesArray = <String>[];
    enableFlag = "";
    //エラーメッセージ用
    nameErr = <String>[];
    emailErr = <String>[];
    passwordErr = <String>[];
    passwordConfirmErr = <String>[];
    checkPasswordPasswordConfirmErr = <String>[];
    rolesArrayErr = <String>[];
    enableFlagErr = <String>[];
//    //実行結果メッセージ用
//    itemErrorMessages = "";
//    errorMessage = "";
//    successMessage = "";
//    //そのた
//    mode = "";
  }

  // HTTPレスポンスデータを反映する
  @override
  fromJson(String resData) {
    Map<String,dynamic> _resDataMap =
              jsonDecode(resData);
    //入出力データ用
    if (_resDataMap["resForm"]==null) {
      name = "";
      email = "";
      password = "";
      passwordConfirm = "";
      rolesArray = <String>[];
      enableFlag = "";
    } else {
      name = _resDataMap["resForm"]["name"] ?? "";
      email = _resDataMap["resForm"]["email"] ?? "";
      password = _resDataMap["resForm"]["password"] ?? "";
      passwordConfirm = _resDataMap["resForm"]["passwordConfirm"] ?? "";
      //jsonから読み込んだときのエラー(”type 'List<dynamic>' is not a subtype of type 'List<String>'”)に
      // 対応し、".cast<String>()"でキャストした
      rolesArray = _resDataMap["resForm"]["rolesArray"]!=null?_resDataMap["resForm"]["rolesArray"].cast<String>():<String>[];
      enableFlag = "";
      if (_resDataMap["resForm"]["enableFlag"] != null) {
        if (_resDataMap["resForm"]["enableFlag"]) {
          enableFlag = "true";
        } else {
          enableFlag = "false";
        }
      }
    }
    //エラーメッセージ用
    if (_resDataMap["resErrorData"]==null) {
      nameErr = <String>[];
      emailErr = <String>[];
      passwordErr = <String>[];
      passwordConfirmErr = <String>[];
      checkPasswordPasswordConfirmErr = <String>[];
      rolesArrayErr = <String>[];
      enableFlagErr = <String>[];
    } else {
      nameErr = _resDataMap["resErrorData"]["name"]!=null?_resDataMap["resErrorData"]["name"].cast<String>():<String>[];
      emailErr = _resDataMap["resErrorData"]["email"]!=null?_resDataMap["resErrorData"]["email"].cast<String>():<String>[];
      passwordErr = _resDataMap["resErrorData"]["password"]!=null?_resDataMap["resErrorData"]["password"].cast<String>():<String>[];
      passwordConfirmErr = _resDataMap["resErrorData"]["passwordConfirm"]!=null?_resDataMap["resErrorData"]["passwordConfirm"].cast<String>():<String>[];
      checkPasswordPasswordConfirmErr = _resDataMap["resErrorData"]["checkPasswordPasswordConfirm"]!=null?_resDataMap["resErrorData"]["checkPasswordPasswordConfirm"].cast<String>():<String>[];
       rolesArrayErr = _resDataMap["resErrorData"]["rolesArray"]!=null?_resDataMap["resErrorData"]["rolesArray"].cast<String>():<String>[];
      enableFlagErr = _resDataMap["resErrorData"]["enableFlag"]!=null?_resDataMap["resErrorData"]["enableFlag"].cast<String>():<String>[];
    }
    //実行結果メッセージ用
//    itemErrorMessages = _resDataMap["itemErrorMessages"] ?? "";
//    errorMessage = _resDataMap["errorMessage"] ?? "";
//    successMessage = _resDataMap["successMessage"] ?? "";
    //そのた
//    mode = _resDataMap["mode"] ?? "";
    super.fromJson(resData);
  }

  // http.postのbodyで使用するJsonデータを返却する
  String toJson(String _mode, String _csrf) => json.encode({
   'name': name,
   'email': email,
   'password': password,
   'passwordConfirm': passwordConfirm,
   'rolesArray': rolesArray,
   'enableFlag': enableFlag,
   'mode': _mode,
   '_csrf': _csrf,
  });

  // From値を、TextFormFieldに設定する
  forController(
      TextEditingController nameController,
      TextEditingController emailController,
      TextEditingController passwordController,
      TextEditingController passwordConfirmController
      ) {
    nameController.text = name;
    emailController.text = email;
    passwordController.text = password;
    passwordConfirmController.text = passwordConfirm;
  }

  // TextFormField値を、Fromに設定する
  fromController(
      TextEditingController nameController,
      TextEditingController emailController,
      TextEditingController passwordController,
      TextEditingController passwordConfirmController
      ) {
    name = nameController.text;
    email = emailController.text;
    password = passwordController.text;
    passwordConfirm = passwordConfirmController.text;
  }
}

■error.dartはこんな感じに書いていた。←SpringBootで例外発生したときの画面

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; //httpリクエスト用
import 'dart:async'; //非同期処理用
import 'form/messegeForm.dart';
import 'form/userForm.dart';
import 'main.dart';
import 'affairs/user/userRegister.dart';
import '../../utils/commUtils.dart';

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

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

class _ErrorState extends State<Error> {
  String _errorSuccessMsg = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('エラー発生',
            overflow: TextOverflow.clip,
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        centerTitle:true,
        backgroundColor: Colors.green,
        actions: <Widget>[
          IconButton(
            onPressed: () => _onSignOut(),
            icon: const Icon(Icons.exit_to_app),
          ),
        ],
      ),
      body:Center(
        child:Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            // ログアウト失敗時のエラーメッセージ
            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),),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    // 画面初期データを設定する
    MessageForm _errorForm = MessageForm.initData();
    _errorForm.fromJson(widget.resData);
    _errorSuccessMsg = _errorForm.errorMessage;
  }

  Future _onSignOut() async{
    try {
      widget.headers["content-type"]= "text/html";
      final url = Uri.parse("http://192.168.1.13:8080/logout?_csrf=${widget.cookies['XSRF-TOKEN']}");
      http.Response response = await http.post(url, headers: widget.headers);
      if (response.statusCode != 200) {
        setState(() {
          int statusCode = response.statusCode;
          _errorSuccessMsg = "エラーが発生しました $statusCode";
        });
        return;
      }
      //最初のページ(ログイン画面)に戻る
      Navigator.popUntil(context, (route) => route.isFirst);
    } on Exception catch (e) {
      setState(() {
        _errorSuccessMsg = "エラーが発生しました";
      });
    }
  }
}