Flutter - TextFormField resets page whenever keyboard pops up - flutter

I am trying to build a login page through firebase with the flutter framework but I have run into the problem of my screen resetting anytime I attempt to use the TextFormField, it'll load the keyboard, and then immediately kick me out and reset the page. I have looked on other threads but can't seem to find any fixes. Thanks for all the help in advance!
class SignInTwo extends StatefulWidget {
final Function toggleView;
SignInTwo({this.toggleView});
#override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignInTwo> {
final AuthService _auth = AuthService();
final _formKey = GlobalKey<FormState>();
bool loading = false;
// text fields state
String email = "";
String password = "";
String error = "";
#override
Widget build(BuildContext context) {
return loading ? Loading() : Scaffold(
backgroundColor: Colors.brown[100],
appBar: AppBar(
backgroundColor: Colors.brown[400],
elevation: 0.0,
title: Text('Sign in'),
actions: <Widget>[
FlatButton.icon(
label: Text("Register"),
icon: Icon(Icons.person),
onPressed: () {
widget.toggleView();
},
)
],
),
body: Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 50.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
SizedBox(
height: 20,
),
TextFormField(
decoration: textDecoration.copyWith(hintText: 'Email'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter email';
}
return null;
},
onChanged: (value) {
setState(() => email = value);
},
),
SizedBox(
height: 20.0,
),
TextFormField(
decoration: textDecoration.copyWith(hintText: 'Password'),
validator: (val) {
if (val.length < 6) {
return 'Enter password with more than 6+ characters';
}
return null;
},
onChanged: (value) {
setState(() => password = value);
},
obscureText: true,
),
SizedBox(
height: 20.0,
),
RaisedButton(
color: Colors.pink[400],
child: Text(
"Sign In",
style: TextStyle(color: Colors.white),
),
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() {
loading = true;
});
dynamic result = await _auth.signInWithEmailAndPassword(
email, password);
if (result == null) {
setState(() {
error = "Something went wrong!!";
loading = false;
});
}
}
},
),
SizedBox(
height: 20,
),
Text(
error,
style: TextStyle(color: Colors.red),
)
],
),
)),
);
}
}
I am not sure if it is a problem with my main class or any of the others but I will leave them below just incase it might be the cause of the problem.
class MyApp extends StatefulWidget {
final SharedPreferences storage;
const MyApp({Key key, this.storage}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
fontFamily: "Audiowide",
primarySwatch: Colors.yellow,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Wrapper(),
);
}
}
This wrapper just decides if the user is already logged in or not. In my case it is not so it will return the Authenticate class.
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final player = Provider.of<Player>(context);
if(player == null){
return Authenticate();
}else{
return Home();
}
}
}
class Authenticate extends StatefulWidget {
#override
_AuthenticateState createState() => _AuthenticateState();
}
class _AuthenticateState extends State<Authenticate> {
bool showSignIn = true;
void toggleView() {
setState(() {
showSignIn = !showSignIn;
});
}
#override
Widget build(BuildContext context) {
if(showSignIn){
return SignInTwo(toggleView: toggleView);
}else{
return Register(toggleView: toggleView);
}
}
}

Every time you open keyboard the build function is called and check if its loading and create new Scaffold instance this the reason of your problem
you can fix it by adding key to Scaffold
something like this
Scaffold(key:scaffoldKey
...)

Related

Flutter Bloc is not emitting or triggering the event

