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

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

Json Serialization 연습

Todo

  • json 데이터 받아와서 Todo 인스턴스 만들어보기
    import 'package:dio/dio.dart';
    
    class Todo {
      int userId;
      int id;
      String title;
      bool completed;
    
      Todo({
        required this.userId,
        required this.id,
        required this.title,
        required this.completed,
      });
    
      factory Todo.fromMap(Map<String, dynamic> map) {
        return Todo(userId: map['userId'], id: map['id'], title: map['title'], completed: map['completed']);
      }
    
      
      String toString() => 'Todo($title)';
    }
    
    Future<Todo?> getData(int todoNumber) async {
      Dio dio = Dio();
      var url = 'https://jsonplaceholder.typicode.com/todos/$todoNumber';
      var res = await dio.get(url);
      if (res.statusCode == 200) {
        return Todo.fromMap(res.data);
      }
      return null;
    }
    
    void main() async {
      var todoNumber = 5;
      var data = await getData(todoNumber);
      print(data);
    }

Todos

  • https://jsonplaceholder.typicode.com/todos
  • 200개의 투두를 가져오는 API
    [
    	{
    		"userId": 1,
    		"id": 1,
    		"title": "delectus aut autem",
    		"completed": false
    	},
    	{
    		"userId": 1,
    		"id": 2,
    		"title": "quis ut nam facilis et officia qui",
    		"completed": false
    	},
    	{
    		"userId": 1,
    		"id": 3,
    		"title": "fugiat veniam minus",
    		"completed": false
    	},
    
    	... 200개
    
    ]
  • json 데이터 받아와서 Todo 인스턴스들 만들어보기
    import 'package:dio/dio.dart';
    
    class Todo {
      int userId;
      int id;
      String title;
      bool completed;
    
      Todo({
        required this.userId,
        required this.id,
        required this.title,
        required this.completed,
      });
    
      factory Todo.fromMap(Map<String, dynamic> map) {
        return Todo(userId: map['userId'], id: map['id'], title: map['title'], completed: map['completed']);
      }
    
      
      String toString() => 'Todo($title)';
    }
    
    Future<Todo?> getData(int todoNumber) async {
      Dio dio = Dio();
      var url = 'https://jsonplaceholder.typicode.com/todos/$todoNumber';
      var res = await dio.get(url);
      if (res.statusCode == 200) {
        return Todo.fromMap(res.data);
      }
      return null;
    }
    
    Future<List<Todo>> readData() async {
      Dio dio = Dio();
      var url = 'https://jsonplaceholder.typicode.com/todos';
      var res = await dio.get(url);
      if (res.statusCode == 200) {
        var data = List<Map<String, dynamic>>.from(res.data); // List<dynamic> -> List<Map<dynamic>>
        return data.map((e) => Todo.fromMap(e)).toList();
      }
      return [];
    }
    
    void main() async {
      var allTodos = await readData();
      print(allTodos);
      print(allTodos[1].title);
    }

과제

1. Todo 활용

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

