「.NET 開発基盤部会 Wiki」は、「Open棟梁Project」,「OSSコンソーシアム .NET開発基盤部会」によって運営されています。
WebAPIを経由した簡単なCRUD処理の実装
プロンプト
添付のReactプロジェクトのコードをFlutterに移植します。 なお、作成したScreenは以下からリンクします。
※ 添付のReactプロジェクトのコード:https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/vite-react-ts/src/pages/FetchData.tsx
※ 「以下からリンクします。」の以下:https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/XPlat/flutter_template/lib/layout/main_layout.dart
pubspec.yamlはコマンドではなく手書き?
flutter pub add http
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> json) {
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/WeatherForecasts?';
// --- Screen ---
class ScreenFetchData extends StatefulWidget {
const ScreenFetchData({super.key});
@override
State<ScreenFetchData> createState() => _ScreenFetchDataState();
}
class _ScreenFetchDataState extends State<ScreenFetchData> {
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}startDateIndex=$page');
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 Map<String, dynamic>))
.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.bold);
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
columns: const [
DataColumn(label: Text('Date', style: headerStyle)),
DataColumn(label: Text('Temp. (C)', style: headerStyle)),
DataColumn(label: Text('Temp. (F)', style: headerStyle)),
DataColumn(label: Text('Summary', style: headerStyle)),
],
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) ? null : _handlePrev,
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: FontWeight.bold),
),
const SizedBox(height: 8),
const Text('This component demonstrates fetching data from the server.'),
const SizedBox(height: 16),
// コンテンツ
Expanded(
child: Center(
child: _loading
? const CircularProgressIndicator()
: _errorMessage != null
? Text(
'Error: $_errorMessage',
style: const TextStyle(color: Colors.red),
)
: _buildForecastTable(),
),
),
const SizedBox(height: 12),
_buildPagination(),
],
),
);
}
}
Flutterでapp.configのようなパラメタ設定ファイルはありますか?
class AppConfig {
// 基本URL
static const String authServerRootUrl = String.fromEnvironment(
'AUTH_SERVER_ROOT_URL',
defaultValue: 'https://localhost:44300/MultiPurposeAuthSite',
);
static const String resourcesServerRootUrl = String.fromEnvironment(
'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 => '$authServerRootUrl/authorize';
static String get tokenRequestUrl => '$authServerRootUrl/token';
static String get userInfoRequestUrl => '$authServerRootUrl/userinfo';
static String get fetchDataRootUrl => '$resourcesServerRootUrl/api/sampledata/weatherforecasts?';
static String get crudSampleRootUrl => '$resourcesServerRootUrl/api/json/';
}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;