I updated my Flutter app to Flutter 2, and now when I try to get the snapshot.error in my StreamBuider I get this
These are validators with Streams.
class LoginStreams with Validators {
dispose() {
_emailController.close();
_passwordController.close();
}
Function(String) get emailOnChange => _emailController.sink.add;
Function(String) get passwordOnChange => _passwordController.sink.add;
final _emailController = StreamController<String>.broadcast();
final _passwordController = StreamController<String>.broadcast();
Stream<String> get emailStream =>
_emailController.stream.transform(emailValidator);
Stream<String> get passwordStream =>
_passwordController.stream.transform(passwordValidator);
}
--
class Validators {
final passwordValidator = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
password.length >= 5
? sink.add(password)
: sink.addError("La contraseña debe contener más de 5 caracteres");
});
final emailValidator =
StreamTransformer<String, String>.fromHandlers(handleData: (email, sink) {
if (email.contains('#') && email.contains('.')) {
sink.add(email);
} else {
sink.addError("Ingrese un email válido");
}
});
}
StreamBuilder(
stream: _validators.emailStream,
builder: (BuildContext context, AsyncSnapshot snapshot)=>TextField(
keyboardType: TextInputType.emailAddress,
controller: emailController,
decoration: InputDecoration(
errorText: '$snapshot.error',
labelText: widget.loginModel.emailTextfield,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(CmbSpacing.SpaceMD))),
onChanged: _validators.emailOnChange,
),
),
SizedBox(height: CmbSpacing.SpaceSM),
StreamBuilder(
stream: _validators.passwordStream,
builder: (BuildContext context, AsyncSnapshot snapshot)=>TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
errorText: '$snapshot.error',
suffixIcon: Icon(Icons.visibility_off_outlined),
labelText: widget.loginModel.passwordTextfield,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(CmbSpacing.SpaceMD))),
onChanged: _validators.passwordOnChange,
),
),
If someone could help me i would be so gratefull, I am so confused because after upgrading to Flutter 2 this used to work great.
It's something new about StreamBuilders in Flutter 2?
EDIT:
I changed "$snapshot.error" to snapshot.error.toString()
and now I get null.
You're not interpolating the error correctly.
If you want to call an extra method before interpolating, you have to wrap the expression in ${}, e.g. '${snapshot.error}. In your case you're just appending .error to a string representation of snapshot
Replace
errorText: '$snapshot.error'
With
errorText: snapshot.error?.toString()
// or if you want to use it in a sentence
errorText: "This is the error: ${snapshot.error}"
You have to apply condition statement overthere:
errorText: snapshot.hasError ? '${snapshot.error}' : null
I'm sure this will help. :)
Related
I'm new in Flutter and have been trying to find a solution for this, but is there a way I can just accept the username ignoring if it's lowercase or uppercase? As long as it matches with their username it's fine to proceed.
Widget emailField() {
return TextFormField(
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
focusNode: emailNode,
validator: validateEmpty,
decoration: InputDecoration(
hintText: 'Username',
prefixIcon: const Icon(Icons.account_circle),
labelStyle: Label,
fillColor: Colors.transparent,
),
//validator: validateEmail,
onSaved: (String? value) {
email = value as String;
},
);
}
submit() async {
if (formKey1.currentState!.validate()) {
formKey1.currentState!.save();
if (_prefs.userSession!.username == email) {
EasyLoading.instance.userInteractions = false;
EasyLoading.show(status: 'Deleting account...');
final response = await authService.masterDelete();
if (_prefs.userSession!.username != email) {
AwesomeDialog(
context: context,
dialogType: DialogType.ERROR,
title: 'ERROR',
desc: 'Incorrect username',
btnCancelText: 'Accept',
btnCancelOnPress: () {},
).show();
}
If it's only about ignoring lowercase/uppercase, then use toLowerCase() on both strings and compare them:
String mailOne = "TeSt123#gmail.com";
String mailTwo = "test123#gMAIL.com";
print(mailOne.toLowerCase() == mailOne.toLowerCase()); // -> true
I am very new to flutter. I am trying to create List view with Dynamically generated DropDownButton & And Label .No matter what I do this error occurs and dropdown items not updating.
Expected a value of type 'List<DropdownMenuItem<String>>', but got one of type 'List<dynamic>'
This is my listview builder code
ListView.builder(
itemCount: tasksLength,
itemBuilder: (context, index) {
String roleId = taskRoles[index]['roleId'];
List<DropdownMenuItem<String>> _userList = [DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
if (usersList['roles'] != null && usersList['roles'][roleId] != null) {
_userList = usersList['roles'][roleId]['users'].map((item) {
return DropdownMenuItem<String>(value: item['id'],child: Text(item['name'].toString()));
}).toList();
}
return UserSelect(userList: _userList);
},
),
This is my widget class with the DropDownbutton
class UserSelect extends StatefulWidget {
List<DropdownMenuItem<String>>? userList =
[DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
UserSelect({this.userList});
#override
_UserSelectState createState() => _UserSelectState();
}
class _UserSelectState extends State<UserSelect> {
String _selected_user = '';
String _roleName = 'User Role';
List<DropdownMenuItem<String>> _userList =
[DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
#override
void initState() {
super.initState();
}
#override
void didUpdateWidget(UserSelect oldWidget) {
if (oldWidget.userList != widget.userList) {
_userList = widget.userList!;
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
_roleName,
style: TextStyle(
fontFamily: 'Bilo',
fontSize: 16,
color: const Color(0xff3b3e51),
letterSpacing: 0.224,
height: 1.5,
),
textHeightBehavior:
TextHeightBehavior(applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18.0),
color: const Color(0xfff6f6f6),
),
child: DropdownButton<String>(
isExpanded: true,
value: (_selected_user.length > 0) ? _selected_user : null,
icon: const Icon(Icons.keyboard_arrow_down),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.black45),
hint: new Text("Select User"),
underline: Container(
height: 2,
color: Colors.white24,
),
onChanged: (String? newValue) {
setState(() {
_selected_user = newValue!;
});
},
items: _userList),
)
],
);
}
}
Some of codes unnecessary I tried by best to skip this error that is why some junk codes are there.
Please help me to fix this issue or show me right direction.
I think the issue here is the .toList(); method calls here:
List<DropdownMenuItem<String>> _userList =
[DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
toList() in dart returns a list with the type that's supplied to its type parameter. Documentation
When not supplied with a type parameter, I would assume that the toList() method's return type is dynamic.
You can just remove the toList() method call altogether, as you already placed the DropDownMenuItem into a list by placing it between the [square brackets]! Ironically, the type would've been inferred by the list declaration before you overwrote it with toList() and made it dynamic :')
If you DO need to do it this way, you can simply add [items ...].toList<DropdownMenuItem>() which will correctly return a list of type DropdownMenuItem :)
In my application user can have multiple home and multiple rooms for each home. On top of my application I have dropdown box which im trying to set default value to selectedHome by user. Below that dropdown box I am showing the rooms in the home selected by user. In firebase I have rooms collection under each home. I'm getting the selected home data from firebase too. Also to show the rooms in selected home i need to query by home name. I have two FutureBuilder as you can see code below. One of them to get the selectedHome data from firebase and other for the getting the rooms in that home from firebase. As I said before to get the rooms in selected home I need to query by name of the home so I have a parameter which is the value of dropdownbox. In my code the problem is getting the rooms part is working before I get the selectedHome data from firebase and assign it to dropdown value. In this case I'm getting "Null check operator used on a null value".
Basicly the question is how can i assign value from future to variable before screen gets build.
Here you can see the code for getting selected home data from firebase;
Future<String> selectedHome() async {
return await database.selectedHome();
}
Future<String> selectedHome() async {
DocumentSnapshot docS =
await firestore.collection("users").doc(auth.currentUser()).get();
String selectedHome = (docS.data() as Map)["selectedHome"];
return selectedHome;
}
Here you can see the code for getting room data based on selectedHome from firebase;
Future<List<Map>> deviceAndRoomInfo() async {
return database.numberOfRooms(_dropdownValue!);
}
Future<List<Map>> numberOfRooms(String selectedHome) async {
List<Map> prodsList = [];
final snapshot = await firestore
.collection("users")
.doc(auth.currentUser())
.collection("homes")
.doc(selectedHome)
.collection("rooms")
.get();
List listOfRooms = snapshot.docs;
for (int a = 1; a <= listOfRooms.length; a++) {
var productsInRoom = await firestore
.collection("users")
.doc(auth.currentUser())
.collection("homes")
.doc(selectedHome)
.collection("rooms")
.doc(listOfRooms[a - 1]["roomName"])
.collection("products")
.get();
List prodList = productsInRoom.docs
.map((e) => DeviceModel.fromMap(e.data()))
.toList();
Map qq = {
"roomName": listOfRooms[a - 1]["roomName"],
"deviceInfo": prodList
};
prodsList.add(qq);
}
return prodsList;
}
Here you can see the code for screen contains 2 future builder that i told;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shelly_ess_production/constants.dart';
import 'package:shelly_ess_production/helper_widgets/loading_widget.dart';
import 'package:shelly_ess_production/screens/home_screen/components/circle_room_data.dart';
import 'package:shelly_ess_production/screens/home_screen/components/device_in_room_card.dart';
import 'package:shelly_ess_production/screens/home_screen/provider/home_screen_provider.dart';
import 'package:shelly_ess_production/screens/models/device_model.dart';
import 'package:shelly_ess_production/size_config.dart';
class Body extends StatefulWidget {
const Body({Key? key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
#override
Widget build(BuildContext context) {
var providerHelper =
Provider.of<HomeScreenProvider>(context, listen: false);
return SafeArea(
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(0.07)),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: getProportionateScreenHeight(0.02),
),
Consumer<HomeScreenProvider>(builder: (context, data, child) {
return FutureBuilder<List<String>>(
future: data.getHomesAndSelected(),
builder: (context, snapshot) {
if (snapshot.hasData) {
data.setDropDownValue = snapshot.data![0];
return DropdownButtonHideUnderline(
child: DropdownButton(
iconEnabledColor: kPrimaryColor,
iconDisabledColor: kPrimaryColor,
style: TextStyle(
color: kPrimaryColor,
fontSize: getProportionateScreenHeight(0.05)),
menuMaxHeight: getProportionateScreenHeight(0.4),
borderRadius: BorderRadius.circular(15),
key: UniqueKey(),
value: data.dropdownValue,
isExpanded: true,
icon: const Icon(Icons.arrow_downward),
onChanged: (String? newValue) async {
data.setDropDownValue = newValue;
await data.changeSelectedHome();
},
items: snapshot.data!
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
alignment: Alignment.center,
value: value,
child: Text(value),
);
}).toList(),
),
);
} else {
return Transform.scale(
scale: 0.5,
child: const Center(
child: CircularProgressIndicator(),
),
);
}
});
}),
SizedBox(
height: getProportionateScreenHeight(0.02),
),
SizedBox(
height: getProportionateScreenHeight(0.14),
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 5,
itemBuilder: (context, index) {
return CircleRoomData(
title: "Oda Sayısı",
icon: Icons.meeting_room,
content: "8",
);
}),
),
Consumer<HomeScreenProvider>(builder: (context, data, snapshot) {
return FutureBuilder<List<Map>>(
future: data.deviceAndRoomInfo(data.dropdownValue!),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: snapshot.data!.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Column(
children: [
Divider(
thickness:
getProportionateScreenHeight(0.002),
),
Text(
snapshot.data![index]["roomName"],
style: TextStyle(
fontWeight: FontWeight.bold,
color: kSecondaryColor,
fontSize:
getProportionateScreenHeight(0.03)),
),
SizedBox(
height: getProportionateScreenHeight(0.01),
),
Text(
"${(snapshot.data![index]["deviceInfo"] as List).length.toString()} Cihaz",
style:
const TextStyle(color: kSecondaryColor),
),
SizedBox(
height: getProportionateScreenHeight(0.02),
),
GridView.builder(
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemCount: (snapshot.data![index]
["deviceInfo"] as List)
.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, indexx) {
print(index);
return DeviceInRoom(
icon: Icons.light,
productName: ((snapshot.data![index]
["deviceInfo"]
as List)[indexx] as DeviceModel)
.deviceName,
);
})
],
);
});
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
});
}
)
],
)),
),
);
}
}
Am not certain where your error is coming from, but from what I see it maybe as a result of one of your functions returning null and a rendering of your content happens before the data is received.
You could try one of these:
You could declare the return type of your feature as being nullable for example you are expecting a value of type int:
Future<int?> xyz(){
......
return .....;
}
Now because your return type is nullable you wont have an issues as long as the receiving variable is also nullable.
Alternatively:
Future<int?> xyz(){
......
return ..... ?? 10 /*some default value*/;
}
because you know you result could be null you could also provide an optional default value incase your Future call returns a null value.
I'm trying to build an adaptative Date and Time Picker for Android and Ios,
DateTimeField(
controller: date,
focusNode: _focusNodeDate,
validator: (value){
if(value == null){
return 'Ce champ est requis';
};
return null;
},
decoration: InputDecoration(
labelText: 'Date de naissance',
labelStyle: TextStyle(
fontSize: getProportionateScreenWidth(20),
color: Colors.grey,
),
),
format: format,
onShowPicker: (context, currentValue) {
if(Platform.isAndroid){
return showDatePicker(
context: context,
firstDate: DateTime(1900),
initialDate: currentValue ?? DateTime.now(),
lastDate: DateTime(2100)
);
} else if(Platform.isIOS) {
return CupertinoDatePicker(onDateTimeChanged: (datetime){});
}
},
)
and I get this Error
error: The return type 'CupertinoDatePicker' isn't a
'Future', as required by the closure's context.
(return_of_invalid_type_from_closure at [app_isophro]
lib\addProfile\addprofile.dart:232)
You need to wrap your widget CupertinoDatePicker with something that shows it and returns value, e.g:
showModalBottomSheet<DateTime>(
context: context,
builder: (context) => CupertinoDatePicker(onDateTimeChanged: (datetime){}))
``
I created a map called "records", the keys of this map are taken from the user when presed on 'save' botton, and the values are from the time counter that I have in my code.
But the problem is when I create a ListView.builder to export this map indexes to cards, it gave me Null values in each card index !!!
How can I show the real value instead of Null ?!!
Here is my code:
var _item;
List listCount = [];
Map<String, dynamic> records = {};
String name;
createAlertDialog(buildContext, context) {
TextEditingController controller;
return showDialog(
context: context,
// barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text(
'Type record name',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.0),
),
content: TextField(
controller: controller,
onChanged: (value) {
name = value;
}),
actions: [
MaterialButton(
elevation: 5.0,
child: Text('Save'),
onPressed: () {
listCount.add(_item);
print(_item);
records[name] = _item;
print(records);
Navigator.pop(context);
},
),
MaterialButton(
elevation: 5.0,
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}
The variable _item is taking it's value from another site, see this:
StreamBuilder<int>(
stream: _stopWatchTimer2.rawTime,
initialData: 0,
builder: (context, snap) {
final value = snap.data;
final displayTime = StopWatchTimer.getDisplayTime(
value,
hours: _isHours2);
_item = displayTime;
return Padding(
padding: EdgeInsets.all(5.0),
child: Text(displayTime,
style: TextStyle(
fontSize: 30.0, color: Colors.white)),
);
},
),
And here where I create the ListView.builder:
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: records.length,
itemBuilder: (context, index) {
return MyCard(
colour: Colors.cyanAccent,
maker: Container(
width: 250.0,
height: 75.0,
child: Text(
'${records[index]}',
style: TextStyle(fontSize: 25.0),
textAlign: TextAlign.center,
),
),
);
},
),
The image in the link is a screen shot from my app.
Image
Looking at the _item variable which is initially null. You are not assigning any value to it. Please check your code. You are assigning a null value to your records because _item has not been given any value.
onChanged: (value){
_item = value;
}
you are not giving any value to the _item variable, I'm assuming you wanted to assign the value in the onChanged event like this:
onChanged: (value) {
_item = value;
}
or maybe you wanted to add the name to the list instead?
If I am understanding correctly, you have stored the item values into the records based on name variable in Save button onPressed event. But you are getting the record through the ListView index values. So, there is no record found in the records collection based on that index. So it returns null value.
Provide the index to store the item in the Save button instead of name. Or check the record based on name inside the ListView builder instead of index.