<aside> ❓ 1. 다음의 공개된 API를 분석하고, 클래스를 활용하여 적용 후 블로그를 보여주는 앱을 다음과 같이 만드시오. https://jsonplaceholder.typicode.com/posts
gif
반드시 Post 클래스를 만들고 Serialization을 진행할 수 있도록 하시오.
ListView.seperated 위젯을 활용하여 Post 5개씩 구분되어 보여질 수 있도록 하시오.
Post 하나를 클릭하면 아래에서 상세 내용이 나타나도록 만드시오.
main.dart
import 'package:flutter/material.dart';
import 'main_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: MainPage(),
);
}
}
post_item.dart
import 'Post.dart';
import 'package:flutter/material.dart';
class PostItem extends StatelessWidget {
const PostItem({Key? key, required this.post}) : super(key: key);
final Post post;
@override
Widget build(BuildContext context) {
return ListTile(
leading: CircleAvatar(
child: Text('${post.id}'),
),
title: Text('${post.title}'),
subtitle: Text('${post.body}'),
);
}
}
Post.dart
class Post {
int userId;
int id;
String title;
String body;
Post({required this.userId, required this.id, required this.title, required this.body});
factory Post.fromMap(Map<String, dynamic> map) {
return Post(userId: map['userId'], id: map['id'], title: map['title'], body: map['body']);
}
}
main_page.dart
import 'package:dio/dio.dart';
import 'post_item.dart';
import 'Post.dart';
import 'package:flutter/material.dart';
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Future<List<Post>> getData() async {
Dio dio = Dio();
var url = '<https://jsonplaceholder.typicode.com/posts>';
var response = await dio.get(url);
if (response.statusCode == 200) {
var data = List<Map<String, dynamic>>.from(response.data);
return data.map((e) => Post.fromMap(e)).toList();
}
return [];
}
final int length = 5; // 묶음당 post 개수
return Scaffold(
body: SafeArea(
child: FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data!;
return ListView.separated(
itemCount: snapshot.data!.length + 1,
itemBuilder: (context, index) {
if (index == 0) return Container();
return GestureDetector(
child: PostItem(post: data[index-1]),
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return BottomSheet(post: data[index-1]);
},
);
},
);
},
separatorBuilder: (context, index) {
int startIdx = index + 1;
int endIdx = startIdx + length - 1;
if ((index) % length == 0) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Post $startIdx ~ $endIdx', style: TextStyle(fontSize: 26))
);
} else {
return Container();
}
},
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
),
);
}
}
// bottom sheet
class BottomSheet extends StatelessWidget {
const BottomSheet({Key? key, required this.post}) : super(key: key);
final Post post;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${post.title}', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
SizedBox(height: 16),
Text('${post.body}'),
]
),
);
}
}