Created
April 19, 2019 23:23
-
-
Save denisviana/7aee8049e0214e60e268b9060b01db30 to your computer and use it in GitHub Desktop.
Flutter + Firebase + BLoC (Recover Password)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:io'; | |
import 'package:firebase_storage/firebase_storage.dart'; | |
import 'package:firebase_auth/firebase_auth.dart'; | |
import 'package:cloud_firestore/cloud_firestore.dart'; | |
import 'package:hotaru_car/src/models/car_ad.dart'; | |
import 'package:hotaru_car/src/models/collections_mapper.dart'; | |
import 'package:hotaru_car/src/models/response/user_response.dart'; | |
import 'package:hotaru_car/src/models/user.dart'; | |
import 'package:uuid/uuid.dart'; | |
class FirebaseProvider { | |
//region User | |
Firestore _firestore = Firestore.instance; | |
FirebaseAuth _firebaseAuth = FirebaseAuth.instance; | |
FirebaseStorage _firebaseStorage = FirebaseStorage.instance; | |
Future<UserResponse> authUser(User user) async { | |
return await _firebaseAuth | |
.signInWithEmailAndPassword(email: user.email, password: user.password) | |
.then((result) async { | |
User user = await getUserByUid(result.uid); | |
return user != null | |
? UserResponse(hasError: false, user: user) | |
: UserResponse(hasError: true); | |
}).catchError((error) { | |
return UserResponse(hasError: true, msg: error.message); | |
}); | |
} | |
Future<User> createUser(User user) async { | |
return await _firebaseAuth | |
.createUserWithEmailAndPassword( | |
email: user.email, password: user.password) | |
.then((result) async { | |
String imageProfileUrl = user.imageFile != null ? await _uploadProfileImage(user.imageFile, result.uid) : null; | |
if(imageProfileUrl!=null && imageProfileUrl.isNotEmpty){ | |
UserUpdateInfo userUpdateInfo = UserUpdateInfo(); | |
userUpdateInfo.displayName = user.name; | |
userUpdateInfo.photoUrl = imageProfileUrl; | |
result.updateProfile(userUpdateInfo); | |
user.imageProfileUrl = imageProfileUrl; | |
} | |
await registerUser(user, result.uid); | |
user.uid = result.uid; | |
return user; | |
}); | |
} | |
Future<User> getUserByUid(String uid) async { | |
return await _firestore | |
.collection(UserFields.COLLECTION) | |
.document(uid) | |
.get() | |
.then((result) { | |
var user = User.fromMap(result.data); | |
if(user!=null) | |
user.uid = uid; | |
return user; | |
}).catchError((error) { | |
return null; | |
}); | |
} | |
Future<User> getUserByEmail(String email) async { | |
return await _firestore | |
.collection(UserFields.COLLECTION) | |
.where(UserFields.EMAIL, isEqualTo: email) | |
.getDocuments() | |
.then((result) { | |
return result.documents.isNotEmpty | |
? User.fromMap(result.documents.first.data) | |
: null; | |
}); | |
} | |
Future<void> registerUser(User user, String uid) => _firestore | |
.collection(UserFields.COLLECTION) | |
.document(uid) | |
.setData(user.toMap()); | |
Future<UserResponse> recoveryPassword(String email) => _firebaseAuth.sendPasswordResetEmail(email: email); | |
Future<Null> signOut() async => await _firebaseAuth.signOut(); | |
Future<UserResponse> changePassword(String email, String oldPassword, String newPassword) async{ | |
AuthCredential credential = EmailAuthProvider.getCredential(email: email, password: oldPassword); | |
FirebaseUser currentUser = await _firebaseAuth.currentUser(); | |
return await currentUser.reauthenticateWithCredential(credential) | |
.then((_) async{ | |
await currentUser.updatePassword(newPassword); | |
return UserResponse(hasError: false); | |
}) | |
.catchError((error){ | |
return UserResponse(hasError: true); | |
}); | |
} | |
Future<UserResponse> updateUser(User user) async{ | |
return await _firebaseAuth.currentUser() | |
.then((result) async{ | |
await result.updateEmail(user.email); | |
if(user.imageFile!=null){ | |
String imageProfileUrl = await _uploadProfileImage(user.imageFile, result.uid); | |
imageProfileUrl!=null ? user.imageProfileUrl = imageProfileUrl : user.imageProfileUrl = ""; | |
} | |
await _firestore.collection(UserFields.COLLECTION) | |
.document(result.uid) | |
.updateData(user.toMap()); | |
return UserResponse(user: user, hasError: false); | |
}).catchError((error){ | |
return UserResponse(hasError: true); | |
}); | |
} | |
Future<String> _uploadProfileImage(File file, String uid) async { | |
var imageName = Uuid().v1(); | |
var ref = _firebaseStorage.ref().child("user_images/$uid/$imageName.jpg"); | |
StorageUploadTask task = ref.putFile(file); | |
StorageTaskSnapshot storageTaskSnapshot = await task.onComplete; | |
String url = await storageTaskSnapshot.ref.getDownloadURL(); | |
return url; | |
} | |
//endregion | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:bloc_provider/bloc_provider.dart'; | |
import 'package:hotaru_car/src/common/constants.dart'; | |
import 'package:hotaru_car/src/models/response/response.dart'; | |
import 'package:hotaru_car/src/repository/recovery_password_repository.dart'; | |
import 'package:rxdart/rxdart.dart'; | |
class RecoveryPasswordBloc extends Bloc{ | |
final repository = RecoveryPasswordRepository(); | |
final _state = StreamController<AppResponse>.broadcast(); | |
final _email = BehaviorSubject<String>(); | |
static final _validateNameField = StreamController<bool>.broadcast(); | |
static final _validateEmailField = StreamController<bool>.broadcast(); | |
Function(String) get changeEmail => _email.sink.add; | |
Stream get isValidName => _validateNameField.stream; | |
Stream get isValidEmail => _validateEmailField.stream; | |
Stream get getState => _state.stream; | |
Observable<String> get email => _email.stream.transform(_validateEmailString); | |
Future<bool> recoveryPassword() async { | |
_state.add(AppResponse(isLoading: true, hasError: false)); | |
return await repository.recoveryPassword(_email.value) | |
.then((_){ | |
_state.add(AppResponse(isLoading: false, hasError: false)); | |
return true; | |
}).catchError((error){ | |
_state.add(AppResponse(isLoading: false, hasError: true, msg: error.message)); | |
return false; | |
}); | |
} | |
final _validateEmailString = | |
StreamTransformer<String, String>.fromHandlers(handleData: (email, sink) { | |
Pattern pattern = | |
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; | |
RegExp regex = new RegExp(pattern); | |
if (email.isEmpty) { | |
sink.addError(AppState.EMAIL_IS_EMPTY); | |
_validateEmailField.sink.add(false); | |
} else if (!regex.hasMatch(email)) { | |
sink.addError(AppState.EMAIL_INVALID); | |
_validateEmailField.sink.add(false); | |
} else { | |
sink.add(email); | |
_validateEmailField.sink.add(true); | |
} | |
}); | |
final _validateNameString = | |
StreamTransformer<String, String>.fromHandlers( | |
handleData: (name, sink) { | |
if (name.isEmpty) { | |
sink.addError(AppState.FIELD_IS_EMPTY); | |
} else { | |
sink.add(name); | |
} | |
}); | |
@override | |
void dispose() { | |
_email.value = ""; | |
_email.drain(); | |
_email.close(); | |
_state.close(); | |
_validateEmailField.close(); | |
_validateNameField.close(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:hotaru_car/src/models/response/user_response.dart'; | |
import 'package:hotaru_car/src/repository/base.dart'; | |
class RecoveryPasswordRepository extends BaseRepository{ | |
Future<UserResponse> recoveryPassword(String email) => firebaseProvider.recoveryPassword(email); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:hotaru_car/src/bloc/recoverypassword/recovery_password_bloc.dart'; | |
import 'package:hotaru_car/src/bloc/recoverypassword/recovery_password_bloc_provider.dart'; | |
import 'package:hotaru_car/src/common/constants.dart'; | |
import 'package:hotaru_car/src/common/localization.dart'; | |
import 'package:hotaru_car/src/common/localization_strings.dart'; | |
import 'package:hotaru_car/src/models/response/response.dart'; | |
import 'package:hotaru_car/src/widget/alerts.dart'; | |
import 'package:hotaru_car/src/widget/margin.dart'; | |
class RecoverPasswordScreen extends StatefulWidget { | |
@override | |
_RecoverPasswordScreenState createState() => _RecoverPasswordScreenState(); | |
} | |
class _RecoverPasswordScreenState extends State<RecoverPasswordScreen> { | |
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>(); | |
final GlobalKey<ScaffoldState> _scaffoldstate = | |
new GlobalKey<ScaffoldState>(); | |
RecoveryPasswordBloc _bloc; | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
_bloc = RecoveryPasswordBlocProvider.of(context); | |
} | |
@override | |
void dispose() { | |
_bloc.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
key: _scaffoldstate, | |
appBar: AppBar( | |
brightness: Brightness.light, | |
backgroundColor: Colors.transparent, | |
elevation: 0.0, | |
iconTheme: IconThemeData(color: Theme.of(context).accentColor), | |
), | |
backgroundColor: Colors.white, | |
body: Container( | |
padding: EdgeInsets.fromLTRB(16, 10, 16, 10), | |
child: Form( | |
key: _formKey, | |
child: StreamBuilder<AppResponse>( | |
stream: _bloc.getState, | |
initialData: AppResponse(isLoading: false, hasError: false), | |
builder: (context, snapshot){ | |
if(snapshot.data.isLoading) | |
return _showLoading(); | |
if(snapshot.data.hasError) | |
_showSnackBar(snapshot.data.msg); | |
return ListView(children: <Widget>[ | |
Text(Localization.of(context).getString(RecoverPassword), | |
style: TextStyle(color: Colors.black, fontSize: 30.0)), | |
MarginVertically(30.0), | |
Container( | |
height: 260.0, | |
child: Stack( | |
fit: StackFit.expand, | |
children: <Widget>[ | |
Positioned( | |
top: 0.0, | |
left: 0.0, | |
right: 0.0, | |
bottom: 25.0, | |
child: Card( | |
shape: RoundedRectangleBorder( | |
borderRadius: BorderRadius.circular(10.0)), | |
elevation: 4.0, | |
child: Container( | |
color: Colors.white, | |
padding: EdgeInsets.fromLTRB(16, 20, 16, 20), | |
child: Column( | |
children: <Widget>[ | |
Text( | |
Localization.of(context) | |
.getString(RecoverPasswordText), | |
style: TextStyle( | |
color: Colors.black45, fontSize: 17.0)), | |
MarginVertically(10.0), | |
StreamBuilder( | |
stream: _bloc.email, | |
builder: (context, snapshot) { | |
return TextField( | |
autofocus: true, | |
textInputAction: TextInputAction.done, | |
keyboardType: TextInputType.emailAddress, | |
style: TextStyle(color: Colors.black), | |
onChanged: _bloc.changeEmail, | |
decoration: InputDecoration( | |
labelText: | |
Localization.of(context).getString(Email), | |
hintText: Localization.of(context) | |
.getString(EnterYourEmail), | |
errorText: _getErrorMsg( | |
snapshot.error as AppState) | |
), | |
); | |
}, | |
), | |
], | |
), | |
), | |
), | |
), | |
Positioned( | |
bottom: 0.0, | |
right: 20.0, | |
child: FloatingActionButton( | |
backgroundColor: Theme.of(context).accentColor, | |
onPressed: () { | |
_submit(); | |
}, | |
child: Icon( | |
Icons.arrow_forward, | |
color: Colors.white, | |
)), | |
) | |
], | |
), | |
), | |
MarginVertically(30.0), | |
] | |
); | |
} | |
), | |
) | |
) | |
); | |
} | |
void _submit() async{ | |
if(await _bloc.recoveryPassword()){ | |
_onSuccess(); | |
} | |
} | |
Widget _showLoading() { | |
return Center( | |
child: SizedBox( | |
height: 40.0, | |
width: 40.0, | |
child: CircularProgressIndicator(), | |
), | |
); | |
} | |
String _getErrorMsg(AppState errorEnum) { | |
switch (errorEnum) { | |
case AppState.FIELD_IS_EMPTY : | |
return Localization.of(context).getString(FieldIsEmpty); | |
break; | |
case AppState.EMAIL_IS_EMPTY : | |
return Localization.of(context).getString(FieldIsEmpty); | |
break; | |
case AppState.EMAIL_INVALID : | |
return Localization.of(context).getString(EmailInvalid); | |
break; | |
case AppState.PASSWORD_IS_EMPTY : | |
return Localization.of(context).getString(PasswordIsEmpty); | |
break; | |
case AppState.PASSWORD_INVALID : | |
return Localization.of(context).getString(PasswordInvalid); | |
break; | |
case AppState.GENERIC_ERROR : | |
return Localization.of(context).getString(GenericError); | |
break; | |
default: | |
return ""; | |
} | |
} | |
void _onSuccess() { | |
showAlert( | |
context, | |
Localization.of(context).getString(RecoverPasswordPopupTitle), | |
Localization.of(context).getString(RecoverPasswordPopupMsg), | |
callback: () {}); | |
} | |
void _showSnackBar(String msg) { | |
_scaffoldstate.currentState.showSnackBar(SnackBar(content: Text(msg))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
superb code mate