データを追加してみる。
モックサーバーにデータを追加するサンプルも作ってみました。モデルが違うと画面にデータを表示できなかったので、別のアプリ作りました。
json-serverは、Flutterのプロジェクトの中に今回は配置しています。起動するときは、VScodeか、ターミナルでコマンドを入力して起動します。
Widgetを公式のサンプルでは、切り分けていたので、試してみたのですが、同じページにクラスを書かないと、プロパティを使えないようなので、同じページに、TextEditingControllerが使えるクラスを書いています。
こちらが完成品です
まずは、モデルを作成します。データを取得するget_model.dartとデータを保存するpost_model.dartを作成します。
Flutterのプロジェクトを作る
新しくプロジェクトを作ったら、その中で今回はmock-serverディレクトリを作成して、その中にcdコマンドで移動して、json-serverの環境構築を行います。
db.jsonですが、今回は空っぽにしておきます。後でPOSTすると、データが追加されていきます。
{
"bookList": [
],
"profile": {
"name": "typicode"
}
}
POSTメソッドで使うファイル
公式ドキュメントに載っていたものを参考にしました。やはり勉強するなら、公式ドキュメントですね。
class PostModel {
final int id;
final String title;
const PostModel({required this.id, required this.title});
factory PostModel.fromJson(Map<String, dynamic> json) {
return PostModel(
id: json['id'],
title: json['title'],
);
}
}
GETメソッドで使うファイル
以前作ったものを修正しただけですね。プロパティが減っただけです。シンプルにしすぎた...
import 'dart:convert' as convert;
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
/// [mocker-serverから、データを取得するクラスを定義]
/// [JSONのデータの中のオブジェクトと同じ名前にする]
class GetModel {
final int id; // idは数字なのでint型
final String title; // titleは文字なのでstring型
// コンストラクターを定義
GetModel({required this.id, required this.title});
// 新しいインスタンスを作成し、jsonを解析してデータを
// 新しいインスタンスに配置します。(公式を翻訳)
GetModel.fromJson(Map<String, dynamic> json)
: id = json['id'],
title = json['title']; //最後のところはセミコロンをつける
// インスタンスをマップに変換するtoJson()メソッド
Map<String, dynamic> toJson() => {'id': id, 'title': title};
}
/// [BookListを型に使いモックサーバーからデータを取得する関数.]
List<GetModel> parseBook(String responseBody) {
// 引数をキャストしてMap型に変換
final parsed = convert.jsonDecode(responseBody).cast<Map<String, dynamic>>();
// 配列をmapメソッドでループさせる。
return parsed.map<GetModel>((json) => GetModel.fromJson(json)).toList();
}
// インターネットからデータを取得するメソッド
Future<List<GetModel>> fetchPosts() async {
const endpoint = 'http://localhost:3000/bookList/';
var url = Uri.parse(endpoint);
var response = await http.get(url);
if (response.statusCode == 200) {
// ステータスコードを表示する
print('ステータスコード: ${response.statusCode}');
// JSONのデータを表示する
print('JSONのデータ: ${response.body}');
return compute(parseBook, response.body);
} else {
throw Exception('Failed to load book');
}
}
HTTPを呼び出すためのファイル
前回使ったProviderとHTTPのGETをするロジックを書いたファイルをdomainディレクトリに配置しています。
POSTをするためのファイル
import 'package:http_post_app/model/post_model.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<PostModel> createBook(String title) async {
print('Riverpodで実行');
final response = await http.post(
Uri.parse('http://localhost:3000/bookList/'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
}),
);
if (response.statusCode == 201) {
print(response.statusCode);
print(response.body);
// サーバーが 201 CREATED レスポンスを返した場合。
// JSON をパースします。
return PostModel.fromJson(jsonDecode(response.body));
} else {
// サーバーが 201 CREATED レスポンスを返さなかった場合。
// 例外が発生する。
throw Exception('Failed to create BookList.');
}
}
前回使ったGETで使うProviderが書かれたファイル
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http_post_app/model/get_model.dart';
// 自動でデータを再取得するよう設定するFutureProvider.
final booksProvider = FutureProvider<List<GetModel>>((ref) async {
// model.dartの関数を呼び出す.
return fetchPosts();
});
アプリの画面
今回は入力フォームをmain.dartに配置して、画面にデータを表示するのは、book_page.dartで行います。
データの表示なのですが、1度だけしか呼び出せないようなので、アプリを再起動しないと、追加されたデータが見れないようでして、このような場合は、StreamProviderやStreamBuilderを使うとよさそうですね。
今回はモックにデータを追加するのを体験するだけなので、比較するものまでは作ってないです。
すいません🙇♂️
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http_post_app/book_page.dart';
import 'package:http_post_app/domain/book_domain.dart';
import 'package:http_post_app/model/post_model.dart';
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
// 切り分けたWidgetで使うためのクラスを同じページに定義
// _futureBookでmodel.dartのクラスを引数に使う
class PostMethod {
final TextEditingController _controller = TextEditingController();
Future<PostModel>? _futureBook;
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Create Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const PostPage(),
);
}
}
class PostPage extends ConsumerWidget {
const PostPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final postClass = new PostMethod();
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueGrey,
title: const Text('POSTメソッドでデータを追加'),
),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: _HTTPWidget(postClass: postClass),
),
);
}
}
class _HTTPWidget extends StatelessWidget {
const _HTTPWidget({
Key? key,
required this.postClass,
}) : super(key: key);
final PostMethod postClass;
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: postClass._controller,
decoration: const InputDecoration(hintText: '本のタイトルを入力'),
),
ElevatedButton(
onPressed: () {
postClass._futureBook = createBook(postClass._controller.text);
},
child: const Text('本を追加'),
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BookPage()),
);
},
child: const Text('BookPage'))
],
);
}
}
追加されたデータを表示するページ。アプリを起動して、一度しかプログラムが実行されないので、Runボタンを押して、アプリを再起動すると、追加されたデータが見れるようになります。
今回はモックにデータを追加するのが、目的なので、これ以上のことはしません。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http_post_app/domain/book_provider.dart';
class BookPage extends ConsumerWidget {
const BookPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
// このページで使用するriverpodを呼び出す変数を定義
final value = ref.watch(booksProvider);
return Scaffold(
appBar: AppBar(
title: const Text('mock-serverからデータを習得する'),
backgroundColor: Colors.blueGrey,
),
body: Center(
child: value.when(
data: (books) {
return ListView.builder(
// 配列のデータを描画するWidget
itemCount: books.length, // 配列の数をカウント
itemBuilder: (context, index) {
return ListTile(
title: Text(books[index].title), // 配列のtitleプロパティを表示
);
},
);
},
// データが習得できなかったら、ローディングされる.
error: (err, stack) => Center(child: Text(err.toString())),
loading: () => const Center(child: CircularProgressIndicator())),
),
);
}
}
では、アプリを立ち上げてデータを追加していきましよう!
追加前のdb.json
追加後のdb.json
データを追加していく
画面にデータが表示されました。今回の実装だと1回しかプログラムを実行できないので、追加したら画面にデータが増えていく動作をみるのはできないですね。
世の中のアプリは、このようにHTTP通信を行なって、サーバーにデータを保存して、サーバーにリクエストを送って、文字や画像のデータを取得してアプリの画面に表示しているようです。
すごいですね。PayPayやSlackなどは、このような仕組みで動いているのでしょうね。
でないと、Web版、デスクトップ版、モバイル版が全て同じデータを表示できるわけないですもんね。