Flutter에서 UI를 구축할 때 사용되는 lstItem
위젯은 리스트 아이템을 표시하는데 사용됩니다. 이 위젯은 MainScreen
클래스부터 QuarterlyBookListScreen
클래스, CategoryBookListScreen
등 거의 모든 클래스에서 활용되는 위젯으로, 화면에 책에 대한 정보를 표시하고 사용자가 특정 책을 탭할 때 해당 책의 상세 정보를 보여주는 데 사용됩니다.
책 제목들로 구성된 List
와, index
정보, 적응형 위젯을 위한 widthRatio
와 heightRatio
를 인자로 받아온다. 따라서 해당 listItem
에 해당하는 책 제목을 bookList[index]
와 같은 방식으로 가져옵니다.
Widget lstItem(List<String> bookList, int index, widthRatio, heightRatio){
//...
}
GestureDetector
위젯을 사용하여 리스트 아이템이 탭되었을 때 onTap
이벤트를 처리합니다. 사용자가 아이템을 탭하면 해당 책의 상세 정보를 보여주는 BookDetailScreen
으로 이동합니다.
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookDetailScreen(
bookTitle: bookList[index],
),
),
);
},
child: Container(
// 위젯 내용 생략
),
);
BookDetailScreen
화면은 아래와 같은데, 해당 클래스에 대해서는 다음 글에서 자세히 다루도록 하겠습니다.
getBookImage
함수를 사용하여 책의 이미지를 비동기적으로 로드합니다. getBookImage
함수는 Firestore
에서 bookInfos collection
에서 인자로 받은 bookTitle
과 id
값이 일치하는 문서를 가져와 해당 문서의 thumnail
필드 값을 imageUrl
에 저장한 후, getImage(imageUrl)
를 반환합니다. getImage
함수는 imageUrl
를 받아서 http 요청
을 통해 이미지 정보를 받아오는 함수입니다.
Future<Image> getBookImage(String bookTitle) async {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
QuerySnapshot querySnapshot = await _firestore
.collection('bookInfos')
.where('title', isEqualTo: bookTitle)
.limit(1)
.get();
Map<String, dynamic>? data = querySnapshot.docs[0].data() as Map<String, dynamic>?;
String imageUrl = data!['thumbnail'];
return getImage(imageUrl);
}
Future<Image> getImage(String imageUrl) async {
if (imageUrl.isEmpty) {
throw Exception('$imageUrl');
}
try {
http.Response response = await http.get(Uri.parse(imageUrl));
if (response.statusCode == 200) {
return Image.memory(response.bodyBytes);
} else {
throw Exception('Failed to load image - ${response.statusCode}');
}
} catch (e) {
throw Exception('Failed to load image - $e');
}
}
FutureBuilder
를 사용하여 이미지가 로딩 중인지, 오류가 발생했는지, 성공적으로 로딩되었는지를 확인합니다. 로딩 중일 때는 CircularProgressIndicator
를 표시하고, 오류가 발생했을 때는 기본 이미지를 표시합니다. 성공적으로 이미지를 로드했을 때는 해당 이미지를 표시합니다.
FutureBuilder(
future: getBookImage(bookList[index]),
builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Image.asset(
'assets/app_logo.png',
width: widthRatio * 105,
fit: BoxFit.fill,
);
} else {
return Container(
// 이미지 표시 및 스타일 설정
);
}
},
),
getAladinDescription
함수는 Firestore
에서 aladinBooks collection
에서 인자로 받은 bookTitle
과 id
값이 일치하는 문서를 가져와 해당 문서의 description
필드 값을 bookContents
에 저장한 후 이를 반환합니다.
Future<String> getAladinDescription(String bookTitle) async {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
QuerySnapshot querySnapshot = await _firestore
.collection('aladinBooks')
.where('title', isEqualTo: bookTitle)
.limit(1)
.get();
Map<String, dynamic>? data = querySnapshot.docs[0].data() as Map<String, dynamic>?;
String bookContents = data!['description'];
return bookContents;
}
Column
위젯을 사용하여 책 제목과 설명을 표시합니다. 책 제목은 Row
위젯을 사용하여 표시하고, 책 설명은 FutureBuilder
를 사용하여 비동기적으로 로드한 후 표시합니다. 설명을 로드하는 동안 로딩 인디케이터를 표시하고, 오류가 발생한 경우 오류 메시지를 표시합니다. 성공적으로 책 설명을 로드한 경우 해당 설명을 표시합니다.
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 책 제목 표시
Row(
children: [
// 책 제목 설정 및 스타일
],
),
// 책 설명 표시
FutureBuilder(
future: getAladinDescription(bookList[index]),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text(
'책소개를 불러오는데 실패했습니다.',
style: TextStyle(
// 텍스트 스타일 설정
),
);
} else {
return Container(
// 책 설명 표시 및 스타일 설정
);
}
},
),
],
),
Widget lstItem(List<String> bookList, int index, widthRatio, heightRatio) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookDetailScreen(
bookTitle: bookList[index],
),
),
);
},
child: Container(
margin: EdgeInsets.only(bottom: heightRatio * 20),
padding: EdgeInsets.symmetric(
vertical: heightRatio * 10,
horizontal: widthRatio * 10,
),
decoration: ShapeDecoration(
color: Colors.white.withOpacity(0),
shape: RoundedRectangleBorder(
side: BorderSide(width: 2, color: Color(0xBFFFFFFF)),
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
margin: EdgeInsets.only(left: widthRatio * 5),
child: Row(
children: [
FutureBuilder(
future: getBookImage(bookList[index]),
builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Image.asset(
'assets/app_logo.png',
width: widthRatio * 105,
fit: BoxFit.fill,
);
} else {
return Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Color(0x3F000000),
blurRadius: 5,
offset: Offset(2, 4),
spreadRadius: 3,
)
],
),
child: Image(
image: snapshot.data!.image,
width: widthRatio * 105,
height: widthRatio * 105 * 1.48,
fit: BoxFit.fill,
),
);
}
},
),
Container(
margin: EdgeInsets.only(left: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
margin: EdgeInsets.only(bottom: heightRatio * 20),
constraints: BoxConstraints(
maxWidth: 200.0,
),
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text(
bookList[index],
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xE5001F3F),
fontSize: 18,
fontFamily: 'GowunBatang',
fontWeight: FontWeight.bold,
height: 0,
letterSpacing: -0.40,
),
),
),
),
],
),
FutureBuilder(
future: getAladinDescription(bookList[index]),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text(
'책소개를 불러오는데 실패했습니다.',
style: TextStyle(
color: Color(0xE5001F3F),
fontFamily: 'GowunBatang',
fontWeight: FontWeight.bold,
height: 0,
letterSpacing: -0.40,
),
);
} else {
return Container(
width: widthRatio * 190,
height: heightRatio * 101,
child: Text(
snapshot.data!,
maxLines: 4,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xE5001F3F),
fontFamily: 'GowunBatang',
fontWeight: FontWeight.bold,
height: 0,
letterSpacing: -0.40,
),
),
);
}
},
),
],
),
),
],
),
),
),
);
}