I have Text field which searches for book ( Using google book api) and gets the result, I am trying to pass this result to a listview builder which will use Cards to display some data like title, author and so on, both of them are in different dart file. What is the best way to achieve that?
Here is the code,
Search.dart - For testing purposes I am using the cancel/clear button to get the search results. Eventually it will be onChanged. The function is named _getData()
class SearchScreen extends StatefulWidget {
const SearchScreen({Key? key}) : super(key: key);
@override
State<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final _searchController = TextEditingController();
var _userModel;
var volumeInfo;
void _getData() async {
_userModel = (await GoogleApiService().getBooks(_searchController.text))!;
setState(() {
volumeInfo = VolumeDetails.fromJson(_userModel);
print(volumeInfo);
});
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: const [
Color.fromRGBO(110, 32, 233, 1),
Color.fromRGBO(215, 110, 216, 1),
Color.fromRGBO(248, 248, 250, 1)
],
stops: [
0.03,
0.3,
0.2
])),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 330,
height: 45,
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: TextField(
onChanged: ((value) => {}),
cursorHeight: 30,
cursorColor: Colors.black,
textAlign: TextAlign.start,
textAlignVertical: TextAlignVertical.bottom,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(
Icons.cancel_rounded,
color: Color.fromRGBO(215, 110, 216, 1),
),
onPressed: () {
_getData(); // Testing for now.
//_searchController.clear();
},
),
filled: true,
fillColor: Colors.white,
hintText: 'Search for books',
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius:
const BorderRadius.all(Radius.circular(10)))),
controller: _searchController,
),
),
)
],
),
Container(
margin: EdgeInsets.fromLTRB(20, 10, 0, 10),
child: Row(
children: [
Text(
"10 Results for ",
style: TextStyle(
fontSize: 14,
color: Colors.white,
),
),
Text(
'"' _searchController.text '"',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
)),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color.fromRGBO(248, 248, 250, 1),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: SearchList(), // This one has the ListView builder
),
],
),
],
),
);
}
List builder dart
import 'package:flutter/material.dart';
import 'detail_bottom_sheet.dart';
class SearchList extends StatefulWidget {
@override
State<SearchList> createState() => _SearchListState();
SearchList({Key? key}) : super(key: key);
}
class _SearchListState extends State<SearchList> {
@override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Container(
height: 480,
margin: EdgeInsets.fromLTRB(0, 15, 0, 0),
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: 5,
itemBuilder: (context, index) {
return Card(
margin: EdgeInsets.fromLTRB(5, 0, 0, 0),
color: const Color.fromRGBO(248, 248, 250, 1),
shadowColor: Colors.transparent,
child: ListTile(
contentPadding:
EdgeInsets.symmetric(horizontal: 15, vertical: 0),
visualDensity: VisualDensity(vertical: 0, horizontal: 0),
dense: false,
title: Text('Title Comes here',
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 15),
),
subtitle: Text( 'Author comes here',
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 14)),
leading: ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child: Image.network('image url',
// volumeInfo[index].imageLinks?.thumbnail as String,
width: 40,
fit: BoxFit.fill,
),
),
trailing: IconButton(
alignment: Alignment.centerRight,
onPressed: () {
//print("--");
},
icon: Icon(Icons.add_box_outlined,
color: Color.fromRGBO(110, 32, 233, 1)),
iconSize: 20,
),
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
constraints:
BoxConstraints.loose(Size(width, height * 0.85)),
context: context,
enableDrag: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return DetailBottomSheet();
});
},
// isThreeLine: true,
));
}),
);
}
}
CodePudding user response:
there are many ways to do this. The simplest is to pass data as a parameter
your SearchScreen
widget
class SearchScreen extends StatefulWidget {
const SearchScreen({Key? key}) : super(key: key);
@override
State<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final _searchController = TextEditingController();
var _userModel;
var volumeInfo;
bool _loading=false;
void _getData() async {
setState(() {
_loading=true;
});
_userModel = (await GoogleApiService().getBooks(_searchController.text))!;
setState(() {
volumeInfo = VolumeDetails.fromJson(_userModel);
// Here it will re-render the page and update the page with the new data
//I added loading. A loading widget will appear on the screen while searching.
_loading=false;
print(volumeInfo);
});
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: const [
Color.fromRGBO(110, 32, 233, 1),
Color.fromRGBO(215, 110, 216, 1),
Color.fromRGBO(248, 248, 250, 1)
],
stops: [
0.03,
0.3,
0.2
])),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 330,
height: 45,
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
child: TextField(
onChanged: ((value) => {}),
cursorHeight: 30,
cursorColor: Colors.black,
textAlign: TextAlign.start,
textAlignVertical: TextAlignVertical.bottom,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(
Icons.cancel_rounded,
color: Color.fromRGBO(215, 110, 216, 1),
),
onPressed: () {
_getData(); // Testing for now.
//_searchController.clear();
},
),
filled: true,
fillColor: Colors.white,
hintText: 'Search for books',
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius:
const BorderRadius.all(Radius.circular(10)))),
controller: _searchController,
),
),
)
],
),
Container(
margin: EdgeInsets.fromLTRB(20, 10, 0, 10),
child: Row(
children: [
Text(
"10 Results for ",
style: TextStyle(
fontSize: 14,
color: Colors.white,
),
),
Text(
'"' _searchController.text '"',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
)),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color.fromRGBO(248, 248, 250, 1),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: _loading ? Center(child: CircularProgressIndicator.adaptive(),) : SearchList(
data:volumeInfo
), // This one has the ListView builder
),
],
),
],
),
);
}
your SearchList
widget
class SearchList extends StatefulWidget {
final List<VolumeDetails> data;
@override
State<SearchList> createState() => _SearchListState();
SearchList({Key? key,required this.data}) : super(key: key);
}
class _SearchListState extends State<SearchList> {
@override
Widget build(BuildContext context) {
return Container(
height: 480,
margin: EdgeInsets.fromLTRB(0, 15, 0, 0),
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: widget.data.length,
itemBuilder: (context, index) {
return VolumeCard(
volume:widget.data[index]
);
}),
);
}
}
your VolumeCard
widget
class VolumeCard extends StatelessWidget {
final VolumeDetails volume;
const VolumeCard({ Key? key,required this.volume }) : super(key: key);
@override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Card(
margin: EdgeInsets.fromLTRB(5, 0, 0, 0),
color: const Color.fromRGBO(248, 248, 250, 1),
shadowColor: Colors.transparent,
child: ListTile(
contentPadding:
EdgeInsets.symmetric(horizontal: 15, vertical: 0),
visualDensity: VisualDensity(vertical: 0, horizontal: 0),
dense: false,
title: Text(volume.title ?? "if the title is empty, write here default title",
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 15),
),
subtitle: Text( volume.author ?? "default authoe",
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 14)),
leading: ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child: Image.network(volume.image ?? " out no image url",
// volumeInfo[index].imageLinks?.thumbnail as String,
width: 40,
fit: BoxFit.fill,
),
),
trailing: IconButton(
alignment: Alignment.centerRight,
onPressed: () {
//print("--");
},
icon: Icon(Icons.add_box_outlined,
color: Color.fromRGBO(110, 32, 233, 1)),
iconSize: 20,
),
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
constraints:
BoxConstraints.loose(Size(width, height * 0.85)),
context: context,
enableDrag: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return DetailBottomSheet(
);
});
},
// isThreeLine: true,
));
}
}