I am trying to create a login and register page for my app, I'm using the stateful widget on both screens.
after filling up the registration or login form and hiding the keyboard to press the register or login button, I'm getting the "string is empty" result, also on onPressed, I've tried to print my email and password in the console but I'm getting an empty field. But, if I try the same with my virtual keyboard still open on my virtual device, I'm able to print out the string, as far as I can understand the error is happening only when the keyboard is hidden.
this is my input field class
class RoundedInputField extends StatefulWidget {
final String hintText;
final ValueChanged<String> onChanged;
final Color color;
final bool boolean;
RoundedInputField({
Key key,
this.hintText,
this.onChanged,
this.color,
this.boolean = false,
}) : super(key: key);
#override
_RoundedInputFieldState createState() => _RoundedInputFieldState();
}
class _RoundedInputFieldState extends State<RoundedInputField> {
#override
Widget build(BuildContext context) {
return TextFieldContainer(
child: TextFormField(
onChanged: widget.onChanged,
obscureText: widget.boolean,
decoration: InputDecoration(
hintText: widget.hintText,
border: InputBorder.none,
),
),
);
}
}
class TextFieldContainer extends StatefulWidget {
final Widget child;
final Color color;
const TextFieldContainer({
Key key,
this.child,
this.color: Colors.white,
}) : super(key: key);
#override
_TextFieldContainerState createState() => _TextFieldContainerState();
}
class _TextFieldContainerState extends State<TextFieldContainer> {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
width: size.width * 0.8,
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(29),
),
child: widget.child,
);
}
}
and I call RoundedInputField
RoundedInputField(
hintText: "Email",
onChanged: (val) {
email = val;
},
this is my button for registering, currently im only printing the values
Container(
margin: EdgeInsets.symmetric(vertical: 10),
width: size.width * 0.8,
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: FlatButton(
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 40),
color: Colors.white,
onPressed: () async {
print(email);
print(password);
},
child: Text(
'login',
style: GoogleFonts.montserrat(
color: HexColor(studentPrimaryColour), fontSize: 20),
),
),
),
),
this is my login screen
class StudentLoginScreen extends StatefulWidget {
StudentLoginScreen();
#override
_StudentLoginScreenState createState() => _StudentLoginScreenState();
}
class _StudentLoginScreenState extends State<StudentLoginScreen> {
#override
Widget build(BuildContext context) {
final AuthService _authService = AuthService();
Size size = MediaQuery.of(context).size;
String email = '';
String password = '';
return Scaffold(
backgroundColor: HexColor(studentPrimaryColour),
body: SafeArea(
child: ListView(
children: <Widget>[
SizedBox(
height: 25.0,
),
HeadingText(
text: 'Login',
size: 60.0,
color: Colors.white,
),
SizedBox(
height: 25.0,
),
RoundedInputField(
hintText: "Email",
onChanged: (val) {
email = val;
},
),
SizedBox(
height: 5.0,
),
RoundedInputField(
hintText: "Password",
boolean: true,
onChanged: (val) {
password = val;
},
),
SizedBox(
height: 15.0,
),
Container(
margin: EdgeInsets.symmetric(vertical: 10),
width: size.width * 0.8,
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: FlatButton(
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 40),
color: Colors.white,
onPressed: () async {
print(email);
print(password);
},
child: Text(
'login',
style: GoogleFonts.montserrat(
color: HexColor(studentPrimaryColour), fontSize: 20),
),
),
),
),
SizedBox(
height: 15.0,
),
InkWell(
onTap: () {
Navigator.pushNamed(context, '/studentRegisterScreen');
},
child: HeadingText(
text: 'register?',
color: Colors.white,
size: 10,
),
),
],
),
),
);
}
}
Inside your login screen, you declared and initialised both properties email and password inside the build method. What this essentially means is, that as soon as your login widget gets rebuilded (for example when hiding the keyboard since Flutter has to recalculate size and so on) both properties are initialised again with ''.
Thats what StatefulWidget are also for - defining properties as part of the state, without being part of the build cycle. In other words, change it up to this:
class StudentLoginScreen extends StatefulWidget {
StudentLoginScreen();
#override
_StudentLoginScreenState createState() => _StudentLoginScreenState();
}
class _StudentLoginScreenState extends State<StudentLoginScreen> {
String email = '';
String password = '';
#override
Widget build(BuildContext context) {
...
}
Related
I have a list of tiles created with the 'tolist' method, each has a textField and controller.I want to get the sum of the values of all textFields into a variable and display as text.``
here is my code: `
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> myList = [
'Materials',
'Labour',
'Plant and Equipment',
'Subcontractor'
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
),
body: SingleChildScrollView(
child: Column(
children: [
ExpansionTile(
maintainState: true,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('Test Code'),
Text('sum of all here',//sum of all values from each textfield here
style: TextStyle(fontSize: 16),),
],
),
children: myList.map((cost) {
return MyListTile(cost);
}).toList(),
),
],
),
));
}
}
and MyListTile code :``
class MyListTile extends StatefulWidget {
String title;
MyListTile(this.title) : super();
#override
State<MyListTile> createState() => _MyListTileState();
}
class _MyListTileState extends State<MyListTile> {
final TextEditingController _myController = TextEditingController();
double materialCost = 0.0;
#override
Widget build(BuildContext context) {
return ListTile(
subtitle: Row(
children: [
Container(
margin: const EdgeInsets.only(top: 5, bottom: 5, right: 0, left: 0),
child: SizedBox(
height: 35,
width: 150,
child: TextField(
textAlignVertical: TextAlignVertical.center,
controller: _myController,
showCursor: true,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(left: 10),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15)),
disabledBorder: const OutlineInputBorder(),
filled: true,
labelText: 'Cost sum',
labelStyle: TextStyle(color: Colors.grey[500]),
hintText: 'Enter Cost',
hintStyle: TextStyle(color: Colors.grey[500]),
suffixIcon: InkWell(
child: const Icon(
Icons.clear,
),
onTap: () {
_myController.clear();
},
),
// isCollapsed: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15))),
),
),
),
Container(
margin: const EdgeInsets.all(3),
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
border: Border.all(color: Colors.white10, width: 1),
borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: () {
setState(() {
materialCost = double.parse(_myController.text);
});
},
child: const Icon(
Icons.done,
),
),
)
],
),
trailing: Column(
children: [
Container(
margin: const EdgeInsets.all(3),
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10)),
child: Text(
materialCost.toString(),
style: const TextStyle(
// color: mainColorShade,
fontSize: 14,
fontWeight: FontWeight.bold),
),
)
],
),
title: Text(
widget.title,
),
);
;
}
}
I have tried to find a solution from allover the internet and I can not get any
example
create textControllers for each of your textfields and pass it to your textfield inside your listTile:
class MyHomePage extends StatefulWidget {
...
}
class _MyHomePageState extends State<MyHomePage> {
List<String> myList = [
'Materials',
'Labour',
'Plant and Equipment',
'Subcontractor'
];
// look here: list of controllers for your need change it for your liking
List<TextEditingController> controllers = [
TextEditingController(),
TextEditingController(),
TextEditingController(),
TextEditingController(),
];
// look here: local state to store your sum of textfields
String sum = "";
#override
void initState() {
super.initState();
// look here: this will change sum value whenever either of the textfield's value changed
for (var i = 0; i < myList.length; i++) {
controllers[i].addListener(() {
setState(() {
sum = getSum(controllers);
});
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
),
body: SingleChildScrollView(
child: Column(
children: [
ExpansionTile(
maintainState: true,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Test Code'),
// look here: this is your sum text
Text(sum,style: TextStyle(fontSize: 16),),
],
),
children: [
// look here: pass the controllers to your mylistTile widgets
for (var i = 0; i < myList.length; i++)
MyListTile(
title: cost,
controller: controllers[i],
),
],
),
],
),
));
}
// if you want to change the sum result, change it here
String getSum(List<TextEditingController> controllers) {
return controllers.map((e) => "${e.text} ").toString();
}
}
Don't forget to do this in your MyListTile widget, otherwise you can't pass the controllers
class MyListTile extends StatefulWidget {
MyListTile({
required this.title,
required this.controller
}) : super();
final String title;
final TextEditingController controller;
#override
State<MyListTile> createState() => _MyListTileState();
}
Use widget.controller in your MyListTile instead of _myController
class _MyListTileState extends State<MyListTile> {
final TextEditingController _myController = TextEditingController();
double materialCost = 0.0;
#override
Widget build(BuildContext context) {
return ListTile(
subtitle: Row(
children: [
Container(
margin: const EdgeInsets.only(top: 5, bottom: 5, right: 0, left: 0),
child: SizedBox(
height: 35,
width: 150,
child: TextField(
textAlignVertical: TextAlignVertical.center,
// look here:
controller: widget.controller,
...
// rest of your code here
Hi im new to Flutter and coding and tried do build my first to do app. I've created a textformfield to add new todos with a button in a container above. I used the texteditingcontroller to get the userinput and stored the input in a variable. To test the outcome, I simply tried to display the information under the button, but it didnt work. When pressing the button, the text doesn't change. What did I do wrong here?
landing_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_application_1/presentation/widgets/to_do_section.dart';
class LandingPage extends StatefulWidget {
const LandingPage({Key? key}) : super(key: key);
#override
State<LandingPage> createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
#override
Widget build(BuildContext context) {
final _textController = TextEditingController();
String userInput = "";
return Scaffold(
appBar: AppBar(
title: const Center(child: Text("To-Do-App")),
backgroundColor: Colors.redAccent,
),
body: SingleChildScrollView(
child: Column(
children: [
const ToDos(),
Column(
children: [
Padding(
padding: EdgeInsets.only(top: 8.0, left: 20, right: 20, bottom: 20),
child: TextField(
controller: _textController,
textAlign: TextAlign.center,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Add a new ToDo",
) ,
),
),
MaterialButton(
color: Colors.redAccent,
onPressed: () {
setState(() {
userInput = _textController.text;
});
},
child: Text("Admit", style: TextStyle(color: Colors.white),),
),
Text(userInput)
],
)],
),
),
);
}
}
to_do_section.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_application_1/responsive_layout.dart';
class ToDos extends StatefulWidget {
const ToDos({Key? key, }) : super(key: key);
#override
State<ToDos> createState() => _ToDosState();
}
class _ToDosState extends State<ToDos> {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Padding(
padding: EdgeInsets.only(
top: SizeConfig.blockSizeHorizontal * 10,
left: SizeConfig.blockSizeVertical * 2.5,
right: SizeConfig.blockSizeVertical * 2.5,
bottom: SizeConfig.screenHeight / 8
),
child: SizedBox(
width: SizeConfig.blockSizeHorizontal*100,
height: SizeConfig.blockSizeVertical*40,
child: Container(
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.black45, style: BorderStyle.solid, width: 4)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text("hi"),
]),
),
),
),
);
}
}
Move these 2 variable declaration outside the build method. The build method is called when you setState and then it rebuilds again. That's the reason its not updated.
final _textController = TextEditingController();
String userInput = "";
#override
Widget build(BuildContext context) {
}
I am a beginner in flutter_bloc library pattern, i am trying to signup http post request. I follow all the bloc necessary steps but when i click on Signup button it shows me on the log "Unhandled Exception:BlocProvider.of() called with a context that does not contain a SignupBloc. No ancestor could be found starting from the context that was passed to BlocProvider.of(). This can happen if the context you used comes from a widget above the BlocProvider." Although i have added this Signupbloc in main.dart how to fix this issue.
Main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MultiBlocProvider(providers: [
BlocProvider<RecommendedBloc>(
create: (BuildContext context) => RecommendedBloc(
RecommendedForYouDataService(),
),
),
BlocProvider<CuisineDishesBloc>(
create: (BuildContext context) => CuisineDishesBloc(
BrowseByCuisineDishesDataService(),
),
),
BlocProvider<DetailsBloc>(
create: (BuildContext context) => DetailsBloc(
DetailsDataService(),
),
),
BlocProvider<SearchBloc>(
create: (BuildContext context) => SearchBloc(
AllRestaurantDataService(), SearchRestaurantDataService()),
),
BlocProvider<AllPhotosBloc>(
create: (BuildContext context) =>
AllPhotosBloc(AllPhotosDataService()),
),
BlocProvider<PlacePhotosBloc>(
create: (BuildContext context) =>
PlacePhotosBloc(PlacePhotosDataService()),
),
BlocProvider<FoodPhotosBloc>(
create: (BuildContext context) =>
FoodPhotosBloc(FoodPhotosDataService()),
),
BlocProvider<EventPhotosBloc>(
create: (BuildContext context) =>
EventPhotosBloc(EventPhotosDataService()),
),
BlocProvider<SignupBloc>(
create: (BuildContext context) => SignupBloc(SignUpDataService()),
),
], child: LoginPage()),
);
}
}
SignUpDataService.dart
class SignUpDataService {
Future<SignUp?> makeRequestSignUp(String firstName, String lastName,
String mobileNumber, String password, String gender) async {
var response =
await http.post(Uri.parse('$baseURL/customer/signup'), body: {
"phone_number": mobileNumber,
"password": password,
});
if (response.statusCode == 200) {
final responseString = response.body;
final data = jsonDecode(responseString);
SignUp signUp = SignUp.fromJson(data);
return signUp;
} else {
throw Exception();
}
}
}
Signup_event.dart
#immutable
abstract class SignupEvent {}
class SignUpSubmittedEvent extends SignupEvent {
final String phoneNumber;
final String password;
SignUpSubmittedEvent(this.firstName, this.lastName, this.phoneNumber,
this.password, this.gender);
}
SignupState_state
#immutable
abstract class SignupState {}
class SignupInitialState extends SignupState {}
class SignupLoadingState extends SignupState {}
class SignupSuccessState extends SignupState {
final SignUp data;
SignupSuccessState(this.data);
}
class SignupErrorState extends SignupState {
final String message;
SignupErrorState(this.message);
}
Signup_bloc.dart
class SignupBloc extends Bloc<SignupEvent, SignupState> {
SignUpDataService signUpDataService;
SignupBloc(this.signUpDataService) : super(SignupInitialState());
#override
Stream<SignupState> mapEventToState(
SignupEvent event,
) async* {
if (event is SignUpSubmittedEvent) {
yield SignupLoadingState();
try {
SignUp? signup = await signUpDataService.makeRequestSignUp(
event.phoneNumber,
event.password,
yield SignupSuccessState(signup!);
} catch (e) {
yield SignupErrorState(e.toString());}}}}
Register.dart
class RegisterPage extends StatefulWidget {
const RegisterPage({Key? key}) : super(key: key);
#override
_RegisterPageState createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
bool checkOS = Platform.isIOS;
late bool _passwordVisible;
final TextEditingController mobileNumber = TextEditingController();
final TextEditingController password = TextEditingController();
bool _validate = false;
final _formKey = GlobalKey<FormState>();
String pWord = "";
late List<String> menus;
static late int menuIndex;
late SignupBloc signupBloc;
void submitForm() {
final isValid = _formKey.currentState!.validate();
}
#override
void initState() {
_passwordVisible = false;
super.initState();
}
#override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: TuxedoColor.loginColor,
body: Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20.0),
child: Container(
alignment: Alignment.center,
child: Center(
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Container(
decoration: BoxDecoration(
border:
Border.all(color: TuxedoColor.greyColor),
borderRadius: BorderRadius.all(Radius.circular(
5.0) // <--- border radius here
),
),
child: Padding(
padding: const EdgeInsets.only(
top: 10.0,
bottom: 10.0,
left: 10.0,
right: 5.0),
child: Icon(
Icons.arrow_back_ios,
color: TuxedoColor.greyColor,
),
),
),
),
Text(
'Sign Up',
style: TextStyle(
fontSize: 25.0, fontWeight: FontWeight.bold),
),
GestureDetector(
onTap: () {},
child: Text(
'عربي',
style: TextStyle(
fontSize: 20.0, fontWeight: FontWeight.bold),
),
)
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 35.0),
child: TextFormField(
controller: mobileNumber,
keyboardType: TextInputType.phone,
decoration: new InputDecoration(
fillColor: TuxedoColor.textFieldColor,
filled: true,
hintText: "Mobile Number",
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: TuxedoColor.greyColor)),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: TuxedoColor.greyColor)),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 30.0),
child: TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: password,
obscureText: !_passwordVisible,
onChanged: (value) => pWord = value,
decoration: new InputDecoration(
suffixIcon: IconButton(
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
),
fillColor: TuxedoColor.textFieldColor,
filled: true,
hintText: "Password",
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: TuxedoColor.greyColor)),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: TuxedoColor.greyColor))),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0,
right: 15.0,
),
child: ConstrainedBox(
constraints:
BoxConstraints.tightFor(height: height * 0.065),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
TuxedoColor.redColor),
),
onPressed: () async {
submitForm();
final fName = firstName.text;
final lName = lastName.text;
final mNumber = mobileNumber.text;
final pass = password.text;
final gen = gender.toString();
signupBloc = BlocProvider.of<SignupBloc>(context)
..add(SignUpSubmittedEvent(
mNumber, pass)); //On this line getting error
},
child: Text(
'SignUp',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: TuxedoColor.blackColor),
)),
),
),
],
),
)
],
),
),
),
),
),
),
);
}
}
You should swap MaterialApp and MultiBlocProvider.
Bloc provider must be on top of MaterialApp as suggested in this issue by lib creator Felix.
I am struggling with this Chat Screen. The app is meant to ask questions (not part of the below code) and the user either selects answers or types them. When the user types a first answer everything goes according to the plan and a first message is displayed. However the app then goes on displaying the second answer twice, the third one three times and so on.
I have been facing this issue for a few days and I cannot figure out why the app behaves the way it does. Could you please take a look at the code and suggest a way to fix this?
To give you some background information, this Chat Screen is part of a larger application. It should subscribe to a stream when the user opens the app. Then each message is pushed to the stream, whether it is a question asked by the bot or an answer given by the User. The system listens to the stream and displays a new message each time the stream broadcasts something, in our case the latest user input.
I am using a list of message models built from the stream to display the messages. For the purpose of asking this question I simplified the model to the extreme but in practice it has 23 fields. Creating this list of messages is the best solution I managed to think of but there may be a better way to handle this situation. Feel free to let me know if you know of any.
Here is the code that I am running.
import 'package:flutter/material.dart';
import 'dart:async';
StreamController<ChatMessageModel> _chatMessagesStreamController = StreamController<ChatMessageModel>.broadcast();
Stream _chatMessagesStream = _chatMessagesStreamController.stream;
const Color primaryColor = Color(0xff6DA7B9);
const Color secondaryColor = Color(0xffF0F0F0);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chat Screen',
home: ChatScreen(),
);
}
}
class ChatMessageModel {
final String message;
const ChatMessageModel({
this.message,
}
);
factory ChatMessageModel.turnSnapshotIntoListRecord(Map data) {
return ChatMessageModel(
message: data['message'],
);
}
#override
List<Object> get props => [
message,
];
}
class ChatScreen extends StatefulWidget {
static const String id = 'chat_screen9';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _messageTextController = TextEditingController();
String _userInput;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: secondaryColor,
appBar: AppBar(
title: Row(
children: [
Container(
padding: EdgeInsets.all(8.0),
child: Text('Chat Screen',
style: TextStyle(color: Colors.white,),
),
)
],
),
backgroundColor: primaryColor,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessagesStream(),
Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: primaryColor,
width: 1.0,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: _messageTextController,
onChanged: (value) {
_userInput = value;
},
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
hintText: 'Type your answer here',
// border: InputBorder.none,
),
),
),
TextButton(
onPressed: () {
_messageTextController.clear();
debugPrint('Adding a ChatMessageModel with the message $_userInput to the Stream');
ChatMessageModel chatMessageModelRecord = ChatMessageModel(message: _userInput);
_chatMessagesStreamController.add(chatMessageModelRecord,);
},
child: Text(
'OK',
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
),
],
),
),
],
),
),
);
}
}
class MessagesStream extends StatelessWidget {
List<ChatMessageModel> _allMessagesContainedInTheStream = [];
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
}
);
return Expanded(
child: ListView.builder(
// reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(chatMessageModelRecord: _allMessagesContainedInTheStream[index]);
}
},
),
);
},
);
}
}
class UserChatBubble extends StatelessWidget {
final ChatMessageModel chatMessageModelRecord;
const UserChatBubble({
Key key,
#required this.chatMessageModelRecord,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 5,),
child: Container(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 7 / 10,),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
topLeft: Radius.circular(15.0),
),
color: primaryColor,
),
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 20,),
child: Text(chatMessageModelRecord.message,
style: TextStyle(
fontSize: 17,
// fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
);
}
}
First of all, thank you for the interesting problem and functioning example provided. I had to do some small changes to convert it to "null-safety", but my code should work on your computer too.
The only problem you had initialization of _chatMessagesStream listener. You should do it only once and ideally in initState, to call it only once.
So here is the fix for you:
class MessagesStream extends StatefulWidget {
#override
_MessagesStreamState createState() => _MessagesStreamState();
}
class _MessagesStreamState extends State<MessagesStream> {
final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
#override
void initState() {
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
return Expanded(
child: ListView.builder(
// reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(
chatMessageModelRecord:
_allMessagesContainedInTheStream[index],
);
} else {
print(snapshot.connectionState);
return Container();
}
},
),
);
},
);
}
}
Also providing full code for null-safety just in case!
import 'package:flutter/material.dart';
import 'dart:async';
final StreamController<ChatMessageModel> _chatMessagesStreamController =
StreamController<ChatMessageModel>.broadcast();
final Stream<ChatMessageModel> _chatMessagesStream =
_chatMessagesStreamController.stream;
const Color primaryColor = Color(0xff6DA7B9);
const Color secondaryColor = Color(0xffF0F0F0);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chat Screen',
home: ChatScreen(),
);
}
}
class ChatMessageModel {
final String? message;
const ChatMessageModel({
this.message,
});
factory ChatMessageModel.turnSnapshotIntoListRecord(Map data) {
return ChatMessageModel(
message: data['message'],
);
}
List<Object> get props => [
message!,
];
}
class ChatScreen extends StatefulWidget {
static const String id = 'chat_screen9';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _messageTextController = TextEditingController();
String? _userInput;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: secondaryColor,
appBar: AppBar(
title: Row(
children: [
Container(
padding: EdgeInsets.all(8.0),
child: Text(
'Chat Screen',
style: TextStyle(
color: Colors.white,
),
),
)
],
),
backgroundColor: primaryColor,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessagesStream(),
Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: primaryColor,
width: 1.0,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: _messageTextController,
onChanged: (value) {
_userInput = value;
},
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 20.0),
hintText: 'Type your answer here',
// border: InputBorder.none,
),
),
),
TextButton(
onPressed: () {
_messageTextController.clear();
debugPrint(
'Adding a ChatMessageModel with the message $_userInput to the Stream');
ChatMessageModel chatMessageModelRecord =
ChatMessageModel(message: _userInput);
_chatMessagesStreamController.add(
chatMessageModelRecord,
);
},
child: Text(
'OK',
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
),
],
),
),
],
),
),
);
}
}
class MessagesStream extends StatefulWidget {
#override
_MessagesStreamState createState() => _MessagesStreamState();
}
class _MessagesStreamState extends State<MessagesStream> {
final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
#override
void initState() {
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
return Expanded(
child: ListView.builder(
// reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(
chatMessageModelRecord:
_allMessagesContainedInTheStream[index],
);
} else {
print(snapshot.connectionState);
return Container();
}
},
),
);
},
);
}
}
class UserChatBubble extends StatelessWidget {
final ChatMessageModel chatMessageModelRecord;
const UserChatBubble({
Key? key,
required this.chatMessageModelRecord,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 5,
),
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 7 / 10,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
topLeft: Radius.circular(15.0),
),
color: primaryColor,
),
padding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 20,
),
child: Text(
"${chatMessageModelRecord.message}",
style: TextStyle(
fontSize: 17,
// fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
);
}
}
I have looked at this question on Stack Overflow Flutter getter isn't specified for the class, when it is specified. And I still cannot understand why my class Practice does not have access to the variable _text which is accessed from an element in the List with type TagColumn.
class Practice extends StatefulWidget {
#override
_PracticeState createState() => _PracticeState();
}
class _PracticeState extends State<Practice>{
int count = 0;
#override
Widget build(BuildContext context){
List<TagColumn> ok = List.generate(count, (int i) => new TagColumn());
return Scaffold(
backgroundColor: Colors.black,
body: new LayoutBuilder(builder: (context, constraint){
return new Stack(
children: <Widget>[
SingleChildScrollView(
child: SafeArea(
child: new Wrap(
direction: Axis.horizontal,
children: ok,
)
),
),
new Positioned(
child: new Align(
alignment: FractionalOffset.bottomRight,
child: Container(
margin: EdgeInsets.only(bottom: 50.0, right: 40.0),
child: RawMaterialButton(
onPressed: (){
setState(() {
if(count != 0 && ok[count]._text.text.isEmpty){
}
else{
count +=1;
}
});
},
shape: CircleBorder(),
child: Icon(
Icons.add_circle,
size: 100.0,
color: Color(0xffd3d3d3),
),
)
)
)
)
],
);
}),
);
}
}
class TagColumn extends StatefulWidget{
#override
State<StatefulWidget> createState() => new _TagColumn();
}
class _TagColumn extends State<TagColumn>{
final _text = TextEditingController();
bool _validate = false;
#override
Widget build(BuildContext context){
final tagField = TextField(
controller: _text,
obscureText: false,
style: TextStyle(fontFamily: 'Play', color: Colors.white, fontSize: 20),
maxLines: null,
keyboardType: TextInputType.text,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
hintText: "Tag",
errorText: _validate ? 'Value Can\'t be Empty': null,
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))),
);
return Container(
width: MediaQuery.of(context).size.width/2 - 40,
margin: EdgeInsets.symmetric(horizontal: 20, vertical: 20),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(32.0),
),
child: Theme(
data: ThemeData(
hintColor: Colors.white,
),
child: tagField,
),
);
}
}
What I am trying to do is not allow the user to create a new tag when pressing, "Plus," in the bottom right corner(see the image below) if the user doesn't enter text in the current one. In other words, if it's not empty. Thus, I am using the variable final _text = TextEditingController(), to check if the current tag is empty when pressing the plus button. If not, a new tag is created.
dart treates variables that start with an underscore as a private variable (since there is no private keyword in dart) so in order to solve your probelm, you need to remove the _(underscore) before the text variable.
what ill do is this
1- move the _text variable to the TagColumn class insted of the State class
class TagColumn extends StatefulWidget{
final text = TextEditingController(); // removed the _ so that to access it inside the Practise class
#override
State<StatefulWidget> createState() => new _TagColumn();
}
and update the TagColumn class to reflect those changes
class _TagColumn extends State<TagColumn>{
// final _text = TextEditingController(); <---- since the text is now in the TagColumn class not the state class
bool _validate = false;
#override
Widget build(BuildContext context){
final tagField = TextField(
controller: widget.text,
obscureText: false,
style: TextStyle(fontFamily: 'Play', color: Colors.white, fontSize: 20),
maxLines: null,
keyboardType: TextInputType.text,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
hintText: "Tag",
errorText: _validate ? 'Value Can\'t be Empty': null,
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))),
);
return Container(
width: MediaQuery.of(context).size.width/2 - 40,
margin: EdgeInsets.symmetric(horizontal: 20, vertical: 20),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(32.0),
),
child: Theme(
data: ThemeData(
hintColor: Colors.white,
),
child: tagField,
),
);
}
}