I am building a mobile application using Flutter. I am using Bloc Flutter library. I am a beginner to Flutter BloC. Now, I am refactoring my login form using BLoC. But it seems that it is not dispatching the BLoC event when the login button is clicked.
I have the LoginBloc class with the following code:
class LoginBloc extends Bloc<LoginEvent, LoginState> {
LoginBloc() : super(LoginState.initial()) {
on<LoginEvent>((event, emit) {
// yield the state here. check the event and then process the event and yield the state based on the result.
if (event is Login) {
ApiService.post(ApiEndpoints.login, {
'email': event.email,
'password': event.password
}).then((response) => () {
if (response.statusCode == 200) {
// TODO: provide implementation
var responseJson = jsonDecode(response.body);
MeData meData = MeData.fromJson(responseJson['data']);
} else {
ApiError apiError = Utilities.parseApiError(response.body);
emit(LoginState(event.email, event.password, false, apiError));
}
}).onError((error, stackTrace) => () {
var apiError = ApiError();
apiError.setGenericErrorMessage("Something went wrong!");
emit(LoginState(event.email, event.password, false, apiError));
});
}
});
}
}
The is my login_event.dart file:
part of 'login_bloc.dart';
#immutable
abstract class LoginEvent extends Equatable {
const LoginEvent();
}
class Login extends LoginEvent {
final String email = "";
final String password = "";
const Login(email, password);
#override
List<Object?> get props => [
email,
password
];
}
This is my login_state.dart file:
part of 'login_bloc.dart';
class LoginState extends Equatable {
final String email;
final String password;
final bool isLoading;
final ApiError error;
const LoginState(this.email, this.password, this.isLoading, this.error);
static LoginState initial()
{
return LoginState("", "", false, ApiError());
}
#override
List<Object?> get props => [
email,
password,
isLoading,
error
];
}
This is my login.dart file (screen)
class LoginPage extends StatefulWidget {
final String title = 'Login';
#override
State<LoginPage> createState() => _LoginPage();
}
class _LoginPage extends State<LoginPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String? _email;
String? _password;
ApiError _apiError = ApiError();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
return Center(
child: Form(
key: _formKey,
child: Column(
children: [
GenericFormError(errorMessage: state.error.getGenericErrorMessage()),
Padding(
padding: const EdgeInsets.all(10),
child: TextFormField(
decoration: InputDecoration(
labelText: "Email",
border: const OutlineInputBorder(),
errorText: _apiError.getFieldError("email")
),
onChanged: (value) => setState(() {
_email = value;
}),
validator: (value) {
if (value == null || value.isEmpty) {
return "Please enter email";
}
return null;
},
),
),
Padding(
padding: const EdgeInsets.all(10),
child: TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(
labelText: "Password",
border: const OutlineInputBorder(),
errorText: _apiError.getFieldError("password")
),
onChanged: (value) => setState(() {
_password = value;
}),
validator: (value) {
if (value == null || value.isEmpty) {
return "Please enter password";
}
return null;
},
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () {
var isValid = _formKey.currentState!.validate();
if (isValid) {
context.read<LoginBloc>().add(Login(_email.toString(), _password.toString()));
}
},
child: const Text('Login'),
),
),
),
],
),
)
);
}
),
);
}
}
As you can see in the login form, when the button is clicked and form is valid, I am dispatching the BLoC event using this code.
context.read<LoginBloc>().add(Login(_email.toString(), _password.toString()));
But it is not triggering LoginBloc() at all. What is wrong with my code and how can I fix it? It is not throwing any errors either.
This is how I initialised the Bloc Provider:
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (BuildContext context) {
return LoginBloc();
})
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Shar Kya Mal'),
));
}
}
I think you haven't initialized the bloc provider for it yet. You can wrap whole widget with bloc provider and init LoginBloc or init on file app
Adding Bloc Provider in file app.dart
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.dark(),
home: Scaffold(
body: BlocProvider(
create: (context) => NameBloc(NameBlocRepository()),
child: HomeScreen(),
),
));
}
}
or this
init bloc service and add to bloc provider
_nameBloc = nameBloc(blocService: _blocService);
return MultiBlocProvider(
providers: [ BlocProvider.value( value: _userBloc, )], child: MaterialApp

Unable to reflect updated parent state in showModalBottomSheet

I am relatively new to Flutter and while I really like it I'm struggling to find a way to have state values in the parent be updated in showModalBottomSheet. I think I understand the issue to be that the values aren't reflecting in showModalBottomSheet when they change in the parent because showModalBottomSheet doesn't get rebuilt when the state updates.
I am storing title and content in the parent because I was also hoping to use it for editing as well as creating todos. I figured the showModalBottomSheet could be shared for both. I am attaching a picture on the simulator. What I am expecting is that when title changes (i.e. is no longer an empty string) then the Add To Do button should become enabled but it currently stays disabled unless I close the modal and re-open it.
Any help or insight would be greatly appreciated. Below is the code in my main.dart file which has showModalBottomSheet and has the state values that need to be passed down. NewToDo contains the text fields in the modal that capture the values and updates the state in main accordingly.
** EDIT **
I have seen this link but it doesn't really explain how to pass state from a parent widget down to a showBottomModalSheet widget, it just shows how to manage state within a showBottomModalSheet widget. My goal is to have the state change from within main to be able to be picked within showBottomModalSheet.
main.dart
import 'package:flutter/material.dart';
import './todoitem.dart';
import './todolist.dart';
import 'classes/todo.dart';
import './newtodo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'To Do Homie',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: const MyHomePage(title: "It's To Do's My Guy"),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String content = '';
String title = '';
int maxId = 0;
ToDo? _todo;
final titleController = TextEditingController();
final contentController = TextEditingController();
List<ToDo> _todos = [];
void _addTodo(){
final todo = ToDo (
title: title,
id: maxId,
isDone: false,
content: content
);
if (_todo != null){
setState(() {
_todos[_todos.indexOf(_todo!)] = todo;
});
} else {
setState(() {
_todos.add(todo);
});
}
setState(() {
content = '';
maxId = maxId++;
title = '';
_todo = null;
});
contentController.text = '';
titleController.text = '';
}
#override
void initState() {
super.initState();
titleController.addListener(_handleTitleChange);
contentController.addListener(_handleContentChange);
futureAlbum = fetchAlbum();
}
void _handleTitleChange() {
setState(() {
title = titleController.text;
});
}
void _handleContentChange() {
setState(() {
content = contentController.text;
});
}
void _editTodo(ToDo todoitem){
setState(() {
_todo = todoitem;
content = todoitem.content;
title = todoitem.title;
});
contentController.text = todoitem.content;
titleController.text = todoitem.title;
}
void _deleteToDo(ToDo todoitem){
setState(() {
_todos = List.from(_todos)..removeAt(_todos.indexOf(todoitem));
});
}
void _clear(){
contentController.text = '';
titleController.text = '';
setState(() {
content = '';
title = '';
_todo = null;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Center(
child: Container(
alignment: Alignment.topCenter,
child: ToDoList(_todos, _editTodo, _deleteToDo)
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
print(context);
return Container(child:NewToDo(titleController, contentController, _addTodo, _clear, _todo),);
});
},
child: const Icon(Icons.add),
backgroundColor: Colors.deepPurple,
),
);
}
}
NewToDo.dart
import 'package:flutter/material.dart';
import './classes/todo.dart';
class NewToDo extends StatelessWidget {
final Function _addTodo;
final Function _clear;
final ToDo? _todo;
final TextEditingController titleController;
final TextEditingController contentController;
const NewToDo(this.titleController, this.contentController, this._addTodo, this._clear, this._todo, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return
Column(children: [
TextField(
decoration: const InputDecoration(
labelText: 'Title',
),
controller: titleController,
autofocus: true,
),
TextField(
decoration: const InputDecoration(
labelText: 'Details',
),
controller: contentController,
autofocus: true,
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: titleController.text.isNotEmpty ? () => _addTodo() : null,
child: Text(_todo != null ? 'Edit To Do' : 'Add To Do'),
style: ButtonStyle(
backgroundColor: titleController.text.isNotEmpty ? MaterialStateProperty.all<Color>(Colors.deepPurple) : null,
overlayColor: MaterialStateProperty.all<Color>(Colors.purple),
),
),
Visibility (
visible: titleController.text.isNotEmpty || contentController.text.isNotEmpty,
child: ElevatedButton(
onPressed: () => _clear(),
child: const Text('Clear'),
)),
])
],
);
}
}
TextControllers are listenable. You can just wrap your Column in two ValueListenables (one for each controller) and that will tell that widget to update whenever their values are updated.
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: contentController,
builder: (context, _content, child) {
return ValueListenableBuilder(
valueListenable: titleController,
builder: (context, _title, child) {
return Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: 'Title',
),
controller: titleController,
autofocus: true,
),
TextField(
decoration: const InputDecoration(
labelText: 'Details',
),
controller: contentController,
autofocus: true,
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed:
titleController.text.isNotEmpty ? () => _addTodo() : null,
child: Text(_todo != null ? 'Edit To Do' : 'Add To Do'),
style: ButtonStyle(
backgroundColor: titleController.text.isNotEmpty
? MaterialStateProperty.all<Color>(Colors.deepPurple)
: null,
overlayColor: MaterialStateProperty.all<Color>(Colors.purple),
),
),
Visibility(
visible: titleController.text.isNotEmpty ||
contentController.text.isNotEmpty,
child: ElevatedButton(
onPressed: () => _clear(),
child: const Text('Clear'),
),
),
],
)
],
);
},
);
},
);
Another more general alternative I can think of is to use Provider (or, if you're familiar enough, regular InheritedWidgets) and the pattern suggested in its readme:
class Example extends StatefulWidget {
const Example({Key key, this.child}) : super(key: key);
final Widget child;
#override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
#override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this,
child: widget.child,
),
);
}
}
where it suggests reading the count like this:
return Text(context.watch<int>().toString());
Except I'm guessing you can just provide the whole state of the widget to descenents by replacing _count with this to refer to the whole stateful widget. Don't know if this is recommended though.
ValueListenables would be my first choice and then maybe hooks to simplify their use though.

