How to use flutter_typeahead with FirestoreFirebase to generate suggestions? - flutter

class Header extends StatefulWidget {
#override
_HeaderState createState() => _HeaderState();
Future<List> getSearch() async {
List<String> searchList;
final List<DocumentSnapshot> documents =
(await FirebaseFirestore.instance.collection('movies').get()).docs;
searchList = documents
.map((documentSnapshot) => documentSnapshot['movieName'] as String)
.toList();
print(searchList); //This does print the data from the database.
return searchList;
}
}
The above code fetches data from FirebaseFirestore and also the print statement prints the list fetched.
class _HeaderState extends State<Header> {
final MenuController _controller = Get.put(MenuController());
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: kDarkBlackColor,
child: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(kDefaultPadding),
constraints: BoxConstraints(maxWidth: kMaxWidth),
child: Column(
children: [
Row(
children: [
Container(
decoration: BoxDecoration(
borderRadius:
new BorderRadius.all(Radius.circular(10.0)),
shape: BoxShape.rectangle,
border: Border.all(color: Colors.white)),
child: IconButton(
icon: Icon(
Icons.menu,
color: Colors.white,
),
onPressed: () {
_controller.openOrCloseDrawer();
},
),
),
Container(
width: MediaQuery.of(context).size.width / 7,
child: Image.asset('assets/images/logo.png')),
Spacer(),
Container(
color: Colors.white,
width: MediaQuery.of(context).size.width / 5,
child: TypeAheadField(
hideOnEmpty: true,
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
style: DefaultTextStyle.of(context)
.style
.copyWith(fontStyle: FontStyle.italic),
decoration: InputDecoration(
border: OutlineInputBorder())),
suggestionsCallback: (pattern) async {
return CitiesService.getSuggestions(pattern);
},
transitionBuilder:
(context, suggestionsBox, controller) {
return suggestionsBox;
},
itemBuilder: (context, suggestion) {
return ListTile(
title: Text(suggestion),
);
},
onSuggestionSelected: (suggestion) {},
)),
Spacer(),
Socal(),
//Spacer(),
],
),
],
),
)
],
),
),
);
}
}
class CitiesService {
static List<String> search = Header().getSearch() as List<String>; //This is not adding data to list
static List<String> getSuggestions(String query) {
print(search); //This does not print any thing.
List<String> matches = List();
matches.addAll(search);
matches.retainWhere((s) => s.toLowerCase().contains(query.toLowerCase()));
return matches;
}
}
I am trying to store the data fetched in getSearch() into search List so that I can use it to provide suggestions but the list is empty. I don't know if this is the correct way to convert future into list of strings. Help would be really appreciated. Also, if there is another way to implement search from FirebaseFirestore, please do let me know.
Thanks in advance.

The problem here is that getSearch is an async function, which mean that it return intially a Future while is awaiting the async part of the function to be fulfilled and continue to execute, so in order to capture that you should be using a future.then() notation, doing something like this while calling getSearch will fix the issue:
Header().getSearch().then((value) {
static List<String> search = value as List<String>;
...
});

Related

how to copy a list of files after selecting multiple files inside listview.builder

