[Flutter] 스나이퍼팩토리 Flutter 중급과정 (6)

GONG·2023년 4월 29일
0
post-thumbnail

Json Serialization 연습

User

  • https://jsonplaceholder.typicode.com/users/1
  • 유저 번호에 해당하는 유저를 가져오는 API
    {
    	"id": 1,
    	"name": "Leanne Graham",
    	"username": "Bret",
    	"email": "Sincere@april.biz",
    	"address": {
    		"street": "Kulas Light",
    		"suite": "Apt. 556",
    		"city": "Gwenborough",
    		"zipcode": "92998-3874",
    		"geo": {
    			"lat": "-37.3159",
    			"lng": "81.1496"
    		}
    	},
    	"phone": "1-770-736-8031 x56442",
    	"website": "hildegard.org",
    	"company": {
    		"name": "Romaguera-Crona",
    		"catchPhrase": "Multi-layered client-server neural-net",
    		"bs": "harness real-time e-markets"
    	}
    }
  • json 데이터 받아와서 User 인스턴스 만들어보기
    import 'package:dio/dio.dart';
    
    class Geo {
      String lat;
      String lng;
    
      Geo({required this.lat, required this.lng});
    
      factory Geo.fromMap(Map<String, dynamic> map) {
        return Geo(
          lat: map['lat'],
          lng: map['lng']
        );
      }
    }
    
    class Address {
      String street;
      String suite;
      String city;
      String zipcode;
      Geo geo;
    
      Address({
        required this.street,
        required this.suite,
        required this.city,
        required this.zipcode,
        required this.geo,
      });
    
      factory Address.fromMap(Map<String, dynamic> map) {
        return Address(
          street: map['street'],
          suite: map['suite'],
          city: map['city'],
          zipcode: map['zipcode'],
          geo: Geo.fromMap(map['geo']),
        );
      }
    }
    
    class Company {
      String name;
      String catchPhrase;
      String bs;
    
      Company({
        required this.name,
        required this.catchPhrase,
        required this.bs,
      });
    
      factory Company.fromMap(Map<String, dynamic> map) {
        return Company(
          name: map['name'],
          catchPhrase: map['catchPhrase'],
          bs: map['bs']
        );
      }
    }
    
    class User {
      int id;
      String name;
      String username;
      String email;
      Address address;
      String phone;
      String website;
      Company company;
    
      User({
        required this.id,
        required this.name,
        required this.username,
        required this.email,
        required this.address,
        required this.phone,
        required this.website,
        required this.company,
      });
    
      factory User.fromMap(Map<String, dynamic> map) {
        return User(
          id: map['id'],
          name: map['name'],
          username: map['username'],
          email: map['email'],
          address: Address.fromMap(map['address']),
          phone: map['phone'],
          website: map['website'],
          company: Company.fromMap(map['company']),
        );
      }
    }
    
    void main() async {
      Dio dio = Dio();
      var url = 'https://jsonplaceholder.typicode.com/users/1';
      var res = await dio.get(url);
    
      if (res.statusCode == 200) {
        var user1 = User.fromMap(res.data);
        print(user1);
      }
    }

Users

  • https://jsonplaceholder.typicode.com/users
  • 10명의 유저 정를 가져오는 API
    [
    	{
    		"id": 1,
    		"name": "Leanne Graham",
    		"username": "Bret",
    		"email": "Sincere@april.biz",
    		"address": {
    			"street": "Kulas Light",
    			"suite": "Apt. 556",
    			"city": "Gwenborough",
    			"zipcode": "92998-3874",
    			"geo": {
    				"lat": "-37.3159",
    				"lng": "81.1496"
    			}
    		},
    			"phone": "1-770-736-8031 x56442",
    			"website": "hildegard.org",
    			"company": {
    				"name": "Romaguera-Crona",
    				"catchPhrase": "Multi-layered client-server neural-net",
    				"bs": "harness real-time e-markets"
    			}
    		},
    	{
    		"id": 2,
    		"name": "Ervin Howell",
    		"username": "Antonette",
    		"email": "Shanna@melissa.tv",
    		"address": {
    			"street": "Victor Plains",
    			"suite": "Suite 879",
    			"city": "Wisokyburgh",
    			"zipcode": "90566-7771",
    			"geo": {
    				"lat": "-43.9509",
    				"lng": "-34.4618"
    			}
    		},
    		"phone": "010-692-6593 x09125",
    		"website": "anastasia.net",
    		"company": {
    			"name": "Deckow-Crist",
    			"catchPhrase": "Proactive didactic contingency",
    			"bs": "synergize scalable supply-chains"
    		}
    	},
    	... 10개
    ]
  • json 데이터 받아와서 Users 인스턴스들 만들어보기
    • 방법 1 : Map을 이용해서 안에 있는 요소를 한 번에 다 바꾸기
      void main() async {
        Dio dio = Dio();
        var url = 'https://jsonplaceholder.typicode.com/users/1';
        var res = await dio.get(url);
      
        if (res.statusCode == 200) {
          var user1 = User.fromMap(res.data);
          print(user1);
        }
      }
    • 방법 2 : 반복문을 이용해서 하나씩 바꾸기
      void main() async {
        Dio dio = Dio();
        var url = 'https://jsonplaceholder.typicode.com/users';
        var res = await dio.get(url);
      
        List<User> users = [];
      
        if (res.statusCode == 200) {
          var data = List<Map<String, dynamic>>.from(res.data);
          for (var userMap in data) {
            users.add(User.fromMap(userMap));
          }
          print(users);
        }
      }