How to add multiple textfields and get the values from it in flutter

I'm trying to add multiple textformfileds on click of add more Button and trying to access the values of all fields on form submit.
I'm not getting the values of dynamic added fields.
I follow this url to dynamically added textformfields.
And here is my code.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dynamic TextFormFields',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyForm(),
debugShowCheckedModeBanner: false,
);
}
}
class MyForm extends StatefulWidget {
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
TextEditingController _nameController;
TextEditingController _name1Controller;
static List<String> friendsList = [null];
static List<String> friendsList1 = [null];
#override
void initState() {
super.initState();
_nameController = TextEditingController();
_name1Controller = TextEditingController();
}
#override
void dispose() {
_nameController.dispose();
_name1Controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
title: Text('Dynamic TextFormFields'),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// name textfield
Padding(
padding: const EdgeInsets.only(right: 32.0),
child: TextFormField(
controller: _nameController,
decoration: InputDecoration(hintText: 'Enter your name'),
validator: (v) {
if (v.trim().isEmpty) return 'Please enter something';
return null;
},
),
),
SizedBox(
height: 20,
),
Text(
'Add Friends',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16),
),
..._getFriends(),
SizedBox(
height: 40,
),
FlatButton(
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
print(_MyFormState.friendsList);
print(_MyFormState.friendsList1);
}
},
child: Text('Submit'),
color: Colors.green,
),
],
),
),
),
);
}
/// get firends text-fields
List<Widget> _getFriends() {
List<Widget> friendsTextFields = [];
for (int i = 0; i < friendsList.length; i++) {
friendsTextFields.addAll(
[
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
children: [
Expanded(child: FriendTextFields(i)),
Expanded(child: FriendTextFields1(i)),
SizedBox(
width: 16,
),
// we need add button at last friends row
_addRemoveButton(i == friendsList.length - 1, i),
],
),
)
],
);
}
return friendsTextFields;
}
/// add / remove button
Widget _addRemoveButton(bool add, int index) {
return InkWell(
onTap: () {
if (add) {
// add new text-fields at the top of all friends textfields
friendsList.insert(0, null);
} else
friendsList.removeAt(index);
setState(() {});
},
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: (add) ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(20),
),
child: Icon(
(add) ? Icons.add : Icons.remove,
color: Colors.white,
),
),
);
}
}
class FriendTextFields extends StatefulWidget {
final int index;
FriendTextFields(this.index);
#override
_FriendTextFieldsState createState() => _FriendTextFieldsState();
}
class _FriendTextFieldsState extends State<FriendTextFields> {
TextEditingController _nameController;
#override
void initState() {
super.initState();
_nameController = TextEditingController();
}
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_nameController.text = _MyFormState.friendsList[widget.index] ?? '';
});
return TextFormField(
controller: _nameController,
onChanged: (v) => _MyFormState.friendsList[widget.index] = v,
decoration: InputDecoration(hintText: 'textbox 1'),
validator: (v) {
if (v.trim().isEmpty) return 'Please enter something';
return null;
},
);
}
}
//////////////////////////////////////////////////////////////////
class FriendTextFields1 extends StatefulWidget {
final int index;
FriendTextFields1(this.index);
#override
_FriendTextFields1State createState() => _FriendTextFields1State();
}
class _FriendTextFields1State extends State<FriendTextFields1> {
TextEditingController _name1Controller;
#override
void initState() {
super.initState();
_name1Controller = TextEditingController();
}
#override
void dispose() {
_name1Controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_name1Controller.text = _MyFormState.friendsList1[widget.index] ?? '';
});
return TextFormField(
controller: _name1Controller,
onChanged: (v) => {
_MyFormState.friendsList1[widget.index] = v,
},
decoration: InputDecoration(hintText: 'textbox 2'),
validator: (v) {
if (v.trim().isEmpty) return 'Please enter something';
return null;
},
);
}
}
You can create a list of TextEditingController just like a list the list of TextFields you have created.
Then when you add a new textField to the list, add a new controller to the controllerList too.
Then you can easily fetch the data from any textfield in the list using its index something like:
String secondTextFieldText = controllerList[1].text;

