Flutterのセカンド・ステップ2
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
「[[.NET 開発基盤部会 Wiki>http://dotnetdevelopmentinfras...
-[[戻る>Flutter]]
--...[[Flutterの5thステップ]]
--[[Flutterのファースト・ステップ2]]
--Flutterのセカンド・ステップ2
--[[Flutterのサード・ステップ2]]
*目次 [#w4d48b9b]
#contents
*概要 [#p3e29e93]
WebAPIを経由した簡単なCRUD処理の実装
*詳細 [#w39aca80]
**WebAPI [#j28c3169]
プロンプト
添付のReactプロジェクトのコードをFlutterに移植します。
なお、作成したScreenは以下からリンクします。
※ 添付のReactプロジェクトのコード:https://github.com/Ope...
※ 「以下からリンクします。」の以下:https://github.com/Op...
***インストール [#jcd0ddb8]
-プロンプト
pubspec.yamlはコマンドではなく手書き?
-出力
flutter pub add http
***FetchData [#l19a7316]
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// --- モデル ---
class WeatherForecast {
final String dateFormatted;
final int temperatureC;
final int temperatureF;
final String summary;
const WeatherForecast({
required this.dateFormatted,
required this.temperatureC,
required this.temperatureF,
required this.summary,
});
factory WeatherForecast.fromJson(Map<String, dynamic> ...
return WeatherForecast(
dateFormatted: json['dateFormatted'] as String,
temperatureC: json['temperatureC'] as int,
temperatureF: json['temperatureF'] as int,
summary: json['summary'] as String,
);
}
}
// --- 定数 (constants.dart の FetchDataRootUrl に相当) ...
// 実際の URL に合わせて変更してください。
const String _fetchDataRootUrl =
'https://localhost:44335/api/SampleData/WeatherForec...
// --- Screen ---
class ScreenFetchData extends StatefulWidget {
const ScreenFetchData({super.key});
@override
State<ScreenFetchData> createState() => _ScreenFetchDa...
}
class _ScreenFetchDataState extends State<ScreenFetchDat...
List<WeatherForecast> _forecasts = [];
bool _loading = true;
int _currentPage = 1;
String? _errorMessage;
@override
void initState() {
super.initState();
_fetchForecasts(1);
}
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
final uri = Uri.parse('${_fetchDataRootUrl}startDate...
try {
// oauth_oidc.createHttpRequestHeader(false) に相...
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer <token>', // 認証...
},
);
if (response.statusCode == 200) {
final List<dynamic> jsonList =
jsonDecode(response.body) as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Ma...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
_loading = false;
});
} else {
throw Exception('HTTP ${response.statusCode}');
}
} catch (e) {
debugPrint('fetch error: $e');
setState(() {
_errorMessage = e.toString();
_loading = false;
});
}
}
void _handlePrev() {
if (_currentPage > 1) {
_fetchForecasts(_currentPage - 1);
}
}
void _handleNext() {
_fetchForecasts(_currentPage + 1);
}
// --- テーブル描画 ---
Widget _buildForecastTable() {
const headerStyle = TextStyle(fontWeight: FontWeight...
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
columns: const [
DataColumn(label: Text('Date', style: headerSt...
DataColumn(label: Text('Temp. (C)', style: hea...
DataColumn(label: Text('Temp. (F)', style: hea...
DataColumn(label: Text('Summary', style: heade...
],
rows: _forecasts
.map(
(f) => DataRow(cells: [
DataCell(Text(f.dateFormatted)),
DataCell(Text('${f.temperatureC}')),
DataCell(Text('${f.temperatureF}')),
DataCell(Text(f.summary)),
]),
)
.toList(),
),
);
}
// --- ページネーション ---
Widget _buildPagination() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: (_loading || _currentPage <= 1) ? n...
child: const Text('« Prev'),
),
const SizedBox(width: 12),
Text('Page $_currentPage'),
const SizedBox(width: 12),
ElevatedButton(
onPressed: _loading ? null : _handleNext,
child: const Text('Next »'),
),
],
);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Weather forecast',
style: TextStyle(fontSize: 24, fontWeight: F...
),
const SizedBox(height: 8),
const Text('This component demonstrates fetchi...
const SizedBox(height: 16),
// コンテンツ
Expanded(
child: Center(
child: _loading
? const CircularProgressIndicator()
: _errorMessage != null
? Text(
'Error: $_errorMessage',
style: const TextStyle(color: ...
)
: _buildForecastTable(),
),
),
const SizedBox(height: 12),
_buildPagination(),
],
),
);
}
}
***AppConfig [#m500026f]
-プロンプト
Flutterでapp.configのようなパラメタ設定ファイルはありま...
-出力~
lib/config/app_config.dart
class AppConfig {
// 基本URL
static const String authServerRootUrl = String.fromEnv...
'AUTH_SERVER_ROOT_URL',
defaultValue: 'https://localhost:44300/MultiPurposeA...
);
static const String resourcesServerRootUrl = String.fr...
'RESOURCES_SERVER_ROOT_URL',
defaultValue: 'https://localhost:44335',
);
// クライアント設定
static const String baseUrl = '~/';
static const String clientId = String.fromEnvironment(
'CLIENT_ID',
defaultValue: 'f374a155909d486a9234693c34e94479',
);
// 派生URL(文字列補間はconstにできないためstaticゲッ...
static String get authRequestUrl => '$authServerRootUr...
static String get tokenRequestUrl => '$authServerRootU...
static String get userInfoRequestUrl => '$authServerRo...
static String get fetchDataRootUrl => '$resourcesServe...
static String get crudSampleRootUrl => '$resourcesServ...
}
-使い方
--定義
my_app/
├── config.json
├── lib/
│ └── config/
│ └── app_config.dart ← ここが一般的
--使用
dartimport 'package:my_app/config/app_config.dart';
// 使用例
final clientId = AppConfig.clientId;
final authUrl = AppConfig.authRequestUrl;
final fetchUrl = AppConfig.fetchDataRootUrl;
--変更~
defaultValueではなく.envの値を使用する場合
flutter run --dart-define-from-file=config.json
***リファクタリング [#i8244bbc]
-共通関数抜き出し
--プロンプト
以下はReactの実装です
```
fetchForecasts(page: number) {
this.setState({ loading: true });
common.getFetch(
constants.FetchDataRootUrl + `startDateIndex=${pag...
oauth_oidc.createHttpRequestHeader(false),
(data) => {
this.setState({
forecasts: data as unknown as WeatherForecast[],
loading: false, currentPage: page });
},
(msg) => {
this.setState({ loading: false });
console.error('fetch error', msg);
},
);
}
```
以下はFlutterに移植したコードです
```
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
final uri = Uri.parse('${_fetchDataRootUrl}startDate...
try {
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer <token>', // 認証...
},
);
if (response.statusCode == 200) {
final List<dynamic> jsonList =
jsonDecode(response.body) as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Ma...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
_loading = false;
});
} else {
throw Exception('HTTP ${response.statusCode}');
}
} catch (e) {
debugPrint('fetch error: $e');
setState(() {
_errorMessage = e.toString();
_loading = false;
});
}
}
```
Flutterに容易スべき共通関数はありますか?
※ なお、共通関数を実装した関連ファイルを添付してある。~
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
※ 追加で「fetchStatusHandlerも作成します。」と問い合わせ...
--出力
Future<void> postFetch(
String url,
Map<String, String> headers,
Object body,
void Function(Map<String, dynamic> data) onSuccess,
void Function(String msg) setMessage,
) async {
setMessage('');
try {
final response = fetchStatusHandler(
await http.post(Uri.parse(url), headers: headers, ...
);
final data = jsonDecode(response.body) as Map<String...
if (data['errorMSG'] != null) {
setMessage(jsonEncode(data['errorMSG']));
} else if (data['exceptionMSG'] != null) {
setMessage(jsonEncode(data['exceptionMSG']));
} else {
onSuccess(data);
}
} catch (e) {
setMessage(e.toString());
}
}
Future<void> getFetch(
String url,
Map<String, String> headers,
void Function(Map<String, dynamic> data) onSuccess,
void Function(String msg) setMessage,
) async {
setMessage('');
try {
final response = fetchStatusHandler(
await http.get(Uri.parse(url), headers: headers),
);
final data = jsonDecode(response.body) as Map<String...
if (data['errorMSG'] != null) {
setMessage(jsonEncode(data['errorMSG']));
} else if (data['exceptionMSG'] != null) {
setMessage(jsonEncode(data['exceptionMSG']));
} else {
onSuccess(data);
}
} catch (e) {
setMessage(e.toString());
}
}
http.Response fetchStatusHandler(http.Response response) {
if (response.statusCode == 200) {
return response;
} else {
throw Exception(response.reasonPhrase ?? 'HTTP ${res...
}
}
Map<String, String> createHttpRequestHeader({bool isJson...
final headers = <String, String>{
'Accept': 'application/json',
'Content-Type': isJsonRpc
? 'application/json'
: 'application/x-www-form-urlencoded',
};
final token = getAccessToken(); // SharedPreferences等...
if (token != null) {
headers['Authorization'] = 'Bearer $token';
}
return headers;
}
Future<String?> getAccessToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('access_token');
}
Future<void> setAccessToken(String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('access_token', token);
}
※ パスについて別途質問し、共通関数のファイルパスは変更無...
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
※ 他にも、以下の必要になるファイルを移行した。~
・https://github.com/OpenTouryoProject/FrontendTemplates/...
※ 「SharedPreferences」の導入には以下の手順が必要だった。~
start ms-settings:developers
Developer Mode の有効化
flutter pub add shared_preferences
-共通関数の利用
--プロンプト
```dart
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
final uri = Uri.parse('${_fetchDataRootUrl}startDate...
try {
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer <token>', // 認証...
},
);
if (response.statusCode == 200) {
final List<dynamic> jsonList =
jsonDecode(response.body) as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Ma...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
_loading = false;
});
} else {
throw Exception('HTTP ${response.statusCode}');
}
} catch (e) {
debugPrint('fetch error: $e');
setState(() {
_errorMessage = e.toString();
_loading = false;
});
}
}
```
を、以下を使用するように書き換えて下さい
```dart
Future<void> getFetch(
String url,
Map<String, String> headers,
void Function(dynamic data) onSuccess,
void Function(String msg) setMessage,
) async {
setMessage('');
try {
final response = fetchStatusHandler(
await http.get(Uri.parse(url), headers: headers),
);
final data = jsonDecode(response.body) as Map<String...
if (data['errorMSG'] != null) {
setMessage(jsonEncode(data['errorMSG']));
} else if (data['exceptionMSG'] != null) {
setMessage(jsonEncode(data['exceptionMSG']));
} else {
onSuccess(data);
}
} catch (e) {
setMessage(e.toString());
}
}
```
--出力
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
await getFetch(
'${_fetchDataRootUrl}startDateIndex=$page',
{'Content-Type': 'application/json'},
(data) {
final jsonList = data as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Map<...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
});
},
(msg) {
setState(() {
_errorMessage = msg;
});
},
);
setState(() {
_loading = false;
});
}
※ ココからいくらか手直しをした。詳しくは下記とのDiffで確...
https://github.com/OpenTouryoProject/FrontendTemplates/...
**CRUD [#ed359dfc]
-先ずは以下のファイルを移行(AIで旧記法を修正して移行)
--移行元
---https://github.com/OpenTouryoProject/FrontendTemplates...
---https://github.com/OpenTouryoProject/FrontendTemplates...
--移行先
---https://github.com/OpenTouryoProject/FrontendTemplates...
---https://github.com/OpenTouryoProject/FrontendTemplates...
-次いで以下のファイルを移行~
main_layout.dartからリンクするという記載を怠ったため「app...
--移行元:https://github.com/OpenTouryoProject/FrontendTe...
--移行先:https://github.com/OpenTouryoProject/FrontendTe...
***共通関数の利用 [#u79caa43]
-プロンプト
以下のコードを書き換えます
```
Future<void> _selectCount() async {
var url = Uri.http(AppConfig.serverFqdn,
'ASPNETWebService/api/json/SelectCount',);
var response = await http.post(url,
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencod...
"Authorization": "Bearer ${AppAuth.accessToken}",
},
body: {
"ddlDap" : this._ddlDap,
"ddlMode1" : this._ddlMode1,
"ddlMode2": this._ddlMode2,
"ddlExRollback" : this._ddlExRollback,
}
);
if (response.statusCode == 200) {
Map<String, dynamic> jsonResponse
= jsonDecode(response.body) as Map<String, dynam...
setState(() {
this._display = jsonResponse['message'];
});
} else {
print('Request failed with status: ${response.stat...
}
}
```
以下の関数を使用します
```
Future<void> postFetch(
String url,
Map<String, String> headers,
Object body,
void Function(dynamic data) onSuccess,
void Function(String msg) setMessage,
) async {
try {
final response = fetchStatusHandler(
await http.post(Uri.parse(url), headers: headers, ...
);
final decoded = jsonDecode(response.body);
// Map の場合のみエラーキーをチェック
if (decoded is Map<String, dynamic>) {
if (decoded['errorMSG'] != null) {
setMessage(jsonEncode(decoded['errorMSG']));
} else if (decoded['exceptionMSG'] != null) {
setMessage(jsonEncode(decoded['exceptionMSG']));
} else {
onSuccess(decoded);
}
} else {
// List など Map 以外はそのまま onSuccess へ
onSuccess(decoded);
}
} catch (e) {
setMessage(e.toString());
}
}
```
```
Future<Map<String, String>> createHttpRequestHeader({boo...
final headers = <String, String>{
'Accept': 'application/json',
'Content-Type': isJsonRpc
? 'application/json'
: 'application/x-www-form-urlencoded',
};
final token = await getAccessToken(); // ← await を追加
if (token != null) {
headers['Authorization'] = 'Bearer $token';
}
return headers;
}
Future<String?> getAccessToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('access_token');
}
Future<void> setAccessToken(String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('access_token', token);
}
```
-出力
Future<void> _selectCount() async {
final url = Uri.http(
AppConfig.serverFqdn,
'ASPNETWebService/api/json/SelectCount',
).toString();
final headers = await createHttpRequestHeader();
final body = {
"ddlDap": this._ddlDap,
"ddlMode1": this._ddlMode1,
"ddlMode2": this._ddlMode2,
"ddlExRollback": this._ddlExRollback,
};
await postFetch(
url,
headers,
body,
(data) {
setState(() {
this._display = (data as Map<String, dynamic>)['...
});
},
(msg) {
setState(() {
this._display = msg;
});
},
);
}
※ 同様に「_selectAll」「_crud」も書き換え。~
※ Uri.http は扱い辛かったので Uri.parse に変更。
***デザインの修正 [#c337487e]
-プロンプト~
build の body部のコードを添付
添付で、DataTable を MyDropdownButtonの並びの右側のSpace...
-出力
--この生成は、それっぽい生成は出来たが貼り付けるとエラー...
--最終的には、Column(MyDropdownButtons)を「Column→Row」...
→Expanded(Column(DataTable))と横並びにする事で上手く行っ...
-その他、以下のようなプロンプトで修正を行った
--添付のDataTableを中央揃えしたいが左寄りになる。コレは、...
--Flutterで「Column(」に続けて左寄せするにはどのようにし...
※ 詳細は割愛しているが、カナリ苦戦。生成AIもマークアップ...
※ 最終的には修正方法を出力させて人間の頭で理解しながら修...
*参考 [#ta9801c7]
-FrontendTemplates/UI/XPlat at develop · OpenTouryoProjec...
https://github.com/OpenTouryoProject/FrontendTemplates/tr...
--https://github.com/OpenTouryoProject/FrontendTemplates/...
--https://github.com/OpenTouryoProject/FrontendTemplates/...
終了行:
「[[.NET 開発基盤部会 Wiki>http://dotnetdevelopmentinfras...
-[[戻る>Flutter]]
--...[[Flutterの5thステップ]]
--[[Flutterのファースト・ステップ2]]
--Flutterのセカンド・ステップ2
--[[Flutterのサード・ステップ2]]
*目次 [#w4d48b9b]
#contents
*概要 [#p3e29e93]
WebAPIを経由した簡単なCRUD処理の実装
*詳細 [#w39aca80]
**WebAPI [#j28c3169]
プロンプト
添付のReactプロジェクトのコードをFlutterに移植します。
なお、作成したScreenは以下からリンクします。
※ 添付のReactプロジェクトのコード:https://github.com/Ope...
※ 「以下からリンクします。」の以下:https://github.com/Op...
***インストール [#jcd0ddb8]
-プロンプト
pubspec.yamlはコマンドではなく手書き?
-出力
flutter pub add http
***FetchData [#l19a7316]
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// --- モデル ---
class WeatherForecast {
final String dateFormatted;
final int temperatureC;
final int temperatureF;
final String summary;
const WeatherForecast({
required this.dateFormatted,
required this.temperatureC,
required this.temperatureF,
required this.summary,
});
factory WeatherForecast.fromJson(Map<String, dynamic> ...
return WeatherForecast(
dateFormatted: json['dateFormatted'] as String,
temperatureC: json['temperatureC'] as int,
temperatureF: json['temperatureF'] as int,
summary: json['summary'] as String,
);
}
}
// --- 定数 (constants.dart の FetchDataRootUrl に相当) ...
// 実際の URL に合わせて変更してください。
const String _fetchDataRootUrl =
'https://localhost:44335/api/SampleData/WeatherForec...
// --- Screen ---
class ScreenFetchData extends StatefulWidget {
const ScreenFetchData({super.key});
@override
State<ScreenFetchData> createState() => _ScreenFetchDa...
}
class _ScreenFetchDataState extends State<ScreenFetchDat...
List<WeatherForecast> _forecasts = [];
bool _loading = true;
int _currentPage = 1;
String? _errorMessage;
@override
void initState() {
super.initState();
_fetchForecasts(1);
}
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
final uri = Uri.parse('${_fetchDataRootUrl}startDate...
try {
// oauth_oidc.createHttpRequestHeader(false) に相...
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer <token>', // 認証...
},
);
if (response.statusCode == 200) {
final List<dynamic> jsonList =
jsonDecode(response.body) as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Ma...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
_loading = false;
});
} else {
throw Exception('HTTP ${response.statusCode}');
}
} catch (e) {
debugPrint('fetch error: $e');
setState(() {
_errorMessage = e.toString();
_loading = false;
});
}
}
void _handlePrev() {
if (_currentPage > 1) {
_fetchForecasts(_currentPage - 1);
}
}
void _handleNext() {
_fetchForecasts(_currentPage + 1);
}
// --- テーブル描画 ---
Widget _buildForecastTable() {
const headerStyle = TextStyle(fontWeight: FontWeight...
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
columns: const [
DataColumn(label: Text('Date', style: headerSt...
DataColumn(label: Text('Temp. (C)', style: hea...
DataColumn(label: Text('Temp. (F)', style: hea...
DataColumn(label: Text('Summary', style: heade...
],
rows: _forecasts
.map(
(f) => DataRow(cells: [
DataCell(Text(f.dateFormatted)),
DataCell(Text('${f.temperatureC}')),
DataCell(Text('${f.temperatureF}')),
DataCell(Text(f.summary)),
]),
)
.toList(),
),
);
}
// --- ページネーション ---
Widget _buildPagination() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: (_loading || _currentPage <= 1) ? n...
child: const Text('« Prev'),
),
const SizedBox(width: 12),
Text('Page $_currentPage'),
const SizedBox(width: 12),
ElevatedButton(
onPressed: _loading ? null : _handleNext,
child: const Text('Next »'),
),
],
);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Weather forecast',
style: TextStyle(fontSize: 24, fontWeight: F...
),
const SizedBox(height: 8),
const Text('This component demonstrates fetchi...
const SizedBox(height: 16),
// コンテンツ
Expanded(
child: Center(
child: _loading
? const CircularProgressIndicator()
: _errorMessage != null
? Text(
'Error: $_errorMessage',
style: const TextStyle(color: ...
)
: _buildForecastTable(),
),
),
const SizedBox(height: 12),
_buildPagination(),
],
),
);
}
}
***AppConfig [#m500026f]
-プロンプト
Flutterでapp.configのようなパラメタ設定ファイルはありま...
-出力~
lib/config/app_config.dart
class AppConfig {
// 基本URL
static const String authServerRootUrl = String.fromEnv...
'AUTH_SERVER_ROOT_URL',
defaultValue: 'https://localhost:44300/MultiPurposeA...
);
static const String resourcesServerRootUrl = String.fr...
'RESOURCES_SERVER_ROOT_URL',
defaultValue: 'https://localhost:44335',
);
// クライアント設定
static const String baseUrl = '~/';
static const String clientId = String.fromEnvironment(
'CLIENT_ID',
defaultValue: 'f374a155909d486a9234693c34e94479',
);
// 派生URL(文字列補間はconstにできないためstaticゲッ...
static String get authRequestUrl => '$authServerRootUr...
static String get tokenRequestUrl => '$authServerRootU...
static String get userInfoRequestUrl => '$authServerRo...
static String get fetchDataRootUrl => '$resourcesServe...
static String get crudSampleRootUrl => '$resourcesServ...
}
-使い方
--定義
my_app/
├── config.json
├── lib/
│ └── config/
│ └── app_config.dart ← ここが一般的
--使用
dartimport 'package:my_app/config/app_config.dart';
// 使用例
final clientId = AppConfig.clientId;
final authUrl = AppConfig.authRequestUrl;
final fetchUrl = AppConfig.fetchDataRootUrl;
--変更~
defaultValueではなく.envの値を使用する場合
flutter run --dart-define-from-file=config.json
***リファクタリング [#i8244bbc]
-共通関数抜き出し
--プロンプト
以下はReactの実装です
```
fetchForecasts(page: number) {
this.setState({ loading: true });
common.getFetch(
constants.FetchDataRootUrl + `startDateIndex=${pag...
oauth_oidc.createHttpRequestHeader(false),
(data) => {
this.setState({
forecasts: data as unknown as WeatherForecast[],
loading: false, currentPage: page });
},
(msg) => {
this.setState({ loading: false });
console.error('fetch error', msg);
},
);
}
```
以下はFlutterに移植したコードです
```
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
final uri = Uri.parse('${_fetchDataRootUrl}startDate...
try {
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer <token>', // 認証...
},
);
if (response.statusCode == 200) {
final List<dynamic> jsonList =
jsonDecode(response.body) as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Ma...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
_loading = false;
});
} else {
throw Exception('HTTP ${response.statusCode}');
}
} catch (e) {
debugPrint('fetch error: $e');
setState(() {
_errorMessage = e.toString();
_loading = false;
});
}
}
```
Flutterに容易スべき共通関数はありますか?
※ なお、共通関数を実装した関連ファイルを添付してある。~
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
※ 追加で「fetchStatusHandlerも作成します。」と問い合わせ...
--出力
Future<void> postFetch(
String url,
Map<String, String> headers,
Object body,
void Function(Map<String, dynamic> data) onSuccess,
void Function(String msg) setMessage,
) async {
setMessage('');
try {
final response = fetchStatusHandler(
await http.post(Uri.parse(url), headers: headers, ...
);
final data = jsonDecode(response.body) as Map<String...
if (data['errorMSG'] != null) {
setMessage(jsonEncode(data['errorMSG']));
} else if (data['exceptionMSG'] != null) {
setMessage(jsonEncode(data['exceptionMSG']));
} else {
onSuccess(data);
}
} catch (e) {
setMessage(e.toString());
}
}
Future<void> getFetch(
String url,
Map<String, String> headers,
void Function(Map<String, dynamic> data) onSuccess,
void Function(String msg) setMessage,
) async {
setMessage('');
try {
final response = fetchStatusHandler(
await http.get(Uri.parse(url), headers: headers),
);
final data = jsonDecode(response.body) as Map<String...
if (data['errorMSG'] != null) {
setMessage(jsonEncode(data['errorMSG']));
} else if (data['exceptionMSG'] != null) {
setMessage(jsonEncode(data['exceptionMSG']));
} else {
onSuccess(data);
}
} catch (e) {
setMessage(e.toString());
}
}
http.Response fetchStatusHandler(http.Response response) {
if (response.statusCode == 200) {
return response;
} else {
throw Exception(response.reasonPhrase ?? 'HTTP ${res...
}
}
Map<String, String> createHttpRequestHeader({bool isJson...
final headers = <String, String>{
'Accept': 'application/json',
'Content-Type': isJsonRpc
? 'application/json'
: 'application/x-www-form-urlencoded',
};
final token = getAccessToken(); // SharedPreferences等...
if (token != null) {
headers['Authorization'] = 'Bearer $token';
}
return headers;
}
Future<String?> getAccessToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('access_token');
}
Future<void> setAccessToken(String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('access_token', token);
}
※ パスについて別途質問し、共通関数のファイルパスは変更無...
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
・https://github.com/OpenTouryoProject/FrontendTemplate...
※ 他にも、以下の必要になるファイルを移行した。~
・https://github.com/OpenTouryoProject/FrontendTemplates/...
※ 「SharedPreferences」の導入には以下の手順が必要だった。~
start ms-settings:developers
Developer Mode の有効化
flutter pub add shared_preferences
-共通関数の利用
--プロンプト
```dart
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
final uri = Uri.parse('${_fetchDataRootUrl}startDate...
try {
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer <token>', // 認証...
},
);
if (response.statusCode == 200) {
final List<dynamic> jsonList =
jsonDecode(response.body) as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Ma...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
_loading = false;
});
} else {
throw Exception('HTTP ${response.statusCode}');
}
} catch (e) {
debugPrint('fetch error: $e');
setState(() {
_errorMessage = e.toString();
_loading = false;
});
}
}
```
を、以下を使用するように書き換えて下さい
```dart
Future<void> getFetch(
String url,
Map<String, String> headers,
void Function(dynamic data) onSuccess,
void Function(String msg) setMessage,
) async {
setMessage('');
try {
final response = fetchStatusHandler(
await http.get(Uri.parse(url), headers: headers),
);
final data = jsonDecode(response.body) as Map<String...
if (data['errorMSG'] != null) {
setMessage(jsonEncode(data['errorMSG']));
} else if (data['exceptionMSG'] != null) {
setMessage(jsonEncode(data['exceptionMSG']));
} else {
onSuccess(data);
}
} catch (e) {
setMessage(e.toString());
}
}
```
--出力
Future<void> _fetchForecasts(int page) async {
setState(() {
_loading = true;
_errorMessage = null;
});
await getFetch(
'${_fetchDataRootUrl}startDateIndex=$page',
{'Content-Type': 'application/json'},
(data) {
final jsonList = data as List<dynamic>;
final forecasts = jsonList
.map((e) => WeatherForecast.fromJson(e as Map<...
.toList();
setState(() {
_forecasts = forecasts;
_currentPage = page;
});
},
(msg) {
setState(() {
_errorMessage = msg;
});
},
);
setState(() {
_loading = false;
});
}
※ ココからいくらか手直しをした。詳しくは下記とのDiffで確...
https://github.com/OpenTouryoProject/FrontendTemplates/...
**CRUD [#ed359dfc]
-先ずは以下のファイルを移行(AIで旧記法を修正して移行)
--移行元
---https://github.com/OpenTouryoProject/FrontendTemplates...
---https://github.com/OpenTouryoProject/FrontendTemplates...
--移行先
---https://github.com/OpenTouryoProject/FrontendTemplates...
---https://github.com/OpenTouryoProject/FrontendTemplates...
-次いで以下のファイルを移行~
main_layout.dartからリンクするという記載を怠ったため「app...
--移行元:https://github.com/OpenTouryoProject/FrontendTe...
--移行先:https://github.com/OpenTouryoProject/FrontendTe...
***共通関数の利用 [#u79caa43]
-プロンプト
以下のコードを書き換えます
```
Future<void> _selectCount() async {
var url = Uri.http(AppConfig.serverFqdn,
'ASPNETWebService/api/json/SelectCount',);
var response = await http.post(url,
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencod...
"Authorization": "Bearer ${AppAuth.accessToken}",
},
body: {
"ddlDap" : this._ddlDap,
"ddlMode1" : this._ddlMode1,
"ddlMode2": this._ddlMode2,
"ddlExRollback" : this._ddlExRollback,
}
);
if (response.statusCode == 200) {
Map<String, dynamic> jsonResponse
= jsonDecode(response.body) as Map<String, dynam...
setState(() {
this._display = jsonResponse['message'];
});
} else {
print('Request failed with status: ${response.stat...
}
}
```
以下の関数を使用します
```
Future<void> postFetch(
String url,
Map<String, String> headers,
Object body,
void Function(dynamic data) onSuccess,
void Function(String msg) setMessage,
) async {
try {
final response = fetchStatusHandler(
await http.post(Uri.parse(url), headers: headers, ...
);
final decoded = jsonDecode(response.body);
// Map の場合のみエラーキーをチェック
if (decoded is Map<String, dynamic>) {
if (decoded['errorMSG'] != null) {
setMessage(jsonEncode(decoded['errorMSG']));
} else if (decoded['exceptionMSG'] != null) {
setMessage(jsonEncode(decoded['exceptionMSG']));
} else {
onSuccess(decoded);
}
} else {
// List など Map 以外はそのまま onSuccess へ
onSuccess(decoded);
}
} catch (e) {
setMessage(e.toString());
}
}
```
```
Future<Map<String, String>> createHttpRequestHeader({boo...
final headers = <String, String>{
'Accept': 'application/json',
'Content-Type': isJsonRpc
? 'application/json'
: 'application/x-www-form-urlencoded',
};
final token = await getAccessToken(); // ← await を追加
if (token != null) {
headers['Authorization'] = 'Bearer $token';
}
return headers;
}
Future<String?> getAccessToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('access_token');
}
Future<void> setAccessToken(String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('access_token', token);
}
```
-出力
Future<void> _selectCount() async {
final url = Uri.http(
AppConfig.serverFqdn,
'ASPNETWebService/api/json/SelectCount',
).toString();
final headers = await createHttpRequestHeader();
final body = {
"ddlDap": this._ddlDap,
"ddlMode1": this._ddlMode1,
"ddlMode2": this._ddlMode2,
"ddlExRollback": this._ddlExRollback,
};
await postFetch(
url,
headers,
body,
(data) {
setState(() {
this._display = (data as Map<String, dynamic>)['...
});
},
(msg) {
setState(() {
this._display = msg;
});
},
);
}
※ 同様に「_selectAll」「_crud」も書き換え。~
※ Uri.http は扱い辛かったので Uri.parse に変更。
***デザインの修正 [#c337487e]
-プロンプト~
build の body部のコードを添付
添付で、DataTable を MyDropdownButtonの並びの右側のSpace...
-出力
--この生成は、それっぽい生成は出来たが貼り付けるとエラー...
--最終的には、Column(MyDropdownButtons)を「Column→Row」...
→Expanded(Column(DataTable))と横並びにする事で上手く行っ...
-その他、以下のようなプロンプトで修正を行った
--添付のDataTableを中央揃えしたいが左寄りになる。コレは、...
--Flutterで「Column(」に続けて左寄せするにはどのようにし...
※ 詳細は割愛しているが、カナリ苦戦。生成AIもマークアップ...
※ 最終的には修正方法を出力させて人間の頭で理解しながら修...
*参考 [#ta9801c7]
-FrontendTemplates/UI/XPlat at develop · OpenTouryoProjec...
https://github.com/OpenTouryoProject/FrontendTemplates/tr...
--https://github.com/OpenTouryoProject/FrontendTemplates/...
--https://github.com/OpenTouryoProject/FrontendTemplates/...
ページ名: