28일차 과제 - rlatkddbs99/Flutter GitHub Wiki
진짜 어려웠다..
main.dart
import 'dart:io';
import 'package:dict/page/mainPage.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: mainPage(),
);
}
}
page/mainPage.dart
import 'package:dict/model/dict.dart';
import 'package:dict/widget/dict_card.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
class mainPage extends StatefulWidget {
const mainPage({super.key});
@override
State<mainPage> createState() => _mainPageState();
}
class _mainPageState extends State<mainPage> {
Dio dio = Dio();
Dict? dict;
search(String query) async {
//함수호출 할 때 어떤 단어 입력했는지 넘기려고
var url = "https://api.dictionaryapi.dev/api/v2/entries/en/";
try {
var res = await dio.get(url + query);
var data = res.data.first; //데이터 대상
dict = Dict.fromMap(data); //새로 생성된 값 저장
setState(() {}); //새로고침
} catch (e) {
dict = null; //만약 서버에 없는 단어이면 null 넣고 화면 새로고침
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dictionary App'),
elevation: 0,
centerTitle: false,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, //자식위젯이 모든 영역 가짐
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: TextField(
decoration: const InputDecoration(
hintText: "Search",
suffixIcon: Icon(Icons.search),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
),
onSubmitted: (value) {
search(value); //함수호출 (value에 단어 들어가 있음)
},
),
),
),
],
),
Divider(),
if (dict != null)
Expanded(
child: SingleChildScrollView(
child:
DictCard(dict: dict!))), //처음들어오면 null이기 떄문에 if로 거르고
//이제 null이 아니면 데이터가 있다고 !붙이기
],
),
),
);
}
}
model/definition.dart
class Definition {
String definition;
List<String> synonyms;
List<String> antonyms;
String? example; //예시 없을 수도 null-safety
Definition({
required this.definition,
required this.synonyms,
required this.antonyms,
required this.example,
});
factory Definition.fromMap(Map<String, dynamic> map) {
return Definition(
definition: map['definition'],
synonyms: List<String>.from(map['synonyms']),
antonyms: List<String>.from(map['antonyms']),
example: map['example']);
}
}
model/dict.dart
import 'package:dict/model/phonetic.dart';
import 'license.dart';
import 'meaning.dart';
class Dict {
String word;
String? phonetic; //발음기호가 없는 단어는 null이 될 수 있다
List<Phonetic> phonetics;
List<Meaning> meanings;
License license;
List<String> sourceUrls;
Dict({
required this.word,
required this.phonetic,
required this.phonetics,
required this.meanings,
required this.license,
required this.sourceUrls,
});
factory Dict.fromMap(Map<String, dynamic> map) {
return Dict(
word: map['word'],
phonetic: map['phonetic'],
phonetics: List<Phonetic>.from(map['phonetics']
.map((e) => Phonetic.fromMap(e))), //리스트라 phonetic이라는 클래스로 바꾸고 넣어야돼
//map함수로 List<dynamic>을 Map<String,dynamic>으로
meanings: List<Meaning>.from(
map['meanings'].map((e) => Meaning.fromMap(e))), //phonetics랑 똑같아
license: License.fromMap(map['license']), //데이터형 바꿔
sourceUrls: List<String>.from(map['sourceUrls']));
}
}
model/license.dart
class License {
String name;
String url;
License({
required this.name,
required this.url,
});
factory License.fromMap(Map<String, dynamic> map) {
return License(name: map['name'], url: map['url']);
}
}
model/meaning.dart
import 'definition.dart';
class Meaning {
String partOfSpeech;
List<Definition> definitions;
Meaning({
required this.partOfSpeech,
required this.definitions,
});
factory Meaning.fromMap(Map<String, dynamic> map) {
return Meaning(
partOfSpeech: map['partOfSpeech'],
definitions: List<Definition>.from(map['definitions']
.map((e) => Definition.fromMap(e))), //리스트 다이나믹으로 바꾸기
);
}
}
model/phondetic.dart
import 'package:dict/model/license.dart';
class Phonetic {
String? text; //텍스트가 없을 수 있다
String? audio;
String? sourceUrl; //없을 수 있다
License? license; //license가 없을 수도
Phonetic({
required this.text,
required this.audio,
required this.sourceUrl,
required this.license,
});
factory Phonetic.fromMap(Map<String, dynamic> map) {
return Phonetic(
text: map['text'],
audio: map['audio'],
sourceUrl: map['sourceUrl'],
license: map['license'] != null
? License.fromMap(map['license'])
: null); //라이센스 클래스 주려고
//null일 수 도 있는데 fromMap에서 오류 삼항연산자로 체크
}
}
widget/dict_card.dart
import 'package:dict/model/dict.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'meaning_card.dart';
class DictCard extends StatelessWidget {
const DictCard({super.key, required this.dict});
final Dict dict;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
dict.word,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
),
const Divider(),
ListView.builder(
physics: const NeverScrollableScrollPhysics(), //스크롤 기능 없애기
shrinkWrap: true,
//렌더링 두번 위해 listView사용
itemBuilder: ((context, index) {
return MeaningCard(meaning: dict.meanings[index]);
}),
itemCount: dict.meanings.length,
)
],
),
),
);
}
}
meaning_card.dart
import 'package:dict/model/meaning.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
class MeaningCard extends StatelessWidget {
const MeaningCard({super.key, required this.meaning});
final Meaning meaning;
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
Text(
meaning.partOfSpeech,
style: TextStyle(fontWeight: FontWeight.bold),
),
ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
physics:
const NeverScrollableScrollPhysics(), //없을 때 커스텀 위젯으로 표현하면 또 오류
itemCount: meaning.definitions.length,
itemBuilder: ((context, index) {
return ListTile(
title: Text(meaning.definitions[index].definition),
);
}),
),
],
),
);
}
}