과제

프로필 앱 만들기

  1. 다음의 공개된 API를 분석하고, 클래스를 활용하여 적용 후
    연락처를 보여주는 앱을 다음과 같이 만드시오.

    - https://jsonplaceholder.typicode.com/users
  • 반드시 Profile 클래스를 만들고 Serialization을 진행할 수 있도록 하시오.
  • 각 사람별 이미지를 CircleAvatar를 통해 보여주도록 한다.
    - 이 때, 해당 API에는 프로필이미지가 없으므로 다음의 이미지 URL을 활용한다.
    - https://xsgames.co/randomusers/assets/avatars/male/{번호}.jpg
    - 위 URL에 들어가는 {번호}에는 유저ID를 넣어 사용할 수 있도록 한다.
  • 애니메이션 효과를 적절히 사용하여 최대한 위 결과물과 비슷하도록 만드시오.
  • 네트워크에 통신하여 데이터를 가져오는 것은 첫 페이지(리스트 보여주는 페이지)에만 할 수 있도록 한다.

코드

  • main.dart
    import 'package:flutter/material.dart';
    
    import 'page/main_page.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      
      Widget build(BuildContext context) {
        return MaterialApp(
          home: MainPage(),
        );
      }
    }

model

  • profile.dart
    import 'address.dart';
    import 'company.dart';
    
    class Profile {
      int id;
      String name;
      String username;
      String email;
      Address address;
      String phone;
      String website;
      Company company;
    
      Profile({
        required this.id,
        required this.name,
        required this.username,
        required this.email,
        required this.address,
        required this.phone,
        required this.website,
        required this.company,
      });
    
      factory Profile.fromMap(Map<String, dynamic> map) {
        return Profile(
          id: map['id'],
          name: map['name'],
          username: map['username'],
          email: map['email'],
          address: Address.fromMap(map['address']),
          phone: map['phone'], website: map['website'],
          company: Company.fromMap(map['company']),
        );
      }
    }
  • address.dart
    import 'geo.dart';
    
    class Address {
      String street;
      String suite;
      String city;
      String zipcode;
      Geo geo;
    
      Address({
        required this.street,
        required this.suite,
        required this.city,
        required this.zipcode,
        required this.geo,
      });
    
      factory Address.fromMap(Map<String, dynamic> map) {
        return Address(
          street: map['street'],
          suite: map['suite'],
          city: map['city'],
          zipcode: map['zipcode'],
          geo: Geo.fromMap(map['geo']),
        );
      }
    
      
      String toString() => '$city, $street, $suite';
    }
  • geo.dart
    class Geo {
      String lat;
      String lng;
    
      Geo({required this.lat, required this.lng});
    
      factory Geo.fromMap(Map<String, dynamic> map) {
        return Geo(lat: map['lat'], lng: map['lng']);
      }
    }
  • company
    class Company {
      String name;
      String catchPhrase;
      String bs;
    
      Company({
        required this.name,
        required this.catchPhrase,
        required this.bs,
      });
    
      factory Company.fromMap(Map<String, dynamic> map) {
        return Company(
          name: map['name'],
          catchPhrase: map['catchPhrase'],
          bs: map['bs']
        );
      }
    }