i selected multiple files inside listview now i want to copy them. And then want to create input filed to make folder and zip files there.
List view builder
ListView.builder(
itemCount: files?.length ?? 0,
itemBuilder: (context, index) {
return InkWell(
onTap: () {},
child: Container(
child: MultiSelectItem(
isSelecting: myMultiSelectController.isSelecting,
onSelected: () {
setState(() {
myMultiSelectController.toggle(index);
});
},
child: Card(
color: myMultiSelectController.isSelected(index)
? Colors.blueAccent
: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7)),
),
child: Center(
child: Text(files[index].path,
),
),
),
)
),
);
},
),
function to copy files
void copy() {
setState(() {
myMultiSelectController.set(files?.length ?? 0);
});
}
Solution ^^: get the selected files by using controller.isSelected inside the for loop
for(int i=0;i<multiSelectList.length;i++)
if(controller.isSelected(i)){
files.add(multiSelectList[i].path);
}
Example
class _MultiSelectListDemoState extends State<MultiSelectListDemo> {
List<FileModel> multiSelectList = [];
MultiSelectController controller = new MultiSelectController();
#override
void initState() {
super.initState();
multiSelectList.add(FileModel(path: 'assets/1.jpeg', name:"Welcome to New York City!"));
multiSelectList.add(FileModel(path: 'assets/2.jpeg', name:"Welcome to San francisco!"));
multiSelectList.add(FileModel(path: 'assets/3.jpeg', name:"Welcome to Chicago!"));
multiSelectList.add(FileModel(path: 'assets/4.jpeg', name:"Welcome to Houston!"));
controller.disableEditingWhenNoneSelected = true;
controller.set(multiSelectList.length);
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
var before = !controller.isSelecting;
setState(() {
controller.deselectAll();
});
return before;
},
child: new Scaffold(
appBar: new AppBar(
title: new Text('Selected ${controller.selectedIndexes.length}' ),
),
body:Column(
children: [
Expanded(child: ListView.builder(
itemCount: multiSelectList.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {},
child: MultiSelectItem(
isSelecting: controller.isSelecting,
onSelected: () {
setState(() {
controller.toggle(index);
});
},
child:Container(
height:75,
margin: EdgeInsets.only(left:15,right:15,top:15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.transparent,
),
child:Card(
color:controller.isSelected(index)
? Colors.grey.shade200:Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
child:Padding(
padding:EdgeInsets.symmetric(vertical:10, horizontal: 12),
child: Row(
children: [
//contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
ClipRRect(
borderRadius: BorderRadius.circular(12),
child:Image.asset(multiSelectList[index].path,fit:BoxFit.cover,width:60,height:60,),
),
SizedBox(width:20,),
Text(multiSelectList[index].name, style: TextStyle(fontSize:14)),
],
),
),
),
),
),
);
},
)),
ElevatedButton(onPressed: (){
List<String> files = List.empty(growable: true);
for(int i=0;i<multiSelectList.length;i++)
if(controller.isSelected(i)){
files.add(multiSelectList[i].path);
}
print("selected files $files");
}, child: Text("zip"))
],
),
),
);
}
}
class FileModel{
final String path;
final String name;
FileModel({required this.path, required this.name});
#override
String toString() {
return 'FileModel{name: $name}';
}
}

Update State without reloading a widget in Flutter

