Related
Right now I'm trying to make a dialog where I can change the role for the users that has been logged in on my app, for that i have a ListView with Cards that shows up the information of the user and a DropDownButton to select the role that a can assign to that user.
But when i select a role or item it change on all the user's Card.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
final user = FirebaseAuth.instance.currentUser!;
String role = '1';
Stream<QuerySnapshot> readUsers() =>
FirebaseFirestore.instance.collection('users').snapshots();
Future accDialog(BuildContext context) => showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (BuildContext context, setState) => Dialog(
child: Container(
margin: const EdgeInsets.all(24),
child: StreamBuilder(
stream: readUsers(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
roleFunction(String q) {
setState(() {
role = q;
});
}
if (snapshot.hasError) {
return const Text('Algo ha salido mal!');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Cargando");
}
return ListView(
shrinkWrap: true,
children: snapshot.data!.docs
.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Card(
margin: const EdgeInsets.all(8),
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
data['name'],
style:
Theme.of(context).textTheme.titleMedium,
),
Text('Correo: ${data['email']}'),
Text('Rol Actual: ${data['role']}'),
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: RoleMenu(
roleFunction: roleFunction)),
const Spacer(),
ElevatedButton(
onPressed: () {
final setRole = <String, String>{
"role": role,
};
FirebaseFirestore.instance
.collection('users')
.doc(document.id)
.set(setRole,
SetOptions(merge: true));
},
child: const Text('Guardar'))
],
)
],
),
),
);
})
.toList()
.cast(),
);
},
),
),
),
),
);
class RoleMenu extends StatefulWidget {
final Function roleFunction;
const RoleMenu({super.key, required this.roleFunction});
#override
State<RoleMenu> createState() => _RoleMenuState();
}
class _RoleMenuState extends State<RoleMenu> {
String selected = '';
#override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton<String>(
enableFeedback: true,
dropdownColor: ElevationOverlay.applySurfaceTint(
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surfaceTint,
2),
iconEnabledColor: Theme.of(context).colorScheme.onSurfaceVariant,
style: Theme.of(context).textTheme.labelLarge,
items: const [
DropdownMenuItem<String>(
value: 'User',
child: Text('Usuario'),
),
DropdownMenuItem<String>(
value: 'Admin',
child: Text('Admin'),
),
],
value: selected,
onChanged: (value) {
setState(() {
selected = value!;
widget.roleFunction(selected);
});
},
),
);
}
}
enter image description here I get Unexpected Null value error when switching from Home page to Vehicle Add page. I couldn't understand what caused this.
I tried to run it in debug mode, but it was very confusing in debug mode, I couldn't understand it.
home.dart page
import 'package:Car_Price_App/Pages/category_add.dart';
import 'package:Car_Price_App/Pages/vehicle_add.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import '../services/auth_service.dart';
class Home extends StatefulWidget {
const Home({super.key});
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
final user = FirebaseAuth.instance.currentUser;
final email = user?.email;
return Scaffold(
appBar: AppBar(
title: Center(child: Text("Sıfır Araçlar ${email?.toUpperCase()}")),
leading: const Icon(Icons.car_rental),
actions: [
IconButton(onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context)=> CategoryAdd(gelenEmail: email.toString(),)));
print("giden $email");
}, icon: const Icon(Icons.category)),
IconButton(onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context)=> VehicleAdd()));
}, icon: const Icon(Icons.add)),
IconButton(onPressed: () {}, icon: const Icon(Icons.update)),
IconButton(onPressed: () {}, icon: const Icon(Icons.delete)),
],
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Giriş Yapıldı $email"),
const SizedBox(height: 10,),
ElevatedButton(onPressed: (){
AuthService().signOut();
}, child: const Text("Çıkış Yap",),),
],
),
),
);
}
}
vehicle_add.dart page
import 'dart:io';
import 'dart:math';
import 'dart:core';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:currency_text_input_formatter/currency_text_input_formatter.dart';
import 'package:file_picker/file_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/trade_mark_model.dart';
import '../models/type_of_vehicle_model.dart';
import '../models/vehicle_body_type_model.dart';
import '../models/vehicle_model.dart';
import '../models/vehicle_year_model.dart';
class VehicleAdd extends StatefulWidget {
const VehicleAdd({Key? key}) : super(key: key);
#override
State<VehicleAdd> createState() => _VehicleAddState();
}
class _VehicleAddState extends State<VehicleAdd> {
GlobalKey? aracEkleKey = GlobalKey<FormState>();
var db = FirebaseFirestore.instance;
TextEditingController vehicleProperties = TextEditingController();
TextEditingController vehiclePrice = TextEditingController();
String? vasitaTipi = "";
String? kasaTipi = "";
String? marka = "";
String? model = "";
String? yil = "";
PlatformFile? pickedFile;
UploadTask? uploadTask;
Future selectFile() async {
final result = await FilePicker.platform.pickFiles();
if (result == null) return;
setState(() {
pickedFile = result.files.first;
});
}
Future uploadFile() async {
final path = 'aracResimleri/${pickedFile?.name}';
final file = File(pickedFile!.path!);
final ref = FirebaseStorage.instance.ref().child(path);
setState(() {
uploadTask = ref.putFile(file);
});
final snapshot = await uploadTask?.whenComplete(() {});
final urlDownload = await snapshot?.ref.getDownloadURL();
print('Download Link : $urlDownload');
setState(() {
uploadTask = null;
});
}
Stream<List<TypeOfVehicleModel>> readVehicleType() => db.collection("Vasıtalar").snapshots().map((snapshot) => snapshot.docs.map((doc) => TypeOfVehicleModel.fromJson(doc.data())).toList());
Stream<List<VehicleBodyTypeModel>> readBodyType() => db.collection("KasaTipleri").snapshots().map((snapshot) => snapshot.docs.map((doc) => VehicleBodyTypeModel.fromJson(doc.data())).toList());
Stream<List<TradeMarkModel>> readTradeMark() => db.collection("Markalar").snapshots().map((snapshot) => snapshot.docs.map((doc) => TradeMarkModel.fromJson(doc.data())).toList());
Stream<List<VehicleModel>> readVehicleModel() => db.collection("Modeller").snapshots().map((snapshot) => snapshot.docs.map((doc) => VehicleModel.fromJson(doc.data())).toList());
Stream<List<VehicleYearModel>> readVehicleYear() => db.collection("Yıllar").snapshots().map((snapshot) => snapshot.docs.map((doc) => VehicleYearModel.fromJson(doc.data())).toList());
late int _key;
_collapse() {
int? newKey;
do {
_key = Random().nextInt(10000);
} while (newKey == _key);
}
#override
void initState() {
super.initState();
_collapse();
}
#override
Widget build(BuildContext context) {
var screenInfo = MediaQuery.of(context);
final double screenWidth = screenInfo.size.width;
final double screenHeight = screenInfo.size.height;
return Scaffold(
appBar: AppBar(
title: const Text("Araç Ekle"),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
child: Center(
child: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: aracEkleKey,
child: SizedBox(
width: screenWidth / 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FutureBuilder<List<TypeOfVehicleModel>>(
future: readVehicleType().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final typeOfVehicle = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.merge_type),
title: const Text("Vasıta Seçiniz"),
children: typeOfVehicle.map(buildTov).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), // Vasıta Tipi
Text("$vasitaTipi"),
Text("$kasaTipi"),
Text("$marka"),
Text("$model"),
Text("$yil"),
FutureBuilder<List<VehicleBodyTypeModel>>(
future: readBodyType().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final bodyType = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.type_specimen),
title: const Text("Kasa Tipi Seçiniz"),
children: bodyType.map(buildBodyType).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), // Kasatipi
FutureBuilder<List<TradeMarkModel>>(
future: readTradeMark().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final tradeMarka = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.branding_watermark),
title: const Text("Marka Seçiniz"),
children: tradeMarka.map(buildTradeMark).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), //Markalar
FutureBuilder<List<VehicleModel>>(
future: readVehicleModel().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final model = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.time_to_leave),
title: const Text("Model Seçiniz"),
children: model.map(buildModel).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), //Modeller
FutureBuilder<List<VehicleYearModel>>(
future: readVehicleYear().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final year = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.calendar_month),
title: const Text(
"Yıl Seçiniz",
),
children: year.map(buildYear).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), //Yıllar
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: vehicleProperties,
decoration: const InputDecoration(
labelText: "Araç Özellikleri Giriniz",
suffixText: "Araç Özellikleri Giriniz",
border: OutlineInputBorder(),
),
minLines: 1,
maxLines: screenHeight.toInt(),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
// for below version 2 use this
FilteringTextInputFormatter.allow(RegExp(
r'\d',
)),
FilteringTextInputFormatter.digitsOnly,
CurrencyTextInputFormatter(
symbol: '₺',
name: "TL",
)
],
controller: vehiclePrice,
decoration: const InputDecoration(
labelText: "Araç Fiyat Giriniz",
suffixText: "Araç Fiyat Giriniz",
border: OutlineInputBorder(),
),
minLines: 1,
maxLines: screenHeight.toInt(),
),
),
Expanded(
child: Image.file(
File(pickedFile!.path!),
width: double.infinity,
fit: BoxFit.cover,
),
),
ElevatedButton.icon(
icon: const Icon(Icons.image),
onPressed: () {
selectFile();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Araç Resmi Seç"),
content: Text(pickedFile!.name),
actions: [
TextButton(
onPressed: () async {
uploadFile();
},
child: const Center(
child: Text(
"Galeri'den Seç",
style: TextStyle(color: Colors.black, fontSize: 20),
))),
],
);
});
},
label: const Text("Araç Resimi Ekle"),
),
const SizedBox(
height: 30,
),
StreamBuilder<TaskSnapshot>(
stream: uploadTask!.snapshotEvents,
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
double progress = data.bytesTransferred / data.totalBytes;
return SizedBox(
height: 50,
child: Stack(
fit: StackFit.expand,
children: [
LinearProgressIndicator(
value: progress,
color: Colors.green,
backgroundColor: Colors.grey,
),
Center(
child: Text(
'${(100 * progress).roundToDouble()} %',
style: const TextStyle(color: Colors.white),
),
)
],
),
);
} else {
return const SizedBox(
height: 50,
);
}
},
)
],
),
),
),
),
),
),
// Fab Buton visible olacak doldurulması geren herşey dolduğunda gözükecek
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// bool kontrolSonucu = aracEkleKey.currentState!.validate();
},
),
);
}
Widget buildTov(TypeOfVehicleModel typeOfVehicleModel) => ListTile(
onTap: () {
setState(() {
_collapse();
vasitaTipi = typeOfVehicleModel.typeOfVehicle;
});
if (kDebugMode) {
print("$vasitaTipi");
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(typeOfVehicleModel.typeOfVehicle.toString()),
); //Araç vasıta tipi
Widget buildBodyType(VehicleBodyTypeModel vehicleBodyTypeModel) => ListTile(
onTap: () {
setState(() {
_collapse();
kasaTipi = vehicleBodyTypeModel.vehicleBodyType;
});
if (kDebugMode) {
print(vehicleBodyTypeModel.vehicleBodyType);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(vehicleBodyTypeModel.vehicleBodyType.toString()),
); // Araç kasa tipi
Widget buildTradeMark(TradeMarkModel tradeMarkModel) => ListTile(
onTap: () {
setState(() {
_collapse();
marka = tradeMarkModel.tradeMarkName;
});
if (kDebugMode) {
print(tradeMarkModel.tradeMarkName);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(tradeMarkModel.tradeMarkName.toString()),
); // Araç MArka
Widget buildModel(VehicleModel vehicleModel) => ListTile(
onTap: () {
setState(() {
_collapse();
model = vehicleModel.modelName;
});
if (kDebugMode) {
print(vehicleModel.modelName);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(vehicleModel.modelName.toString()),
); // Araç Modeli
Widget buildYear(VehicleYearModel vehicleYearModel) => ListTile(
onTap: () {
setState(() {
_collapse();
yil = vehicleYearModel.year;
});
if (kDebugMode) {
print(vehicleYearModel.year);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(vehicleYearModel.year.toString()),
); // Araç Yılı
Widget buildProgress() => StreamBuilder<TaskSnapshot>(
stream: uploadTask?.snapshotEvents,
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
double progress = data.bytesTransferred / data.totalBytes;
return SizedBox(
height: 50,
child: Stack(
fit: StackFit.expand,
children: [
LinearProgressIndicator(
value: progress,
color: Colors.green,
backgroundColor: Colors.grey,
),
Center(
child: Text(
'${(100 * progress).roundToDouble()} %',
style: const TextStyle(color: Colors.white),
),
)
],
),
);
} else {
return const SizedBox(
height: 50,
);
}
},
);
}
I don't know which variable is causing the problem. I'm inexperienced in flutter.
The error message says on which line the error is. See
That means it's on line 297. If I'm not mistaken that is the line:
child: Image.file(File(pickedFile!.path!),
So pickedFile is null
What im trying to do is showing a list of assignments and have buttons at the bottom to let the user to select which function they want but when the data becomes a lot and my buttons got push below the screen, i was hoping for the buttons to stay on the screen no matter how many data is generated, buttons will not get push down and the button does not block the visuals of the data, i got an image below to show that
example at here
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fyp/assignment/ViewComplete.dart';
import 'ReassignEmployee.dart';
import 'ViewAssigned.dart';
import 'ViewUnassigned.dart';
class assignmentPage extends StatefulWidget {
const assignmentPage({Key? key}) : super(key: key);
#override
State<assignmentPage> createState() => _assignmentPageState();
}
class _assignmentPageState extends State<assignmentPage> {
TextEditingController searchController = TextEditingController();
String searchText = '';
String id = '';
CollectionReference allNoteCollection =
FirebaseFirestore.instance.collection('Assignment');
List<DocumentSnapshot> documents = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('View Assignment'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
controller: searchController,
onChanged: (value) {
setState(() {
searchText = value;
});
},
decoration: InputDecoration(
hintText: 'Search...',
prefixIcon: Icon(Icons.search),
),
),
StreamBuilder(
stream: allNoteCollection.snapshots(),
builder: (ctx, streamSnapshot) {
if (streamSnapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator());
}
documents = streamSnapshot.data!.docs;
if (searchText.length > 0) {
documents = documents.where((element)
{
return
( element.get('carPlate').toString().
toLowerCase().contains(searchText.toLowerCase()) ||
element.get('custName').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('date').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('employee').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('id').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('payment').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('serviceName').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('status').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('timeEnd').toString().
toLowerCase().contains(searchText.toLowerCase())||
element.get('timeStart').toString().
toLowerCase().contains(searchText.toLowerCase())
);
}).toList();
}
return ListView.separated(
reverse: true,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: documents.length,
separatorBuilder: (BuildContext context, int index) {
return Divider();
},
itemBuilder: (BuildContext context, int index) {
id = documents[index]['id'];
return ListTile(
contentPadding:
EdgeInsets.symmetric(horizontal: 0.0),
onTap: () {
},
title: Column(
children: <Widget>[
Text(documents[index]['carPlate']),
Text(documents[index]['custName']),
Text(documents[index]['date']),
Text(documents[index]['employee']),
Text(documents[index]['payment']),
Text(documents[index]['serviceName']),
Text(documents[index]['status']),
Text(documents[index]['timeStart']),
Text(documents[index]['timeEnd']),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: (){
Navigator.push(context,
MaterialPageRoute(builder: (context) => ReassignEmployee(
serviceName: documents[index]['serviceName'],
carPlate: documents[index]['carPlate'],
custName: documents[index]['custName'],
date: documents[index]['date'],
timeStart: documents[index]['timeStart'],
timeEnd: documents[index]['timeEnd'],
payment: documents[index]['payment'],
status: documents[index]['status'],
employee: documents[index]['employee'],
id: documents[index]['id'],
)));
},child: Icon(Icons.edit),
),
],
)
);
},
);
},
),
ElevatedButton(
onPressed: () async {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ViewUnassigned(id: id.toString(),)));
},
child: Text('Show unassigned'),
),
ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ViewAssigned()));
},
child: Text('Show assigned'),
),
],
),
),
);
}
}
I have no idea how to do it, hope someone can guide me, really thanks a lot
You can use Stack and Positioned to pin buttons to the bottom of screen, like this:
class assignmentPage extends StatefulWidget {
const assignmentPage({Key? key}) : super(key: key);
#override
State<assignmentPage> createState() => _assignmentPageState();
}
class _assignmentPageState extends State<assignmentPage> {
TextEditingController searchController = TextEditingController();
String searchText = '';
String id = '';
CollectionReference allNoteCollection =
FirebaseFirestore.instance.collection('Assignment');
List<DocumentSnapshot> documents = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('View Assignment'),
),
body: Stack(
children: [
SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
controller: searchController,
onChanged: (value) {
setState(() {
searchText = value;
});
},
decoration: InputDecoration(
hintText: 'Search...',
prefixIcon: Icon(Icons.search),
),
),
StreamBuilder(
stream: allNoteCollection.snapshots(),
builder: (ctx, streamSnapshot) {
if (streamSnapshot.connectionState ==
ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
documents = streamSnapshot.data!.docs;
if (searchText.length > 0) {
documents = documents.where((element) {
return (element
.get('carPlate')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('custName')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('date')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('employee')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('id')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('payment')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('serviceName')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('status')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('timeEnd')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()) ||
element
.get('timeStart')
.toString()
.toLowerCase()
.contains(searchText.toLowerCase()));
}).toList();
}
return ListView.separated(
reverse: true,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: documents.length,
separatorBuilder: (BuildContext context, int index) {
return Divider();
},
itemBuilder: (BuildContext context, int index) {
id = documents[index]['id'];
return ListTile(
contentPadding:
EdgeInsets.symmetric(horizontal: 0.0),
onTap: () {},
title: Column(
children: <Widget>[
Text(documents[index]['carPlate']),
Text(documents[index]['custName']),
Text(documents[index]['date']),
Text(documents[index]['employee']),
Text(documents[index]['payment']),
Text(documents[index]['serviceName']),
Text(documents[index]['status']),
Text(documents[index]['timeStart']),
Text(documents[index]['timeEnd']),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ReassignEmployee(
serviceName: documents[index]
['serviceName'],
carPlate: documents[index]
['carPlate'],
custName: documents[index]
['custName'],
date: documents[index]
['date'],
timeStart: documents[index]
['timeStart'],
timeEnd: documents[index]
['timeEnd'],
payment: documents[index]
['payment'],
status: documents[index]
['status'],
employee: documents[index]
['employee'],
id: documents[index]['id'],
)));
},
child: Icon(Icons.edit),
),
],
));
},
);
},
),
],
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Column(
children: [
ElevatedButton(
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ViewUnassigned(
id: id.toString(),
)));
},
child: Text('Show unassigned'),
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ViewAssigned()));
},
child: Text('Show assigned'),
),
],
))
],
),
);
}
}
I am trying to render a search form above a list of items from Firestore and filter locally based on what is typed in the form.
I tried adding both widgets to the body like this, but it is only displaying the search form:
body: Column(
children: <Widget>[Searchform(), ContentWidget()],
),
This is the current code which displays a basic list:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class Items extends StatefulWidget {
Items({Key key}) : super(key: key);
#override
_ItemsState createState() => _ItemsState();
}
class _ItemsState extends State<Items> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Search'),
),
body: ContentWidget(),
);
}
}
class Searchform extends StatelessWidget {
final TextEditingController _searchController = TextEditingController();
#override
Widget build(BuildContext context) {
return TextField(
controller: _searchController,
decoration: InputDecoration(
labelText: "Search",
hintText: "Search",
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
),
),
);
}
}
class ContentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('content').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['term']),
);
}).toList(),
);
}
},
);
}
}
What I was thinking of doing is saving the items in local state and filter them based on what is typed in the search box.
this is a very simple way try this code within "snapshot.data.documents.map((DocumentSnapshot document)"
if(_searchController.text.toString().contains(document['term'])){
return new ListTile(
title: new Text(document['term']),
);
}
I have provide simple filter record in listview code.
class FilterDemo extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return FilterState();
}
}
class FilterState extends State<FilterDemo> {
List<String> items, duplicateList;
TextEditingController editingController = TextEditingController();
#override
void initState() {
// TODO: implement initState
super.initState();
items = List<String>.generate(1000, (i) => "Item $i");
duplicateList = items;
}
void filterSearchResults(String query) {
List<String> dummySearchList = List<String>();
dummySearchList.addAll(duplicateList);
if (query.isNotEmpty) {
List<String> dummyListData = List<String>();
dummySearchList.forEach((item) {
if (item.contains(query)) {
dummyListData.add(item);
}
});
setState(() {
items.clear();
items.addAll(dummyListData);
});
return;
} else {
setState(() {
items.clear();
items.addAll(duplicateList);
});
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("Filter Demo"),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
onChanged: (value) {
filterSearchResults(value);
},
controller: editingController,
decoration: InputDecoration(
labelText: "Search",
hintText: "Search",
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(25.0)))),
),
),
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index]}'),
);
},
),
),
],
),
);
}
}
I have provide Code how the saving the items in local state and filter them based on what is typed in the search box.
class UserList extends StatefulWidget {
final FirebaseUser user;
final String currentUserId;
UserList({this.currentUserId, this.user});
#override
_UserListState createState() => _UserListState();
}
class _UserListState extends State<UserList> {
TextEditingController _signUpConfirmPassword = new TextEditingController();
String _myValue = '';
UniqueKey _myKey = UniqueKey();
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("UserList"),
),
child: ListView(
shrinkWrap: true,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
child: CupertinoTextField(
keyboardType: TextInputType.text,
//inputFormatters: [LengthLimitingTextInputFormatter(60)],
placeholder: 'Search For..',
// placeholderStyle: TextStyle(
// fontWeight: FontWeight.w200
// ),
prefix: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Icon(
Icons.search,
),
),
onChanged: (val) {
if (val.isNotEmpty) {
_myValue = val;
}
setState(() {
_myKey = UniqueKey();
});
},
decoration: BoxDecoration(
border: Border.all(color: primaryColor),
borderRadius: BorderRadius.circular(20.0)),
)),
SizedBox(height: 10.0),
Container(
key: _myKey,
child: FetchUsers(
user: widget.user,
myValue: _myValue,
)),
],
));
}
}
class FetchUsers extends StatefulWidget {
final String myValue;
final FirebaseUser user;
FetchUsers({this.myValue, this.user});
#override
_FetchUsersState createState() => _FetchUsersState();
}
class _FetchUsersState extends State<FetchUsers> {
List searchName = List();
List userName = List();
Future listOfUsers() {
if (widget.myValue.isEmpty) {
return Firestore.instance
.collection('users')
.where('Role', isEqualTo: 'user')
.orderBy('Created', descending: true)
.limit(10)
.getDocuments()
.then((d) {
userName.clear();
d.documents.forEach((f) {
userName.add(f);
});
return userName;
});
} else {
return Firestore.instance
.collection('users')
.where('Role', isEqualTo: 'user')
.limit(10)
.getDocuments()
.then((d) {
searchName.clear();
d.documents.forEach((f) {
if (f.data['Name']
.toString()
.toLowerCase()
.contains(widget.myValue.toLowerCase())) {
searchName.add(f);
}
});
return searchName;
});
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: listOfUsers(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CupertinoActivityIndicator(),
);
} else {
return ListView.separated(
physics: ClampingScrollPhysics(),
separatorBuilder: (context, int) {
return Divider();
},
itemCount: snapshot.data.length,
shrinkWrap: true,
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) {
return Card(
elevation: 7.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
' ${snapshot.data[index]['Name']}',
style: TextStyle(
color: outlineColor,
fontWeight: FontWeight.bold),
),
SizedBox(
height: 5.0,
),
Text(
' ${snapshot.data[index]['Email']}',
),
],
),
Spacer(),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
RaisedButton.icon(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20.0)),
color: primaryColor,
onPressed: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => Chat(
user: widget.user,
name: snapshot.data[index]
['Name'],
peerId: snapshot.data[index]
['UID'],
)));
},
icon: Icon(
Icons.chat,
color: themeColor,
),
label: Text(
"Chat",
style: TextStyle(color: themeColor),
)),
RaisedButton.icon(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20.0)),
color: primaryColor,
onPressed: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) =>
SendNotificationOption(
name: snapshot.data[index]
['Name'],
myFcm: snapshot.data[index]
['UID'],
isBroadcast: false,
)));
},
icon: Icon(
Icons.notifications,
color: themeColor,
),
label: Text(
"Notification",
style: TextStyle(color: themeColor),
)),
],
),
],
),
),
));
},
);
}
},
);
}
}
What you have type in Search then that Data is shown in listview]1
I have a login screen and I'm using BloC pattern, but when i click on button the validation fails, the message from error is called many times, because the stream builder snapshot.error has a value, i don't know how change this to show error only when the user click at the button and validation in fact throw an error.
class LoginPage extends StatefulWidget {
static String tag = 'login-page';
#override
State<StatefulWidget> createState() => LoginState();
}
class LoginState extends State<LoginPage> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
LoginBloc loginBloc = BlocProvider.of(context).loginBloc;
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(16.0),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Colors.blueAccent,
Colors.blue,
]),
),
child: Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
elevation: 4.0,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.0, right: 16.0),
children: <Widget>[
/*_logo(),*/
SizedBox(height: 24.0),
_emailField(loginBloc),
SizedBox(height: 8.0),
_passwordField(loginBloc),
SizedBox(height: 24.0),
_loginButtonSubmit(loginBloc),
_loading(loginBloc),
_error(loginBloc),
_success(loginBloc),
_settingsText()
],
),
),
)),
);
}
Widget _logo() {
return Hero(
tag: 'hero',
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: Center(
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: AssetImage('assets/4.0x/ic_launcher.png'),
),
borderRadius: BorderRadius.all(Radius.circular(50.0)),
),
),
),
),
);
}
Widget _emailField(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
//Anytime the builder sees new data in the emailStream, it will re-render the TextField widget
return TextField(
onChanged: loginBloc.setEmail,
keyboardType: TextInputType.emailAddress,
controller: _usernameController,
decoration: InputDecoration(
labelText: 'Usuário',
errorText: snapshot
.error, //retrieve the error message from the stream and display it
),
);
},
);
}
Widget _passwordField(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: loginBloc.setPassword,
obscureText: true,
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Senha',
errorText: snapshot.error,
),
);
},
);
}
Widget _loginButtonSubmit(LoginBloc loginBloc) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
onPressed: () {
loginBloc.submit(
LoginRequest(_usernameController.text, _passwordController.text));
},
padding: EdgeInsets.all(12),
color: Colors.blue,
child: Text('Entrar', style: TextStyle(color: Colors.white)),
),
);
}
Widget _loading(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.loadingStream,
initialData: false,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Center(
child: snapshot.data
? Padding(
padding: const EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
)
: null,
);
});
}
Widget _error(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasError) {
_onWidgetDidBuild(() {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${snapshot.error}'),
backgroundColor: Colors.red,
));
});
}
return Container();
});
}
Widget _success(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
initialData: null,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData && snapshot.data.erro == 0) {
_onWidgetDidBuild(() {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => HomePage()));
});
}
return Container();
});
}
Widget _settingsText() {
return Center(
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => LoginSettingsPage()));
},
child: Padding(
padding: EdgeInsets.fromLTRB(16.0, 0, 16.0, 16.0),
child: Text(
"Configurações",
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
),
),
),
);
}
void _onWidgetDidBuild(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
}
Bloc
class LoginBloc with Validator {
//RxDart's implementation of StreamController. Broadcast stream by default
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
final _loadingController = BehaviorSubject<bool>();
final _successController = BehaviorSubject<LoginResponse>();
final _submitController = PublishSubject<LoginRequest>();
//Return the transformed stream
Stream<String> get emailStream => _emailController.stream.transform(performEmptyEmailValidation);
Stream<String> get passwordStream => _passwordController.stream.transform(performEmptyPasswordValidation);
Stream<bool> get loadingStream => _loadingController.stream;
Stream<LoginResponse> get successStream => _successController.stream;
//Add data to the stream
Function(String) get setEmail => _emailController.sink.add;
Function(String) get setPassword => _passwordController.sink.add;
Function(LoginRequest) get submit => _submitController.sink.add;
LoginBloc() {
_submitController.stream.distinct().listen((request) {
_login(request.username, request.password);
});
}
_login(String useName, String password) async {
_loadingController.add(true);
ApiService.login(useName, password).then((response) {
if (response.erro == 0) {
saveResponse(response);
} else {
final error = Utf8Codec().decode(base64.decode(response.mensagem));
_successController.addError(error);
print(error);
}
_loadingController.add(false);
}).catchError((error) {
print(error);
_loadingController.add(false);
_successController.addError("Falha ao realizar login!");
});
}
saveResponse(LoginResponse response) {
SharedPreferences.getInstance().then((preferences) async {
var urlSaved = await preferences.setString(
Constants.LOGIN_RESPONSE, response.toJson().toString());
if (urlSaved) {
_successController.add(response);
}
}).catchError((error) {
_successController.addError(error);
});
}
dispose() {
_emailController.close();
_passwordController.close();
_loadingController.close();
_successController.close();
_submitController.close();
}
}
I found the solution to my error when in click in InputField and focus change, the StreamBuilder rebuild de widget e show again the error every time.
I just put a validation before start shows the error to consider the state of the snapshot.
Widget _error(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.active &&
snapshot.hasError) {
_onWidgetDidBuild(() {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${snapshot.error}'),
backgroundColor: Colors.red,
));
});
}
return Container();
});
}
If is active is because I throw an error in my Bloc class, if not, is because the stream builder was rebuilt the widget. This solves my problem.
I don't know if it is the better solution but solves my problem at the moment.
I had same problem, adding initialdata null works fine for me.
return new StreamBuilder(
stream: _bloc.stream,
initialData: null,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
According Flutter Docs hasError validate null value.