page

  • main_page.dart
    import 'package:animate_do/animate_do.dart';
    import 'package:dio/dio.dart';
    import 'package:flutter/material.dart';
    
    import '../model/profile.dart';
    import '../widget/profile_item.dart';
    
    class MainPage extends StatefulWidget {
      const MainPage({Key? key}) : super(key: key);
    
      
      State<MainPage> createState() => _MainPageState();
    }
    
    class _MainPageState extends State<MainPage> {
    
      Future<List<Profile>> getData() async {
        Dio dio = Dio();
        String url = 'https://jsonplaceholder.typicode.com/users';
        var res = await dio.get(url);
    
        if (res.statusCode == 200) {
          var data = List<Map<String, dynamic>>.from(res.data);
          return data.map((e) => Profile.fromMap(e)).toList();
        }
        return [];
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.transparent,
            foregroundColor: Colors.black,
            elevation: 0,
            centerTitle: true,
            title: Text('My Contacts'),
          ),
          body: FutureBuilder(
            future: getData(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                var profiles = snapshot.data!;
                return ListView.builder(
                  itemCount: profiles.length,
                  itemBuilder: (context, index) {
                    var profile = profiles[index];
                    return FadeInRight(
                      delay: Duration(milliseconds: 500*index),
                      child: ProfileItem(profile: profile),
                    );
                  }
                );
              } else {
                return Center(child: CircularProgressIndicator());
              }
            },
          ),
        );
      }
    }
  • profile_page.dart
    import 'package:animate_do/animate_do.dart';
    import 'package:flutter/material.dart';
    
    import '../model/profile.dart';
    
    class ProfilePage extends StatelessWidget {
      const ProfilePage({Key? key, required this.profile, required this.imgUrl}) : super(key: key);
    
      final Profile profile;
      final String imgUrl;
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          extendBodyBehindAppBar: true,
          appBar: AppBar(
            backgroundColor: Colors.transparent,
            elevation: 0,
            title: Text(profile.name),
          ),
          body: Stack(
            children: [
              Column(
                children: [
                  Container(
                    width: double.infinity,
                    height: 250,
                    decoration: BoxDecoration(
                      image: DecorationImage(
                        image: NetworkImage(imgUrl),
                        fit: BoxFit.cover,
                        colorFilter: ColorFilter.mode(
                          Colors.black54, BlendMode.darken
                        ),
                      ),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        FadeInRight(
                          delay: Duration(milliseconds: 500),
                          child: Padding(
                            padding: const EdgeInsets.only(top: 45, bottom: 20),
                            child: Text(profile.name, style: TextStyle(fontSize: 30)),
                          ),
                        ),
                        Divider(),
                        FadeInRight(
                          delay: Duration(milliseconds: 1500),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Padding(
                                padding: const EdgeInsets.symmetric(vertical: 8.0),
                                child: Text('Information', style: TextStyle(fontSize: 24)),
                              ),
                              Row(
                                children: [
                                  Icon(Icons.mail),
                                  SizedBox(width: 8),
                                  Text(profile.email),
                                ],
                              ),
                              Row(
                                children: [
                                  Icon(Icons.call),
                                  SizedBox(width: 8),
                                  Text(profile.phone),
                                ],
                              ),
                              Row(
                                children: [
                                  Icon(Icons.location_on),
                                  SizedBox(width: 8),
                                  Text('${profile.address}'),
                                ],
                              ),
                            ],
                          ),
                        ),
                        Divider(),
                        FadeInRight(
                          delay: Duration(milliseconds: 2500),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Padding(
                                padding: const EdgeInsets.symmetric(vertical: 8.0),
                                child: Text('Company', style: TextStyle(fontSize: 24)),
                              ),
                              Text(profile.company.name),
                              Text(profile.company.catchPhrase),
                              Text(profile.company.bs),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
              Positioned(
                top: 200,
                left: 16,
                child: Container(
                  width: 100,
                  height: 100,
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(100),
                    child: Image.network(imgUrl),
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }

widget

  • profile_item.dart
    import 'package:flutter/material.dart';
    
    import '../model/profile.dart';
    import '../page/profile_page.dart';
    
    class ProfileItem extends StatelessWidget {
      const ProfileItem({Key? key, required this.profile}) : super(key: key);
    
      final Profile profile;
    
      
      Widget build(BuildContext context) {
        String imgUrl = 'https://xsgames.co/randomusers/assets/avatars/male/${profile.id}.jpg';
    
        return ListTile(
          leading: ClipRRect(
            borderRadius: BorderRadius.circular(30),
            child: Image.network(imgUrl),
          ),
          title: Text(profile.name),
          subtitle: Text(profile.email),
          trailing: Icon(Icons.arrow_forward_ios),
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => ProfilePage(profile: profile, imgUrl: imgUrl))
            );
          },
        );
      }
    }

결과


27일차 끝!!

profile
우와재밋다

0개의 댓글