How can I solve Flutter navigation BuilderContext subtype error?

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_auths/pages/searchservice.dart';
import 'package:flutter_auths/pages/tasks.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var queryResultSet = [];
var tempSearchStore = [];
initiateSearch(value) {
if (value.length == 0) {
setState(() {
queryResultSet = [];
tempSearchStore = [];
});
}
var capitalizedValue =
value.substring(0, 1).toUpperCase() + value.substring(1);
if (queryResultSet.length == 0 && value.length == 1) {
SearchService().searchByName(value).then((QuerySnapshot docs) {
for (int i = 0; i < docs.documents.length; ++i) {
queryResultSet.add(docs.documents[i].data);
}
});
} else {
tempSearchStore = [];
queryResultSet.forEach((element) {
if (element['Username'].startsWith(capitalizedValue)) {
setState(() {
tempSearchStore.add(element);
});
}
});
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text('Firestore search'),
),
body: ListView(children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
onChanged: (val) {
initiateSearch(val);
},
decoration: InputDecoration(
prefixIcon: IconButton(
color: Colors.black,
icon: Icon(Icons.arrow_back),
iconSize: 20.0,
onPressed: () {
Navigator.of(context).pop();
},
),
contentPadding: EdgeInsets.only(left: 25.0),
hintText: 'Search by name',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0))),
),
),
SizedBox(height: 10.0),
GridView.count(
padding: EdgeInsets.only(left: 10.0, right: 10.0),
crossAxisCount: 2,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0,
primary: false,
shrinkWrap: true,
children: tempSearchStore.map((element) {
return buildResultCard(element);
}).toList())
]));
}
}
Widget buildResultCard(data) {
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
elevation: 2.0,
child: Container(
child: Column(
children: <Widget> [ Text(data['Username'],
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
),
),
RaisedButton(
onPressed: () {
Navigator.push(
data,
MaterialPageRoute(builder: (data) => ProfilePage()),
);
},
child: const Text('asd', style: TextStyle(fontSize: 12)),
),
]
)
)
);
}
Here I search for a user from database then it shows me the results in cards, I added a button and by clicking on it I want to navigate the page to another page but the following error occures.
this is the error and the app
So I want to click on specific user’s button and redirect the page to that user’s profile. How can I do that?
You are getting this error because instead of passing buildContext you are passing data.
So your error gets removed if you change you code from this
Navigator.push(
data,
MaterialPageRoute(builder: (data) => ProfilePage()),
);
to
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProfilePage(username: data['Username']))
);
This is how you should pass the data to the Profile Page.
Also
Widget buildResultCard(data)
be changed to
Widget buildResultCard(context, data)
and
buildResultCard(element);
to
buildResultCard(context, element);
First, you need to Navigate to that page with data like
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProfilePage(profileData: data))
);
then you need to receive that data
class ProfilePage extends StatefulWidget {
var profileData;
ProfilePage({this.profileData});
#override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(widget.profileData['username']),
),
);
}
}
You can pass and receive data in another way
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProfilePage(),settings: RouteSettings(arguments: data))
);
then
class ProfilePage extends StatefulWidget {
#override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
var profileData;
#override
Widget build(BuildContext context) {
profileData=ModalRoute.of(context).settings.arguments;
return Scaffold(
body: Center(
child: Text(profileData['username']),
),
);
}
}

