커뮤니티
포인트
쿠폰
내 강의실
국비 신청 내역
증명서
계정
로그아웃
학습 질문
개발 일지
나의 활동
답변 완료
에뮬레이터 동작시 에러
[왕초보] 플러터(Flutter)로 시작하는 앱개발 종합반 v0
4주차
북마크
김*섭
댓글
11
추천
0
조회수
22
조회수
22
답변 완료

* 겪고 있는 문제 상황을 최대한 자세하게 작성해주세요.

* 문제 해결을 위해 어떤 시도를 해보았는지 구체적으로 함께 알려주세요.



코딩후 run without Debugging 동작시키면 에뮬레이터 화면에 에러가 표시됩니다.



스파르타 즉문즉답스파르타 즉문즉답




main.dart

// ignore_for_file: must_be_immutable, prefer_const_constructors_in_immutables, unused_local_variable


import 'package:flutter/material.dart';

import 'package:provider/provider.dart';

import 'package:shared_preferences/shared_preferences.dart';

import 'package:webview_flutter/webview_flutter.dart';


import 'book.dart';

import 'book_service.dart';


late SharedPreferences prefs;


void main() async {

WidgetsFlutterBinding.ensureInitialized();

prefs = await SharedPreferences.getInstance();

runApp(

MultiProvider(

providers: [

ChangeNotifierProvider(create: (context) => BookService()),

],

child: const MyApp(),

),

);

}


class MyApp extends StatelessWidget {

const MyApp({Key? key}) : super(key: key);


@override

Widget build(BuildContext context) {

return MaterialApp(

debugShowCheckedModeBanner: false,

home: HomePage(),

);

}

}


class HomePage extends StatefulWidget {

HomePage({Key? key}) : super(key: key);


@override

State<HomePage> createState() => _HomePageState();

}


class _HomePageState extends State<HomePage> {

var bottomNavIndex = 0;

@override

Widget build(BuildContext context) {

return Scaffold(

body: [

SearchPage(),

LikedBookPage(),

].elementAt(bottomNavIndex),

bottomNavigationBar: BottomNavigationBar(

selectedItemColor: Colors.black,

unselectedItemColor: Colors.grey,

showUnselectedLabels: true,

selectedFontSize: 12,

unselectedFontSize: 12,

iconSize: 28,

type: BottomNavigationBarType.fixed,

onTap: (value) {

setState(() {

bottomNavIndex = value;

});

},

items: [

BottomNavigationBarItem(

icon: Icon(Icons.search),

label: '검색',

),

BottomNavigationBarItem(

icon: Icon(Icons.star),

label: '좋아요',

),

],

currentIndex: bottomNavIndex,

),

);

}

}


class SearchPage extends StatelessWidget {

SearchPage({super.key});


@override

Widget build(BuildContext context) {

return Consumer<BookService>(

builder: (context, bookService, child) {

return Scaffold(

appBar: AppBar(

backgroundColor: Colors.white,

toolbarHeight: 80,

title: TextField(

onSubmitted: (value) {

bookService.search(value);

},

cursorColor: Colors.grey,

decoration: InputDecoration(

prefixIcon: Icon(Icons.search, color: Colors.grey),

hintText: "작품, 감독, 배우, 컬렉션, 유저 등",

border: OutlineInputBorder(

borderSide: BorderSide(color: Colors.white),

borderRadius: BorderRadius.all(Radius.circular(10)),

),

focusedBorder: OutlineInputBorder(

borderSide: BorderSide(color: Colors.black),

borderRadius: BorderRadius.all(Radius.circular(10)),

),

),

),

),

body: Padding(

padding: const EdgeInsets.symmetric(horizontal: 12),

child: ListView.separated(

itemCount: bookService.bookList.length,

separatorBuilder: (context, index) {

return Divider();

},

itemBuilder: (context, index) {

if (bookService.bookList.isEmpty) return SizedBox();

Book book = bookService.bookList.elementAt(index);

return BookTile(book: book);

},

),

),

);

},

);

}

}


class BookTile extends StatelessWidget {

const BookTile({

super.key,

required this.book,

});


final Book book;


@override

Widget build(BuildContext context) {

BookService bookService = context.read<BookService>();


return ListTile(

onTap: () {

Navigator.push(

context,

MaterialPageRoute(

builder: (context) => WebViewPage(

url: book.previewLink.replaceFirst("http", "https"),

),

),

);

},

leading: Image.network(

book.thumbnail,

fit: BoxFit.fitHeight,

),

title: Text(

book.title,

style: TextStyle(fontSize: 16),

),

subtitle: Text(

'${book.subtitle}\n${book.authors.join('/')}\n${book.publishedDate}',

style: TextStyle(color: Colors.grey),

),

trailing: IconButton(

onPressed: () {

bookService.toggleLikeBook(book: book); //좋아요 아이콘 눌렀을때

},

icon: bookService.likedBookList.map((book) => book.id).contains(book.id)

? Icon(

Icons.star,

color: Colors.amber,

)

: Icon(Icons.star_border),

),

);

}

}


