フロント側を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 = "エラーが発生しました"; }); } } }