フロント側をFlutter(スマホ)Thymeleaf(PC)、バックエンド側SpringBootの自動作成勉強中
8:27
①昨日に続き、ユーザー情報詳細画面を作っている。
14:30
①とりあえず、ユーザー情報詳細画面が動いた。次は削除ボタンだ。
■Flutter側スマホ画面
↓ID=12を選んで詳細ボタン押下。ユーザー情報詳細画面が表示される。
↓変更ボタン押下。ユーザー情報変更画面が表示される。
↓登録押下でリスト一覧表示になる。更新OKメッセージも表示される。
↓ID=12を選んで詳細ボタン押下。ユーザー情報詳細画面が表示される。
↓一覧に戻るボタン押下。リスト一覧表示が表示される。
■PC側WEB画面
↓ID=12を選んで詳細ボタン押下。ユーザー情報詳細画面が表示される。
↓変更ボタン押下。ユーザー情報変更画面が表示される。
↓登録押下でリスト一覧表示になる。更新OKメッセージも表示される。
↓ID=12を選んで詳細ボタン押下。ユーザー情報詳細画面が表示される。
↓一覧に戻るボタン押下。リスト一覧表示が表示される。
■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( // 情報一覧リストへ、detail、httpアクセス onPressed: (){httpForDetail(element["id"]);}, style: ElevatedButton.styleFrom( primary: Colors.blue, ), child: const Text("詳細"), ), ), ・・・ ] ), ], ) ), ); } } return contentWidgets; } ・・・ /// 情報一覧リストへ、detailで、httpアクセス void httpForDetail(int _id) { httpForInfo("detail", "http://192.168.1.13:8080/members/admin/user/userA/detail", _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; if (response.statusCode == 401) { _errorSuccessMsg = "ログインしてください"; } else { _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); // 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 == 'detail') { //情報登録 Navigator.of(context).push( MaterialPageRoute( builder: (context) => UserDetail(title: widget.title, username: widget.username, headers: widget.headers, cookies: widget.cookies, resData: _resData), ), ); } 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 (_errorSuccessMsg == '' && _userSrchForm.itemErrorMessages != '') { _errorSuccessMsg = '項目エラーを確認してください'; // 検索画面表示 _selectedIndex = 0; } // 並び順指定があればドロップダウンリストはそれにする。 if (_userSrchForm.sortItemName == "") { _selectItem = 0; } else { for (int i = 0; i < _selectItems.length; i++) { if (_selectItems[i] == _userSrchForm.sortItemName) { _selectItem = i; break; } } } }); // 画面を先頭に戻す _scrollController.animateTo(0, duration: const Duration(milliseconds:600), curve: Curves.easeInQuint); } } } on Exception catch (ex) { setState(() { _errorSuccessMsg = "エラーが発生しました" + ex.toString(); }); } }
・UserDetail.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/affairs/user/userRegisterAmend.dart'; import 'package:flutter_app/form/messageForm.dart'; import 'package:flutter_app/form/userSrchForm.dart'; import 'package:flutter_app/utils/elements.dart'; import 'package:http/http.dart' as http; //httpリクエスト用 import 'dart:async'; //非同期処理用 import '../../main.dart'; import '../../error.dart'; import '../../utils/commUtils.dart'; import '../../form/userForm.dart'; class UserDetail extends StatefulWidget { final String title; final String username; final Map<String, String> cookies; final Map<String, String> headers; final String resData; const UserDetail({Key? key, required this.title, required this.username, required this.headers, required this.cookies, required this.resData}) : super(key: key); @override State<UserDetail> createState() => _UserDetailState(); } class _UserDetailState extends State<UserDetail> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); String _errorSuccessMsg = ""; late UserForm _userForm; late ScrollController _scrollController; @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: _makeWidgets(), ), ), ); } /// 表示するWidgets List<Widget> _makeWidgets() { var contentWidgets = List<Widget>.empty(growable: true); //var contentWidgets = <Widget>[]; if (_errorSuccessMsg.isNotEmpty) { contentWidgets.add( // エラーメッセージ Container( padding: const EdgeInsets.fromLTRB(20.0, 10, 20.0, 5.0), child:Text(_errorSuccessMsg, textAlign: TextAlign.center, overflow: TextOverflow.clip, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.red),), ), ); } else { contentWidgets.add(const SizedBox( height : 10.0, )); } contentWidgets.add( // ID Container( margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5), padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0), child:Text('ID : ' + CommUtils.chgToString(_userForm.id), textAlign: TextAlign.left, overflow: TextOverflow.clip, ), ) ); contentWidgets.add( // 名前 Container( margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5), padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0), child:Text('名前 : ' + _userForm.name, textAlign: TextAlign.left, overflow: TextOverflow.clip, ), ) ); contentWidgets.add( // メールアドレス Container( margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5), padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0), child:Text('メールアドレス : ' + _userForm.email, textAlign: TextAlign.left, overflow: TextOverflow.clip, ), ) ); contentWidgets.add( // ロール Container( margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5), padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0), child:Text('ロール : ' + _userForm.roles, textAlign: TextAlign.left, overflow: TextOverflow.clip, ), ) ); contentWidgets.add( // 可否フラグ Container( margin: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 5), padding: const EdgeInsets.fromLTRB(10.0, 0, 10.0, 0), child:Text('可否フラグ : ' + Elements.getKeyVal('ENABLE_FLG', CommUtils.chgToString(_userForm.enableFlag)), textAlign: TextAlign.left, overflow: TextOverflow.clip, ), ) ); contentWidgets.add( Row ( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( margin: const EdgeInsets.fromLTRB(5.0, 0, 5.0, 5), padding: const EdgeInsets.fromLTRB(5.0, 0, 5.0, 0), child: ElevatedButton( onPressed: (){httpForUpd();}, style: ElevatedButton.styleFrom( primary: Colors.blue, ), child: const Text("変更"), ), ), Container( margin: const EdgeInsets.fromLTRB(5.0, 0, 5.0,5), padding: const EdgeInsets.fromLTRB(5.0, 0, 5.0, 0), child: ElevatedButton( onPressed: (){httpForListBack();}, style: ElevatedButton.styleFrom( primary: Colors.blue, ), child: const Text("一覧に戻る"), ), ), ] ), ); return contentWidgets; } /// 情報一覧リストへ、updで、httpアクセス void httpForUpd() { httpForInfo("upd", "http://192.168.1.13:8080/members/admin/user/userA/upd"); } /// 情報一覧リストへ、list_backで、httpアクセス void httpForListBack() { httpForInfo("list_back", "http://192.168.1.13:8080/members/admin/user/userA/list_back"); } ///情報変更、情報一覧へのhttpアクセス void httpForInfo(String _mode, String _url) async { try { http.Response response; if (_userForm.mode =="list_back") { UserSrchForm _userSrchForm = UserSrchForm.initData(); String _userSrchFormJson = _userSrchForm.toJson(_mode, "${widget.cookies['XSRF-TOKEN']}", _userForm.page); widget.headers["content-type"]= "application/json; charset=UTF-8"; widget.headers["X-XSRF-TOKEN"]= "${widget.cookies['XSRF-TOKEN']}"; final url = Uri.parse(_url); response = await http.post(url,headers: widget.headers, body: _userSrchFormJson); } else { String _userFormJson = _userForm.toJson(_mode, "${widget.cookies['XSRF-TOKEN']}", _userForm.page); widget.headers["content-type"]= "application/json; charset=UTF-8"; widget.headers["X-XSRF-TOKEN"]= "${widget.cookies['XSRF-TOKEN']}"; final url = Uri.parse(_url); response = await http.post(url,headers: widget.headers, body: _userFormJson); } 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') { // 2画面戻る 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 { // 1画面戻る int count = 0; Navigator.popUntil(context, (_) => count++ >= 1); //情報登録 Navigator.of(context).push( MaterialPageRoute( builder: (context) => UserRegisterAmend(title: widget.title, username: widget.username, headers: widget.headers, cookies: widget.cookies, resData: _resData), ), ); } } } on Exception catch (e) { setState(() { _errorSuccessMsg = "エラーが発生しました" + e.toString(); }); } } @override void initState() { super.initState(); // 画面初期データを設定する _userForm = UserForm.initData(); _userForm.fromJson(widget.resData); _scrollController= ScrollController(); } @override void dispose() { _scrollController.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 userDetailComm(UserFForm userFForm, String id, String page, Model model, boolean flutterFlg) { model.addAttribute("page", page); User user = null; try { //ユーザー情報を取得する user = userService.findByPk(Long.parseLong(id)); if (user == null) { log.error("user not found:pk={}", id); throw new RuntimeException("invalid pk"); } } catch(Exception e){ if (e.getMessage() != null && e.getMessage().equals("invalid pk")) { model.addAttribute("successMessage", "『id』=" + id +"は既に削除されています"); } 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(user, userFForm); model.addAttribute("userFForm", userFForm); model.addAttribute("mode", "detail"); } else { model.addAttribute("user", user); } return returnComm("/members/admin/user/userDetail", model, null, flutterFlg, "userFForm"); } /** * 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 userFForm Flutter向けユーザー情報+ページ番号 * @param model モデル * @return jsonデータ */ @SuppressWarnings("unchecked") @PostMapping("/members/admin/user/userA/detail") @ResponseBody public Map<String, Object> userDetailFlutter(@RequestBody UserFForm userFForm, Model model) { return ( Map<String, Object>)userDetailComm(userFForm, Long.toString(userFForm.getId()), 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=detail") public String userDetail( @RequestParam("id") String id, @RequestParam("mode") String mode, @RequestParam("page") String page, Model model) { return (String)userDetailComm(null, id, page, model, false); }
■2022/06/15に、勉強した成果:『Flutter_JavaSpringプログラム自動作成◎自動生成ツール』をVectorに載せました。Zenn本も書きました。使ってみての感想や間違いの指定や、こうやったほうがいいとかの情報があればメールください。
・Vector
www.vector.co.jp
・Zenn本(Flutter_JavaSpringプログラム自動作成)
zenn.dev