class LikedBookPage extends StatelessWidget {

const LikedBookPage({super.key});


@override

Widget build(BuildContext context) {

return Consumer<BookService>(

builder: (context, bookService, child) {

return Scaffold(

body: Padding(

padding: const EdgeInsets.symmetric(horizontal: 12),

child: ListView.separated(

itemCount: bookService.likedBookList.length,

separatorBuilder: (context, index) {

return Divider();

},

itemBuilder: (context, index) {

if (bookService.likedBookList.isEmpty) return SizedBox();

Book book = bookService.likedBookList.elementAt(index);

return BookTile(book: book);

},

),

),

);

},

);

}

}


class WebViewPage extends StatelessWidget {

WebViewPage({super.key, required this.url});


String url;


@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

backgroundColor: Colors.grey,

title: Text(url),

),

body: WebView(initialUrl: url),

);

}

}



book_service.dart

import 'dart:convert';


import 'package:dio/dio.dart';

import 'package:flutter/material.dart';

import 'package:watcha_pedia/main.dart';


import 'book.dart';


class BookService extends ChangeNotifier {

BookService() {

loadLikeBookList();

}

List<Book> bookList = [];

List<Book> likedBookList = [];


void toggleLikeBook({required Book book}) {

String bookId = book.id;

if (likedBookList.map((book) => book.id).contains(bookId)) {

likedBookList.removeWhere((book) => book.id == bookId);

} else {

likedBookList.add(book);

}

notifyListeners();

saveLikeBookList();

}


void search(String q) async {

bookList.clear(); //검색 버튼 누를때 이전 데이터들을 지워주기


if (q.isNotEmpty) {

Response res = await Dio().get(

"https://www.googleapis.com/books/v1/volumes?q=$q&startIndex=0&maxResults=40",

);

List items = res.data["items"];


for (Map<String, dynamic> item in items) {

Book book = Book(

id: item['id'],

title: item['volumeInfo']['title'] ?? "",

subtitle: item['volumeInfo']['subtitle'] ?? "",

authors: item['volumeInfo']['authors'] ?? [],

publishedDate: item['volumeInfo']['publishedDate'] ?? '',

thumbnail: item['volumeInfo']['imageLinks']?['thumbnail'] ??

"https://thumbs.dreamstime.com/b/no-image-available-icon-flat-vector-no-image-available-icon-flat-vector-illustration-132482953.jpg",

previewLink: item['volumeInfo']['previewLink'] ?? "",

);

bookList.add(book);

}

}

notifyListeners();

}


saveLikeBookList() {

List likeBoolJosnList = likedBookList.map((book) => book.toJson()).toList();


String jsonString = jsonEncode(likeBoolJosnList);


prefs.setString('likeBookList', jsonString);

}


loadLikeBookList() {

String? jsonString = prefs.getString('likeBookList');


if (jsonString == null) return;


List likeBookJsonList = jsonDecode(jsonString);


likedBookList =

likeBookJsonList.map((json) => Book.fromJson(json)).toList();

}

}


book.dart

class Book {

String id;

String title;

String subtitle;

List authors;

String publishedDate;

String thumbnail; // 썸네일 이미지 링크

String previewLink; // ListTile 을 눌렀을 때 이동하는 링크 , 각요소


Book({

//생성자

required this.id,

required this.title,

required this.subtitle,

required this.authors,

required this.publishedDate,

required this.thumbnail,

required this.previewLink,

}); // } 이름을 지정해주는 파라미터


Map toJson() {

return {

'id': id,

'title': title,

'subtitle': subtitle,

'authors': authors,

'publishDate': publishedDate,

'thumbnail': thumbnail,

'previewLink': previewLink,

};

}


factory Book.fromJson(json) {

return Book(

id: json['id'],

title: json['title'],

subtitle: json['subtitle'],

authors: json['authors'],

publishedDate: json['publishedDate'],

thumbnail: json['thumbnail'],

previewLink: json['previewLink'],

);

}

}


취소
 공유
취소
댓글 0
댓글 알림
나의얼굴