break a form into multiple widget and interact with those widget in flutter

i have a form which i decided to break into multiple widget for code re- usability. the problem i am having i dont know how to interact with each components. for example, if the main form declare a variable, how do i access that variable in the custom textfield widget which is store in a different dart file.
below is the code i have
form dart file (main.dart)
import 'package:flutter/material.dart';
import 'package:finsec/widget/row_text_input.dart';
import 'package:finsec/widget/text_form_field.dart';
import 'package:finsec/widget/save_button.dart';
import 'package:finsec/utils/strings.dart';
import 'package:finsec/utils/dimens.dart';
import 'package:finsec/utils/colors.dart';
import 'package:finsec/widget/column_text_input.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Simple Interest Calculator App',
home: ThirdFragment(),
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.indigo,
accentColor: Colors.indigoAccent),
));
}
class ThirdFragment extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ThirdFragmentState();
}
}
class _ThirdFragmentState extends State<ThirdFragment> {
var _formKey = GlobalKey<FormState>();
var _currentItemSelected = '';
bool isError = false;
bool isButtonPressed = false;
#override
void initState() {
super.initState();
}
TextEditingController amountController = TextEditingController();
TextEditingController frequencyController = TextEditingController();
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Scaffold(
appBar: AppBar(
title: Text('Simple Interest Calculator'),
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column (children: [
Padding(
padding: EdgeInsets.only(top: 10.0, bottom: 5.0, left: 15.0, right: 15.0),
child: CustomTextField(textInputType:TextInputType.number,
textController: amountController,
errorMessage:'Enter Income Amount',
labelText:'Income Amount for testing'),
),
RowTextInput(inputName: 'Frequency:',
textInputType: TextInputType.number,
textController: frequencyController,
errorMessage: 'Choose Income Frequency',
labelText: 'Income Amount for testing'
),
RowTextInput(inputName: 'Date Paid:',
textInputType: TextInputType.number,
textController: datePaidController,
errorMessage: 'Pick Income Payment Date',
labelText: 'Income Amount for testing'
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save),
onPressed: () => {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
},
splashColor: blueGrey,
),
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save_and_continue),
onPressed: () => {},
splashColor: blueGrey,
)
])
]
),
),
}
RowTextInput is a different dart file that contains this code. RowTextInput.dart
import 'package:flutter/material.dart';
import 'package:finsec/utils/hex_color.dart';
class CustomTextField extends StatelessWidget {
CustomTextField({
this.textInputType,
this.textController ,
this.errorMessage,
this.labelText,
});
TextInputType textInputType;
TextEditingController textController;
String errorMessage, labelText;
#override
Widget build(BuildContext context) {
bool isError = false;
return Container(
child: TextFormField(
keyboardType: textInputType,
style: Theme
.of(context)
.textTheme
.title,
controller: textController,
validator: (String value) {
if (value.isEmpty) {
return errorMessage;
}
},
decoration: InputDecoration(
labelStyle: TextStyle(
color: Colors.grey,
fontSize: 16.0
),
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //size of textfield
errorStyle: TextStyle(
color: Colors.red,
fontSize: 15.0
),
border: OutlineInputBorder(
borderSide: BorderSide(width:5.0),
borderRadius: BorderRadius.circular(5.0)
)
)
),
);
}
}
i want to access isError and isButtonPressed variables located in main.dart from RowTextInput.dart and be able to assign values. main.dart should then be able to see those values assign in RowTextInput.dart file.
also,i want to move the MaterialButton button in its own widget file (button.dart) but then i dont know how this dart file will interact with the main.dart file when button is click or to check values of isError and IS button pressed. basically, i am breaking the form into different components (textfield and button) and store them in their own separate file. but i want all the files main.dart, rowintputtext, button.dart(new) to be able to see values of variables in main.dart and change the values. is this possible? is there an easier way?
thanks in advance
If you think about it. In Flutter the Button and RawMaterialButton are already in other files. And the manage to do exactly what you want.
You should create a File mycustomButtons.dart.
In the file you should create a class that will build your Buttons...
But it must has two parameters in it's constructor actionSave actionSaveAndContinue.
You will then create two functions in your main something like:
void _save() {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
}
Then you should pass your created functions as parameters:
MyCustomButtons(actionSave: _save, actionSaveAndContinue: _saveAndContinue)
So the button will have all needed information to update your main.dart variables.
The textField is pretty much the same. But you will need pass a validation function and a TextEditingController.
You can see the font of RawnMaterialButton, TextFormField to see how they receive (and pass) data from one class to an other.
I was also looking for breaking a form into multiple classes. This is that I did :
Form
Pass the onSaved function at the form level.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_CustomFormField(
onSaved: (value) => _myModelForm.field1 = value),
),
_CustomFormField2(
onSaved: (value) => _myModelForm.field2 = value),
)
),
RaisedButton(
onPressed: () {
// Validate will return true if the form is valid, or false if
// the form is invalid.
if (_formKey.currentState.validate()) {
// Process data.
_formKey.currentState.save();
// Observe if your model form is updated
print(myModelForm.field1);
print(myModelForm.field2)
}
},
child: Text('Submit'),
),
],
),
);
}
_CustomFormField1
The onSaved function will be passed as argument. This class can be either in the same file than the form or in another dedicated file.
class _CustomFormField1 extends StatelessWidget {
final FormFieldSetter<String> onSaved;
//maybe other properties...
_CustomFormField1({
#required this.onSaved,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
// You can keep your validator here
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: onSaved,
),
);
}
}
Like onSaved, you can do the same way for focusNode, onFieldSubmitted, validator if needed in
I hope it will help you and others
There's probably a more elegant way to do it but I am currently experimenting with Singletons. See the code below:
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'dart:async';
class AppModel {
TextEditingController nameController;
TextEditingController surnameController;
StreamController<String> fullnameStreamController;
AppModel() {
nameController = TextEditingController();
surnameController = TextEditingController();
fullnameStreamController = StreamController.broadcast();
}
update() {
String fullname;
if (nameController.text != null && surnameController.text != null) {
fullname = nameController.text + ' ' + surnameController.text;
} else {
fullname = 'Please enter both names';
}
fullnameStreamController.add(fullname);
}
}
GetIt getIt = new GetIt();
final appModel = getIt.get<AppModel>();
void main() {
getIt.registerSingleton<AppModel>(AppModel());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Singleton Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String text;
update() {
setState(() {
});
}
#override
void initState() {
text = 'waiting for input';
appModel.fullnameStreamController.stream.listen((data) {
text = data;
update();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amberAccent),
child: Column(
children: <Widget> [
Card(
color: Colors.white,
child: Text('Name'),
),
Card(
color: Colors.yellow,
child: NameTextField()
),
Divider(),
Card(
color: Colors.white,
child: Text('Surname'),
),
Card(
color: Colors.yellow,
child: SurnameTextField()
),
OkButton(),
Card(
color: Colors.white,
child: Text('Full name'),
),
Card(
color: Colors.orange,
child: FullnameText(text),
),
],
),
),
);
}
}
class NameTextField extends StatefulWidget {
NameTextField({Key key}) : super(key: key);
_NameTextFieldState createState() => _NameTextFieldState();
}
class _NameTextFieldState extends State<NameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.nameController,
),
);
}
}
class SurnameTextField extends StatefulWidget {
SurnameTextField({Key key}) : super(key: key);
_SurnameTextFieldState createState() => _SurnameTextFieldState();
}
class _SurnameTextFieldState extends State<SurnameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.surnameController,
),
);
}
}
class FullnameText extends StatefulWidget {
FullnameText(this.text,{Key key}) : super(key: key);
final String text;
_FullnameTextState createState() => _FullnameTextState();
}
class _FullnameTextState extends State<FullnameText> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.text),
);
}
}
class OkButton extends StatefulWidget {
OkButton({Key key}) : super(key: key);
_OkButtonState createState() => _OkButtonState();
}
class _OkButtonState extends State<OkButton> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white10,
child: RaisedButton(
color: Colors.white,
child: Icon(Icons.check),
onPressed: () {appModel.update();},
),
);
}
}
Check how I use the three controllers in the update function of the AppModel class.
CustomTextFields must extends parent(widget where is form) in this case it is ThirdFragment
class CustomTextField extends ThirdFragment{
CustomTextField({
this.textInputType,
this.textController,
this.errorMessage,
this.labelText,
});