https://jsonplaceholder.typicode.com/todos

  • 반드시 Todo 클래스를 만들고 Serialization을 진행할 수 있도록 하시오.
  • AppBar는 다음의 조건을 따라 만들도록 하시오
    1. Blur 효과를 넣어 body의 내용이 흐릿하게 보여질 수 있도록 디자인하시오.
    2. Actions에는 다음의 기능이 포함되어있는 아이콘을 제작하시오
      • Filter 아이콘 :
        • 클릭시 아래서 필터를 설정할 수 있도록 시트 위젯이 켜진다.
        • 필터가 적용되면 화면에 보이는 데이터의 종류가 바뀐다.
        • (필터선택시 아래에서 올라오는 안내문구는 선택사항임)
      • Refresh 아이콘 :
        • 클릭시 네트워크에 데이터를 한 번 더 요청하여 리스트에 재적용한다.
  • 각 Post를 보여주는 Widget은 다음의 조건을 따라 만들도록 하시오
    1. 완료된 상태의 Post라면, 초록색 배경에 체크버튼의 아이콘이 보여지도록 한다.
    2. Dismissable 위젯을 활용하여 옆으로 슬라이드 했을 때, 리스트에서 사라지도록 한다.
      1. 추가적으로, Dismissable 위젯의 key 속성이 의미하는 바를 정리하시오.
  • 제공되는 소스코드를 활용할 수 있도록 하시오.
    • widget/filter_bottom_sheet.dart
      • 필터 아이콘 누를 시 하단에 출력되는 위젯입니다.

      • enum에 대해 학습을 따로 진행하는 것을 추천드립니다.

        import 'package:flutter/material.dart';
        
        enum TodoFilter { all, completed, incompleted }
        
        class FilterBottomSheet extends StatefulWidget {
          const FilterBottomSheet(
              {Key? key, required this.filter, required this.onApply})
              : super(key: key);
          final TodoFilter filter;
          final Function(TodoFilter) onApply;
        
          @override
          State<FilterBottomSheet> createState() => _FilterBottomSheetState();
        }
        
        class _FilterBottomSheetState extends State<FilterBottomSheet> {
          onApply(TodoFilter filter) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('Filter applied: $filter'),
              ),
            );
            widget.onApply(filter);
            Navigator.pop(context);
          }
        
          @override
          Widget build(BuildContext context) {
            return Container(
              padding: const EdgeInsets.all(16),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  ListTile(
                    title: const Text('All'),
                    trailing: Checkbox(
                      value: widget.filter == TodoFilter.all,
                      onChanged: (value) {
                        if (value == true) onApply(TodoFilter.all);
                      },
                    ),
                  ),
                  ListTile(
                    title: const Text('Completed'),
                    trailing: Checkbox(
                      value: widget.filter == TodoFilter.completed,
                      onChanged: (value) {
                        if (value == true) onApply(TodoFilter.completed);
                      },
                    ),
                  ),
                  ListTile(
                    title: const Text('InCompleted'),
                    trailing: Checkbox(
                      value: widget.filter == TodoFilter.incompleted,
                      onChanged: (value) {
                        if (value == true) onApply(TodoFilter.incompleted);
                      },
                    ),
                  ),
                ],
              ),
            );
          }
        }
    • widget/todo_card.dart
      • (model 폴더에 todo클래스를 만들어놓을 것)

        import 'package:flutter/material.dart';
        
        import '../model/todo.dart';
        
        class TodoCard extends StatelessWidget {
          const TodoCard({super.key, required this.todo});
          final Todo todo;
        
          
          Widget build(BuildContext context) {
            return Dismissible(
              key: Key(todo.id.toString()),
              child: Container(
                margin: const EdgeInsets.all(8),
                decoration: BoxDecoration(
                  color: todo.completed ? Colors.green.shade100 : null,
                  border: todo.completed
                      ? Border.all(
                          color: Colors.green,
                        )
                      : null,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: ListTile(
                  title: Text(
                    todo.title,
                    style: TextStyle(
                      color: todo.completed ? Colors.green : null,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  trailing: todo.completed
                      ? const Icon(
                          Icons.check_circle,
                          color: Colors.green,
                        )
                      : null,
                ),
              ),
            );
          }
        }

코드

  • 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

  • todo.dart

    class Todo {
      int userId;
      int id;
      String title;
      bool completed;
    
      Todo({
        required this.userId,
        required this.id,
        required this.title,
        required this.completed,
      });
    
      factory Todo.fromMap(Map<String, dynamic> map) {
        return Todo(userId: map['userId'], id: map['id'], title: map['title'], completed: map['completed']);
      }
    }

page

  • main_page.dart

    import 'dart:developer';
    import 'dart:ui';
    
    import 'package:dio/dio.dart';
    import 'package:first_app/homework/week6/day26/widget/todo_card.dart';
    import 'package:flutter/material.dart';
    
    import '../model/todo.dart';
    import '../widget/filter_bottom_sheet.dart';
    
    class MainPage extends StatefulWidget {
      const MainPage({Key? key}) : super(key: key);
    
      
      State<MainPage> createState() => _MainPageState();
    }
    
    class _MainPageState extends State<MainPage> {
      TodoFilter _filter = TodoFilter.all;
    
      // 데이터 불러오기
      Future<List<Todo>> getData() async {
        Dio dio = Dio();
        String url = 'https://jsonplaceholder.typicode.com/todos';
        var res = await dio.get(url);
        if (res.statusCode == 200) {
          var data = List<Map<String, dynamic>>.from(res.data);
          return data.map((e) => Todo.fromMap(e)).toList();
        }
        return [];
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          extendBodyBehindAppBar: true,
          appBar: AppBar(
            backgroundColor: Colors.transparent,
            foregroundColor: Colors.black,
            elevation: 0,
            title: Text('Todo App'),
            // blur
            flexibleSpace: ClipRRect(
              child: BackdropFilter(
                filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
                child: Container(color: Colors.transparent)
              )
            ),
            actions: [
              IconButton(
                onPressed: (){
                  showModalBottomSheet(
                    context: context,
                    builder: (context) => FilterBottomSheet(
                      filter: _filter, onApply: (filter) {
                        setState(() {
                          _filter = filter;
                        });
                      },
                    ),
                  );
                },
                icon: Icon(Icons.filter_list)
              ),
              IconButton(
                onPressed: (){
                  setState((){});
                },
                icon: Icon(Icons.restart_alt)
              ),
            ],
          ),
          body: FutureBuilder(
            future: getData(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                var todos = snapshot.data!;
                return ListView.builder(
                  itemCount: todos.length,
                  itemBuilder: (context, index) {
                    var todo = todos[index];
                    switch (_filter) {
                      case TodoFilter.completed:
                        return todo.completed ? TodoCard(todo: todo) : Container();
                      case TodoFilter.incompleted:
                        return !todo.completed ? TodoCard(todo: todo) : Container();
                      default:
                        return TodoCard(todo: todo);
                    }
                  },
                );
              } else {
                return Center(child: CircularProgressIndicator());
              }
            },
          ),
        );
      }
    }

widget

  • filter_bottom_sheet.dart

    import 'package:flutter/material.dart';
    
    // 필터링 옵션
    enum TodoFilter { all, completed, incompleted }
    
    class FilterBottomSheet extends StatefulWidget {
      const FilterBottomSheet(
          {Key? key, required this.filter, required this.onApply})
          : super(key: key);
      final TodoFilter filter;            // 현재 선택된 필터링 옵션 저장
      final Function(TodoFilter) onApply; // 선택된 필터링 옵션 적용
    
      
      State<FilterBottomSheet> createState() => _FilterBottomSheetState();
    }
    
    class _FilterBottomSheetState extends State<FilterBottomSheet> {
      onApply(TodoFilter filter) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Filter applied: $filter'),
          ),
        );
        widget.onApply(filter);   // 선택된 필터링 옵션 적용
        Navigator.pop(context);   // bottom sheet 닫기
      }
    
      
      Widget build(BuildContext context) {
        return Container(
          padding: const EdgeInsets.all(16),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              ListTile(
                title: const Text('All'),
                trailing: Checkbox(
                  value: widget.filter == TodoFilter.all,
                  onChanged: (value) {
                    if (value == true) onApply(TodoFilter.all);
                  },
                ),
              ),
              ListTile(
                title: const Text('Completed'),
                trailing: Checkbox(
                  value: widget.filter == TodoFilter.completed,
                  onChanged: (value) {
                    if (value == true) onApply(TodoFilter.completed);
                  },
                ),
              ),
              ListTile(
                title: const Text('InCompleted'),
                trailing: Checkbox(
                  value: widget.filter == TodoFilter.incompleted,
                  onChanged: (value) {
                    if (value == true) onApply(TodoFilter.incompleted);
                  },
                ),
              ),
            ],
          ),
        );
      }
    }
  • todo_card.dart

    import 'package:flutter/material.dart';
    
    import '../model/todo.dart';
    
    class TodoCard extends StatelessWidget {
      const TodoCard({super.key, required this.todo});
      final Todo todo;
    
      
      Widget build(BuildContext context) {
        return Dismissible(
          key: Key(todo.id.toString()),
          child: Container(
            margin: const EdgeInsets.all(8),
            decoration: BoxDecoration(
              color: todo.completed ? Colors.green.shade100 : null,
              border: todo.completed
                  ? Border.all(
                color: Colors.green,
              )
                  : null,
              borderRadius: BorderRadius.circular(8),
            ),
            child: ListTile(
              title: Text(
                todo.title,
                style: TextStyle(
                  color: todo.completed ? Colors.green : null,
                  fontWeight: FontWeight.bold,
                ),
              ),
              trailing: todo.completed
                  ? const Icon(
                Icons.check_circle,
                color: Colors.green,
              )
                  : null,
            ),
          ),
        );
      }
    }