I have a widget on a screen that receives its data from API calls. The API call is made inside the init method of the Navigation Bar so that continuous API calls can be prevented when going back and forth between screens. Although this works fine, I'm facing a real challenge in trying to get the state of the widget updated when new data is added to that particular API that the widget relies on for displaying data. I would therefore need to know how to display the updated data that I added to the Database by making a post request on a different screen. The only way this happens now is by way of reloading the entire app or by killing it. Any help will be appreciated.
This is the NavBar where the API is getting called. I usually make all the API calls at once here and something I have done here too.
NavBar
class CustomBottomNavigationState extends State<CustomBottomNavigation> {
bool isLoading = true;
int index = 2;
final screens = [
MenuScreen(),
LeaveScreen(),
// TaskList(),
HomeScreen(),
// PaySlipScreen(),
TaskList(),
Claimz_category(),
// ClaimzScreen()
];
#override
void initState() {
// TODO: implement initState
Provider.of<LeaveRequestViewModel>(context, listen: false)
.getLeaveRequest()
.then((value) {
Provider.of<AnnouncementViewModel>(context, listen: false)
.getAllAnouncements()
.then((value) {
Provider.of<TodaysTaskList>(context, listen: false)
.getTodaysTasks() //This is the API call in question
.then((value) {
setState(() {
isLoading = false;
});
});
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
final items = ['The icons are stored here'];
// TODO: implement build
return SafeArea(
child: Scaffold(
body: isLoading
? const Center(
child: CircularProgressIndicator(),
)
: screens[index],
extendBody: true,
bottomNavigationBar: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(200),
topRight: Radius.circular(200)),
boxShadow: [
BoxShadow(
color: Colors.transparent,
blurRadius: 10,
offset: Offset(1, 2))
]),
child: CurvedNavigationBar(
items: items,
index: index,
height: 60,
color: const Color.fromARGB(255, 70, 70, 70),
backgroundColor: Colors.transparent,
onTap: (index) => setState(() {
this.index = index;
})),
),
),
);
}
}
ToDoList widget(This the widget where the updates never reflect without reloading)
class ToDoListState extends State<ToDoList> {
#override
Widget build(BuildContext context) {
final toDoList = Provider.of<TodaysTaskList>(context).getToDoList; //This is the getter method that stores the data after it has been fetched from API
// TODO: implement build
return ContainerStyle(
height: SizeVariables.getHeight(context) * 0.35,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(
top: SizeVariables.getHeight(context) * 0.015,
left: SizeVariables.getWidth(context) * 0.04),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
// color: Colors.red,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
'To do list',
style: Theme.of(context).textTheme.caption,
),
),
),
],
),
),
SizedBox(height: SizeVariables.getHeight(context) * 0.01),
Padding(
padding: EdgeInsets.only(
left: SizeVariables.getWidth(context) * 0.04,
top: SizeVariables.getHeight(context) * 0.005,
right: SizeVariables.getWidth(context) * 0.04),
child: SizedBox(
height: SizeVariables.getHeight(context) * 0.25,
child: Container(
// color: Colors.red,
child: toDoList['today'].isEmpty
? Center(
child: Lottie.asset('assets/json/ToDo.json'),
)
: ListView.separated(
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => Row(
children: [
Icon(Icons.circle,
color: Colors.white,
size:
SizeVariables.getWidth(context) * 0.03),
SizedBox(
width:
SizeVariables.getWidth(context) * 0.02),
FittedBox(
fit: BoxFit.contain,
child: Text(
toDoList['today'][index]['task_name'], //This is where it is used
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodyText1),
)
],
),
separatorBuilder: (context, index) => Divider(
height: SizeVariables.getHeight(context) * 0.045,
color: Colors.white,
thickness: 0.5,
),
itemCount: toDoList['today'].length > 4
? 4
: toDoList['today'].length),
),
),
)
],
),
);
}
}
The other widget where the date gets added
class _TaskListState extends State<TaskList> {
#override
Widget build(BuildContext context) {
var floatingActionButton;
return Scaffold(
backgroundColor: Colors.black,
floatingActionButton: Container(
....
....,
child: FloatingActionButton(
backgroundColor: Color.fromARGB(255, 70, 69, 69),
onPressed: openDialog, //This is the method for posting data
child: Icon(Icons.add),
),
),
),
body: Container(
....
....
....
),
);
}
Future<dynamic> openDialog() => showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Color.fromARGB(255, 87, 83, 83),
content: Form(
key: _key,
child: TextFormField(
controller: taskController,
maxLines: 5,
style: Theme.of(context).textTheme.bodyText1,
decoration: InputDecoration(
border: InputBorder.none,
),
validator: (value) {
if (value!.isEmpty || value == '') {
return 'Please Enter Task';
} else {
input = value;
}
},
),
),
actions: [
InkWell(
onTap: () async {
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2010),
lastDate:
DateTime.now().add(const Duration(days: 365)))
.then((date) {
setState(() {
_dateTime = date;
});
print('Date Time: ${dateFormat.format(_dateTime!)}');
});
},
child: const Icon(Icons.calendar_month, color: Colors.white)),
TextButton(
child: Text(
"Add",
style: Theme.of(context).textTheme.bodyText1,
),
onPressed: () async {
Map<String, dynamic> _data = {
'task': taskController.text,
'task_date': dateFormat.format(_dateTime!).toString()
};
print(_data);
if (_key.currentState!.validate()) {
await Provider.of<ToDoViewModel>(context, listen: false)
.addToDo(_data, context) //This is the post method
.then((_) {
Navigator.of(context).pop();
Provider.of<TodaysTaskList>(context, listen: false)
.getTodaysTasks(); //I did this here again to re-initialize the data. I was under the impression that the new data would get initialized for the widget to reflect it on the other screen.
});
}
},
),
],
),
);
void add() {
Navigator.of(context).pop();
}
}
The Get API Call
class TodaysTaskList with ChangeNotifier {
Map<String, dynamic> _getToDoList = {};
Map<String, dynamic> get getToDoList {
return {..._getToDoList};
}
Future<void> getTodaysTasks() async {
SharedPreferences localStorage = await SharedPreferences.getInstance();
var response = await http.get(Uri.parse(AppUrl.toDoList), headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${localStorage.getString('token')}'
});
if (response.statusCode == 200) {
_getToDoList = json.decode(response.body);
} else {
_getToDoList = {};
}
print('TO DO LIST: $_getToDoList');
notifyListeners();
}
}
Please let me know for additional input.
i think it's because you didn't call the provider to update your state correctly
as i see that you declare new variable to store your provider like this
final toDoList = Provider.of<TodaysTaskList>(context).getToDoList;
then you use it like this
Text(
toDoList['today'][index]['task_name'], //This is where it is used
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodyText1),
)
it's not updating the state, you should wrap the widget that need to be updated with Consumer
Consumer<TodaysTaskList>(
builder: (context, data, child) {
return _Text(
data.[your_list]['today'][index]['task_name'],
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText1),
);
},
);

Flutter with Firebase, Search by multiple queries

