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
Related
Im having an issue while running the app. after initializing the app I couldnt properly see the screen at the same tiem there was an error shows which is **Future.value(); inside the feedback.dart. I have tried to catch any error which I have added but I couldnt find any.
I have seen many solution also tried but didnt worked out. Can I anyone please Let me know error which I showed below.
This is loginPage file:
import 'package:flutter/material.dart';
import 'package:food_app/bottombar/screenA.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
#override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
static Future<User?>loginUsingEmailPassword({required String email, required String password, required BuildContext context}) async {
FirebaseAuth auth = FirebaseAuth.instance;
User? user;
try {
UserCredential userCredential = await auth.signInWithEmailAndPassword(email: email, password: password);
user = userCredential.user;
} on FirebaseAuthException catch (e) {
if(e.code == "user not found"){
print("No User found for that email");
}
}
return user;
}
#override
Widget build(BuildContext context) {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: const Text("Hello world"),
),
body: SizedBox(
height: 400.0,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
controller: emailController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter Username",
),
),
),
const SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsets.all(15.0),
child: TextFormField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter Password",
),
),
),
const SizedBox(
height: 20.0,
),
ElevatedButton(
onPressed: () async{
User? user = await loginUsingEmailPassword(email: emailController.text, password: passwordController.text, context: context);
print(user);
if (user != null) {
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => const ScreenA()));
}
},
child: const Text("Login")),
],
),
),
);
}
}
And this is the main.dart file:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:food_app/loginpage.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<FirebaseApp> _initializeFirebase() async {
FirebaseApp firebaseApp = await Firebase.initializeApp();
return firebaseApp;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _initializeFirebase(),
builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.done) {
return const LoginPage();
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}
And my error is :
context.findRenderObject()!.sendSemanticsEvent(const TapSemanticEvent());
switch (_platform(context)) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return SystemSound.play(SystemSoundType.click);
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return Future<void>.value();
}
}
I'm trying to authenticate user login with firebase, but I have a problem when it has to move to a dashboard page after successful login. The login is successful, but it's not doing anything after login.
Here's my code:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const FirsApp());
}
class FirsApp extends StatelessWidget {
const FirsApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthenticationService>(
create: (context) => AuthenticationService(FirebaseAuth.instance)),
StreamProvider(
create: (_) =>
context.read<AuthenticationService>().authStateChanged,
initialData: const [])
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'First App',
theme: ThemeData(
primaryColor: appPrimaryColour,
scaffoldBackgroundColor: appBackGroupCoulour,
),
home: const AuthenticationWrapper(),
),
);
}
}
class AuthenticationWrapper extends StatelessWidget {
const AuthenticationWrapper({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User?>();
if (firebaseUser != null) {
return const Dashboard();
}
return const SignInPage();
}
}
class SignInPage extends StatelessWidget {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextField(
controller: emailController,
decoration: InputDecoration(
labelText: "Email",
),
),
TextField(
controller: passwordController,
decoration: InputDecoration(
labelText: "Password",
),
),
RaisedButton(
onPressed: () {
context.read<AuthenticationService>().signInWithEmailAndPassword(
email: emailController.text.trim(),
password: passwordController.text.trim(),
);
},
child: Text("Sign in"),
)
],
),
);
}
}
class AuthenticationService {
final FirebaseAuth _firebaseAuth;
AuthenticationService(this._firebaseAuth);
Stream<User?> get authStateChanged => _firebaseAuth.authStateChanges();
Future<String?> signInWithEmailAndPassword(
{required String email, required String password}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
return "Singed in";
} on FirebaseAuthException catch (e) {
return e.message;
}
}
}
I did put a breakpoint on the AuthenticationService and it did return Singed in, but it never moved to the dashboard page. The dashboard page is just an empty page with a container and a text field.
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
...)
I have my from that updates the user profile. I would like that the initial user profile values to be populated in the form field as the initial values before the user can make a change.
**Here is a form field **
final TextEditingController _firstNameController = TextEditingController();
Widget _buildFirstNameInput() {
return TextFormField(
controller: _firstNameController,
style: inputTextStyle,
decoration: formInputDecoration.copyWith(
labelText: 'First Name',
),
validator: (String value) {
print(value);
if (value.isEmpty) {
return 'First name is Required';
}
return null;
},
onChanged: (String value){
setState(() {
isEdited=true;
});
},
);
}
When the user navigators to this edit profile widget, the user profile instance is passed as an argument to the widget
class ProfilePage extends StatefulWidget {
final User userProfile;
ProfilePage({this.userProfile});
...some other code...
I have tried setting the initial values for the input field by using the the controller of the input field in initstate like so
void intitState(){
super.initState();
_firstNameController.text=widget.userProfile.name;
}
But this did not seem to work. How can i achieve it?
Setting the TextEditingController.text in your initState() is totally valid.
Full source code
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: ProfilePage(
userName: 'Thierry',
),
),
);
}
class ProfilePage extends StatefulWidget {
final String userName;
ProfilePage({this.userName});
#override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
final TextEditingController _firstNameController = TextEditingController();
#override
void initState() {
super.initState();
_firstNameController.text = widget.userName;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: _buildFirstNameInput()),
);
}
Widget _buildFirstNameInput() {
return TextFormField(
controller: _firstNameController,
decoration: InputDecoration(
labelText: 'First Name',
),
validator: (String value) {
print(value);
if (value.isEmpty) {
return 'First name is Required';
}
return null;
},
onChanged: (value) {},
);
}
}
!!! The other answer is WRONG
If you set the TextEditingController.text at the beginning of your _buildFirstNameInput() it will reset the form field at every rebuild:
Full source code for easy copy-paste:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: ProfilePage(
userName: 'Thierry',
),
),
);
}
class ProfilePage extends StatefulWidget {
final String userName;
ProfilePage({this.userName});
#override
_ProfilePageState createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
final TextEditingController _firstNameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
_buildFirstNameInput(),
const SizedBox(height: 48.0),
ElevatedButton(
onPressed: () {
setState(() {});
},
child: Text('MESS AROUND WITH THE STATE'),
),
],
)),
);
}
Widget _buildFirstNameInput() {
_firstNameController.text = widget.userName;
return TextFormField(
controller: _firstNameController,
decoration: InputDecoration(
labelText: 'First Name',
),
validator: (String value) {
print(value);
if (value.isEmpty) {
return 'First name is Required';
}
return null;
},
onChanged: (value) {},
);
}
}
You could also use Flutter Hooks. It will make your code leaner:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: ProfilePage(
userName: 'Thierry',
),
),
);
}
class ProfilePage extends HookWidget {
final String userName;
ProfilePage({this.userName});
#override
Widget build(BuildContext context) {
final _firstNameController = useTextEditingController(text: userName);
return Scaffold(
body: Center(
child: _FirstNameInput(controller: _firstNameController),
),
);
}
}
class _FirstNameInput extends StatelessWidget {
final TextEditingController controller;
const _FirstNameInput({Key key, this.controller}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: 'First Name',
),
validator: (String value) {
print(value);
if (value.isEmpty) {
return 'First name is Required';
}
return null;
},
onChanged: (value) {},
);
}
}
The following code displays the items listed in my collection (Firestore)
I am attempting to create the ability to check any item(s) and then have those items store into a "Favorites" on the next screen.
Currently, the checkboxes are an all or nothing. Either all items are unchecked or checked once tapped.
class _SelectScreenState extends State<SelectScreen> {
bool _isChecked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Select Exercises')),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('exercises').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot)
{
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context,
data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}
}
class Record {
final String name;
final DocumentReference reference;
Record(this.name, this.reference);
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
name = map['name'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Record<$name:>";
}
It is because you are making use of a single variable for all the checkboxes.
To fix that you could create a dedicated stateful widget, which would handle the state of each of the checkbox's separately from the rest.
So you could replace your ListTile with something like
Exercise(
title: record.name,
)
and then you could define the Exercise widget as follows
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}
Here is a complete working example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: <Widget>[
Exercise(
title: "Exercises 1",
),
Exercise(
title: "Exercises 2",
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}
Because you have a global variable _isChecked, this needs to be created with each listTile.
Try moving the variable
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
bool _isChecked = false; //try moving it here
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}