결과


Dismissable 위젯의 key 속성

Dismissable 위젯

  • 리스트나 그리드뷰 등에서 항목을 스와이프하여 삭제하거나 이동할 수 있는 기능을 제공

key

  • Dismissable 위젯을 사용할 때 각 항목을 고유하게 식별하는 데 사용

  • 위젯의 상태를 관리하고, 위젯이 생성 및 소멸될 때 어떤 작업을 수행할지 결정할 수 있음

  • 각 항목은 고유한 key를 가지고 있으며, 이를 사용하여 해당 항목의 상태를 유지하고 관리

  • GlobalKey

    • 전역 키(Global key)
    • 위젯을 트리에서 고유하게 식별할 때 사용
    • 위젯을 찾을 때 앱의 전체 위젯 트리에서 검색할 수 있음
    • 전체 위젯 트리에서 검색할 수 있으므로 상태를 다른 위젯과 공유하는 경우 유용
  • UniqueKey

    • 고유 키(Unique key)
    • 위젯이 다른 위젯과 구별되도록 만들어줌
    • 새로운 인스턴스를 만들 때마다 고유한 값을 생성
    • ListView에서 각 항목에 고유한 키를 할당하기 위해 UniqueKey를 사용할 수 있음

26일차 끝,....

profile
우와재밋다

0개의 댓글