I am trying to create a search feature that allows users to search by their username, email, designation or company. I intend to let users have the flexibility of typing any of those into the search bar and search for their target. However, what i am facing is that my code below only calls on the first function and displays results only for username (username is the first function here, can be interchanged with other functions and it will call according to that). my code is below and thanks for all the help in advance.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:ib_club/services/database.dart';
import 'package:ib_club/widgets/widget.dart';
class SearchScreen extends StatefulWidget {
#override
State<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
DatabaseMethods databaseMethods = new DatabaseMethods();
TextEditingController searchTextEditingController =
new TextEditingController();
QuerySnapshot<Map<String, dynamic>> searchSnapshot;
initiateUsernameSearch() {
databaseMethods
.getUserByUsername(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateEmailSearch() {
databaseMethods
.getUserByEmail(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateDesignationSearch() {
databaseMethods
.getUserByDesignation(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateCompanySearch() {
databaseMethods
.getUserByCompany(searchTextEditingController.text)
.then((val) {
setState(() {
searchSnapshot = val;
});
});
}
initiateSearch() {
initiateUsernameSearch();
initiateEmailSearch();
initiateDesignationSearch();
initiateCompanySearch();
}
// Create chatroom, send user to conversation screen, pushreplacement
/*createChatroomAndStartConversation(String userUsername) {
List<String> users = [
userUsername,
];
databaseMethods.createChatRoom();
}*/
Widget searchList() {
return searchSnapshot != null
? ListView.builder(
itemCount: searchSnapshot.docs.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return SearchTile(
userUsername: searchSnapshot.docs[index].data()["username"],
userEmail: searchSnapshot.docs[index].data()["email"],
userDesignation:
searchSnapshot.docs[index].data()["designation"],
userCompany: searchSnapshot.docs[index].data()["company"],
);
})
: Container(
/*child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
const Color(0XffFBD24F))))*/
);
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBarMain(context),
body: Container(
child: Column(children: [
SizedBox(
height: 16,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
children: [
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
const Color(0x36FFFFFF),
const Color(0x0FFFFFF)
]),
borderRadius: BorderRadius.circular(40)),
child: TextField(
controller: searchTextEditingController,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
hintText: "Search User",
hintStyle: TextStyle(color: Colors.white54),
border: InputBorder.none),
),
)),
GestureDetector(
onTap: () {
initiateSearch();
},
child: Container(
height: 45,
width: 45,
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
const Color(0x36FFFFFF),
const Color(0x0FFFFFF)
]),
borderRadius: BorderRadius.circular(45)),
child: Icon(Icons.search,
size: 30, color: const Color(0XffFBD24F))),
),
],
),
),
searchList()
]),
));
}
}
class SearchTile extends StatelessWidget {
final String userUsername;
final String userEmail;
final String userDesignation;
final String userCompany;
SearchTile(
{this.userUsername,
this.userEmail,
this.userDesignation,
this.userCompany});
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [const Color(0x36FFFFFF), const Color(0x0FFFFFF)]),
borderRadius: BorderRadius.horizontal()),
child: Row(children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userUsername,
style: mediumWhiteTextStyle(),
),
Text(
userEmail,
style: mediumWhiteTextStyle(),
),
Text(
userDesignation,
style: mediumWhiteTextStyle(),
),
Text(
userCompany,
style: mediumWhiteTextStyle(),
)
],
),
),
Spacer(),
GestureDetector(
onTap: () {},
child: Container(
decoration: BoxDecoration(
color: const Color(0XffFBD24F),
borderRadius: BorderRadius.circular(30)),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text(
"Message",
style: mediumTextStyle(),
),
),
)
]),
);
}
}
I could not give a straight answer base on your code but I have a search concept, which I am hoping to help you in any way.
In my case, I always fetch all my item data from firebase and put it in "overallItems" (which is a List of Item Model). From there, I can now start to filter/search my list and return the result.
The concept I use is like this:
List<Item> filterItems() {
//first, I would want to have a temporary holder for a copy of my original items
final List<Item> itemHolder = List<Item>.from(overallItems);
//If user has title input on the search bar
if (titleKeyword.text != '') {
//then this for loop will iterate to all my data
for (final item in List<Item>.from(itemHolder)) {
//will check each title from the list
if (!item.title.toLowerCase().contains(titleKeyword.text.toLowerCase())) {
//elimate items that did not qualify
itemHolder.remove(item);
}
}
}
//so after the first if, itemHolder will be left with the result....
//now, I would also like to search the seller name.
//then, I would just have to repeat the process above
//Searching for Seller Name
if (sellerKeyword.text != '') {
for (final item in List<Item>.from(itemHolder)) {
if (!item.sellerName
.toLowerCase()
.contains(sellerKeyword.text.toLowerCase())) {
itemHolder.remove(item);
}
}
}
//the itemHolder list will have the result
return itemHolder;
}
In the end, we will have the search result after the elimination process.

