kazpgmの日記

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

フロント側を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