How to solve: type 'List<dynamic>' is not a subtype of type 'String' [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 2 years ago.
Improve this question
I want to update image from listofTaskNotApprove class which it will pass the object of documentsnapshot into the EditTaskNotApprove. Before I update the image, I need to display specific of image where the user will be select specific info from listofTaskNotApprove. The problem is how to display the current index of image into the new screen?
ListOfTaskNotAccepted class.
import 'package:carousel_pro/carousel_pro.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:fyp/screen/RecordOfficer/EditTaskNotApprove.dart';
import 'package:fyp/shared/Loading.dart';
import 'package:google_fonts/google_fonts.dart';
class ListOfTaskNotAccepted extends StatefulWidget {
#override
_ListOfTaskNotAcceptedState createState() => _ListOfTaskNotAcceptedState();
}
final FirebaseAuth auth = FirebaseAuth.instance;
Stream<QuerySnapshot> getUser(BuildContext context) async* {
final FirebaseUser rd = await auth.currentUser();
yield* Firestore.instance.collection("Task").where('uid',isEqualTo: rd.uid).where("verified", isEqualTo: 'TidakSah').snapshots();
}
class _ListOfTaskNotAcceptedState extends State<ListOfTaskNotAccepted> {
List<NetworkImage> _listOfImages = <NetworkImage>[];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Aduan Tidak Diterima"),
backgroundColor: Colors.redAccent,
),
body: Container(
child: StreamBuilder(
stream: getUser(context),
builder: (context, snapshot){
if (snapshot.hasError || !snapshot.hasData) {
return Loading();
} else{
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index){
DocumentSnapshot da = snapshot.data.documents[index];
_listOfImages =[];
for(int i =0; i <da['url'].length; i++){
_listOfImages.add(NetworkImage(da['url'][i]));
}
return Card(
child:ListTile(
title: Container(
alignment: Alignment.centerLeft,
child: Column(
children: <Widget>[
SizedBox(height: 5.0),
Container(alignment: Alignment.centerLeft,
child: Row(
children: [
Text("Sumber Aduan: ", style: GoogleFonts.asap(fontWeight: FontWeight.bold)),
Text(da['sumberAduan'], style: GoogleFonts.asap(fontWeight: FontWeight.bold)),
],
),
),
SizedBox(height: 5.0),
Container(alignment: Alignment.centerLeft,
child: Row(
children: [
Text("Nombor Aduan: ", style: GoogleFonts.lato(fontWeight: FontWeight.bold)),
Text(da['noAduan'], style: GoogleFonts.lato(fontWeight: FontWeight.bold)),
],
),
),
SizedBox(height: 5.0),
Container(alignment: Alignment.centerLeft,
child: Row(
children: [
Text("Lokasi: ", style: GoogleFonts.lato(fontWeight: FontWeight.bold)),
Text(da['kawasan'] + " " + da['naJalan'], style: GoogleFonts.lato(fontWeight: FontWeight.bold)),
],
),
),
SizedBox(height: 5.0),
Container(alignment: Alignment.centerLeft,
child: Row(
children: [
Text("Kategori: ", style: GoogleFonts.arimo(fontWeight: FontWeight.w500)),
Text(da['kategori'], style: GoogleFonts.arimo(fontWeight: FontWeight.w500)),
],
),
),
Column(
children: [
Container(
margin: EdgeInsets.all(10.0),
height: 200,
decoration: BoxDecoration(
color: Colors.white
),
width: MediaQuery.of(context).size.width,
child: Carousel(
boxFit: BoxFit.cover,
images: _listOfImages,
autoplay: false,
indicatorBgPadding: 5.0,
dotPosition: DotPosition.bottomCenter,
animationCurve: Curves.fastLinearToSlowEaseIn,
animationDuration: Duration(milliseconds: 2000),
),
)
],
)
],
),
),
subtitle: Container(
child: Column(
children: [
SizedBox(height: 5.0),
Container(alignment: Alignment.centerLeft,
child: Row(
children: [
Text("Catatan: ", style: GoogleFonts.arimo(fontWeight: FontWeight.w500)),
Text(da['comments'], style: GoogleFonts.arimo(fontWeight: FontWeight.w500)),
],
),
),
],
),
),
onTap: () {Navigator.push(context, MaterialPageRoute(builder: (context) => EditTask(da:da)));}
)
);
});
}
}),
)
);
}
}
Here is EditTask class which I need to display current index of image that selected by user.
import 'package:carousel_pro/carousel_pro.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class EditTask extends StatefulWidget {
final DocumentSnapshot da;
const EditTask({Key key, this.da}) : super(key: key);
#override
_EditTaskState createState() => _EditTaskState(da);
}
class _EditTaskState extends State<EditTask> {
DocumentSnapshot da;
_EditTaskState(DocumentSnapshot da){
this.da = da;
}
TextEditingController _noAduan;
TextEditingController _sumberAduan;
TextEditingController _kategori;
DateTime myDateTime = DateTime.now();
#override
void initState(){
super.initState();
_noAduan = TextEditingController(text: widget.da.data['noAduan']);
_sumberAduan =TextEditingController(text: widget.da.data['sumberAduan']);
_kategori = TextEditingController(text: widget.da.data['kategori']);
myDateTime = (da.data['date']).toDate();
_listOfImages = NetworkImage(da.data['url']) as List<NetworkImage>; // this line show the error
}
List <String> sumber = <String> ['Sistem Aduan MBPJ', 'Sistem Aduan Waze', 'Sistem Aduan Utiliti'];
List <String> kate = <String> ['Segera', 'Pembaikan Biasa'];
String kategori;
String sumberAduan;
List <NetworkImage> _listOfImages = <NetworkImage>[];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Kemaskini Aduan"),
backgroundColor: Colors.redAccent,
),
body: Container(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SizedBox(height: 10.0),
TextFormField(
decoration:InputDecoration(
hintText: myDateTime.toString(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5))),
onChanged: (value){
setState(() {
myDateTime = value as DateTime;
print(myDateTime);
});
},
),
SizedBox(height: 10.0),
TextFormField(
decoration:InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5))),
controller: _noAduan,
),
SizedBox(height: 10.0),
DropdownButtonFormField(
hint:Text(widget.da.data['sumberAduan']),
decoration: InputDecoration(
prefixIcon: Icon(Icons.perm_contact_calendar),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(5))),
isExpanded: true,
value: sumberAduan,
onChanged: (newValue) {
setState(() {
sumberAduan = newValue;
_sumberAduan.text = sumberAduan;
});
},
items: sumber.map((sum){
return DropdownMenuItem(
value: sum,
child: new Text(sum),
);
}).toList(),
),
SizedBox(height: 10.0),
DropdownButtonFormField(
hint:Text(widget.da.data['kategori']),
decoration: InputDecoration(
prefixIcon: Icon(Icons.perm_contact_calendar),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(5))),
isExpanded: true,
value: kategori,
onChanged: (newValue) {
setState(() {
kategori = newValue;
_kategori.text = kategori;
});
},
items: kate.map((ka){
return DropdownMenuItem(
value: ka,
child: new Text(ka),
);
}).toList(),
),
Container(
margin: EdgeInsets.all(10.0),
height: 200,
decoration: BoxDecoration(
color: Colors.white
),
width: MediaQuery.of(context).size.width,
child: Carousel(
boxFit: BoxFit.cover,
autoplay: false,
//images: _listOfImages,
indicatorBgPadding: 5.0,
dotPosition: DotPosition.bottomCenter,
animationCurve: Curves.fastLinearToSlowEaseIn,
animationDuration: Duration(milliseconds: 2000),
),
)
],
),
),
)
);
}
}
the image that need to display for update image
This is how I want to display image in class EditTask when the user want to update information from ListOfTaskNotApprove
The error show that "type 'List' is not a subtype of type 'String'"
Can someone help me? because I had tried many method to solve this problem but it didn't work for me.
You say:
_listOfImages = NetworkImage(da.data['url']) as List<NetworkImage>; // this line show the error
Yes, you can't cast a NetworkImage to a List<NetworkImage>. You probably meant:
_listOfImages = [ NetworkImage(da.data['url']) ] as List<NetworkImage>;
The thing is your are getting a list of url's from Firebase storage which are Strings but you are adding this strings into a list of type Network Image which is wrong. As a list of string cannot be converted to a list of Network Image.
There are 2 ways to resolve this-
Change your list type to List and then wherever you show this image use
list data as an argument for Network Image.
List<String> urls=new List();
//suppose list is not empty
....
return NetworkImage(urls[i]);
....
While adding url's to your list add an Network Image object and directly use the list item while showing images.
List<NetworkImage> list=new List();
list.add(NetworkImage('some url'));
....
return list[i];
....

GlobalKey<FormState>().currentState.save() is falling when I submit a form in Flutter

Using bloc from rxdart: ^0.24.1
I am trying to save object on mysql. The first try the object get saved succefully, the second try, with a new object, it falling on formKey.currentState.save(). I am using GlobalKey<FormState>() in order to validate the form with Stream
My code is
class DetailGamePage extends StatefulWidget {
#override
_DetailGameState createState() => _DetailGameState();
}
class _DetailGameState extends State<DetailGamePage> {
final formKey = GlobalKey<FormState>();
GameBloc gameBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (gameBloc == null) {
gameBloc = Provider.gameBloc(context);
}
}
#override
Widget build(BuildContext context) {
Game _game = ModalRoute.of(context).settings.arguments;
if (_game == null) {
_game = Game(
color: "#000000",
description: "",
env: "",
isBuyIt: false,
isOnBacklog: false);
}
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
title: Text(
"Add Game",
style: TextStyle(color: Colors.black),
),
actions: [
FlatButton(
onPressed: () {
if (formKey.currentState.validate()) {
formKey.currentState.save();
Fluttertoast.showToast(msg: "Game saved");
setState(() {
gameBloc.saveOrUpdate(_game, gameBloc.name,
gameBloc.description, "listGame");
});
Navigator.pushReplacementNamed(context, "home");
}
},
child: Text(
(StringUtils.isNullOrEmpty(_game.id)) ? "Add" : "Update",
style: TextStyle(color: HexColor(_game.color), fontSize: 20),
))
],
),
body: Form(
key: formKey,
child: Stack(children: <Widget>[
_createBackground(context, _game),
_createFormGame(context, _game, gameBloc)
]),
));
}
Widget _createBackground(BuildContext context, Game game) {
final size = MediaQuery.of(context).size;
final gradientTop = Container(
height: size.height, //* 0.4,
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: <Color>[HexColor(game.color), Colors.white])),
);
final circule = Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0),
color: Color.fromRGBO(255, 255, 255, 0.1)),
);
return Stack(
children: <Widget>[
gradientTop,
Positioned(
child: circule,
top: 90,
left: 50,
),
Positioned(
child: circule,
top: -40,
right: -30,
),
Container(
padding: EdgeInsets.only(top: 80),
child: Column(
children: <Widget>[
SizedBox(
height: 10.0,
width: double.infinity,
),
],
),
)
],
);
}
Widget _createFormGame(BuildContext context, Game game, GameBloc gameBloc) {
final size = MediaQuery.of(context).size;
return SingleChildScrollView(
child: Column(
children: <Widget>[
SafeArea(
child: Container(
height: 80.0,
)),
Container(
width: size.width * 0.85,
padding: EdgeInsets.symmetric(vertical: 50.0),
margin: EdgeInsets.symmetric(vertical: 30.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5.0),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black26,
blurRadius: 3.0,
offset: Offset(0.0, 5.0),
spreadRadius: 3.0)
]),
child: Column(
children: <Widget>[
Text("Foto", style: TextStyle(fontSize: 20.0)),
SizedBox(
height: 50.0,
),
_createNameImput(gameBloc, game),
_createDescriptionImput(gameBloc, game),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
_createWasGameImput(gameBloc, game),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
_createToTheBacklogImput(gameBloc, game),
SizedBox(height: 60),
_createDeleteButton(gameBloc, game),
SizedBox(height: 60),
],
))
],
),
);
}
#override
void dispose() {
gameBloc?.dispose();
super.dispose();
}
Widget _createWasGameImput(GameBloc gameBloc, Game game) {
return StreamBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: SwitchListTile(
activeColor: HexColor(game.color),
title: Text("Do you have it?"),
value: game.isBuyIt,
onChanged: (bool value) {
setState(() {
game.isBuyIt = value;
});
},
secondary: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: null,
color: HexColor(game.color),
),
));
},
);
}
Widget _createToTheBacklogImput(GameBloc gameBloc, Game game) {
return StreamBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: SwitchListTile(
activeColor: HexColor(game.color),
title: Text("To the backlog?"),
value: game.isOnBacklog,
onChanged: (bool value) {
setState(() {
game.isOnBacklog = true;
});
},
secondary: IconButton(
icon: Icon(Icons.list),
onPressed: null,
color: HexColor(game.color),
),
));
},
);
}
Widget _createNameImput(GameBloc gamebloc, Game game) {
return Column(children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: game.name,
onSaved: (value) {
gameBloc.setName(value);
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Name",
icon: Icon(
Icons.games,
color: HexColor(game.color),
)),
),
),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
]);
}
Widget _createDescriptionImput(GameBloc gameBloc, Game game) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: game.description,
onSaved: (value) {
gameBloc.setDescription(value);
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Description",
icon: Icon(
Icons.description,
color: HexColor(game.color),
)),
),
);
}
Widget _createDeleteButton(GameBloc gameBloc, Game game) {
if (StringUtils.isNotNullOrEmpty(game.id)) {
return FlatButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text("Do you wan to remove the game"),
actions: <Widget>[
FlatButton(
onPressed: () {
setState(() {
gameBloc.remove(game, "listGame");
});
Navigator.pop(context);
Navigator.pop(context);
},
child: Text("Yes")),
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text("No"))
],
);
});
},
child: Text("Remove Game"));
} else {
return Container();
}
}
}
This is the bloc
class GameBloc extends Validators {
//Controller
final _allDataGames = BehaviorSubject<List<Game>>();
final _descriptionController = BehaviorSubject<String>();
final _nameController = BehaviorSubject<String>();
final _allMyListGamesByNameController = BehaviorSubject<List<Game>>();
//Services
GameService gameService = GameService();
//get Data from streams
Stream<List<Game>> get allGameData => _allDataGames.stream;
Stream<List<Game>> get allGameByNameList =>
_allMyListGamesByNameController.stream;
Stream<String> get getDescriptionStream =>
_descriptionController.stream.transform(validateDescription);
Stream<String> get getNameStream =>
_nameController.stream.transform(validName);
//Observable
Stream<bool> get validateDescriptionStream =>
Rx.combineLatest([getDescriptionStream], (description) => true);
Stream<bool> get validateNameStream =>
Rx.combineLatest([getNameStream], (name) => true);
//Set Stream
Function(String) get setDescription => _descriptionController.sink.add;
Function(String) get setName => _nameController.sink.add;
//Get Stream
//From repo
void allGames() async {
List<Game> games = await gameService.getAllDataGames();
_allDataGames.sink.add(games);
}
//From my setting
void allMyListGamesByName(String listName) async {
List<Game> games = await gameService.allMyListGamesByName(listName);
_allMyListGamesByNameController.sink.add(games);
}
void saveOrUpdate(
Game game, String name, String description, String listGame) {
game.name = name;
game.description = description;
if (StringUtils.isNullOrEmpty(game.id)) {
game.id = Uuid().v1();
gameService.add(game, listGame);
} else {
gameService.update(game);
}
}
void remove(Game game, String listGame) {
gameService.remove(game, listGame);
}
//Get Lastest stream value
String get name => _nameController.value;
String get description => _descriptionController.value;
dispose() {
_descriptionController?.close();
_allMyListGamesByNameController?.close();
_allDataGames?.close();
_nameController?.close();
}
}
The provider:
class Provider extends InheritedWidget {
static Provider _imstance;
final _gameBloc = GameBloc();
factory Provider({Key key, Widget child}) {
if (_imstance == null) {
_imstance = new Provider._internal(key: key, child: child);
}
return _imstance;
}
Provider._internal({Key key, Widget child}) : super(key: key, child: child);
static GameBloc gameBloc(BuildContext context) {
return (context.inheritFromWidgetOfExactType(Provider) as Provider)
._gameBloc;
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
The error is:
════════ Exception caught by gesture ═══════════════════════════════════════════
Bad state: Cannot add new events after calling close
When I evaluate formKey.currentState.save(); I got:
formKey.currentState.save()
Unhandled exception:
Bad state: Cannot add new events after calling close
#0 _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:249:24)
#1 Subject._add (package:rxdart/src/subjects/subject.dart:141:17)
#2 Subject.add (package:rxdart/src/subjects/subject.dart:135:5)
#3 _StreamSinkWrapper.add (package:rxdart/src/subjects/subject.dart:167:13)
I was reading about this error, it mention the error is on Bloc singleston scope or dispose method.
What is happen?
When you navigate to home with Navigator.pushReplacementNamed(context, "home"), the _DetailGamePage<State> is being disposed, calling gameBloc?.dispose. This leaves _gameBloc instantiated with all streams closed.
As you are using a Singleton Provider, when you navigate back to DetailGamePage, your save is trying to write to the closed streams.
What you need to do is move the closure of the streams farther up the widget tree so as not to close them before you are done with them, perhaps at the app level OR re-instantiate _gameBloc if the streams are closed, loading the data from the repo again.