give permission to read and use photos - firebase

I want to give my users the opportunity to upload a profile picture. Therefor I wrote some code with permissionHandler and image_picker packages.
MaterialButton(
child: Text('Profilbild auswählen'),
onPressed: () async {
var status = await Permission.photos.status;
print(status);
if (status.isGranted) {
final pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
print(pickedFile);
setState(() {
if (pickedFile != null) {
_imageFile = File(pickedFile.path);
} else {
print('No image selected.');
}
});
} else {
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text('Bilder Zugriff'),
content: Text(
'Wir brauchen zugriff auf deine Bilder um ein Profilbild hochladen zu können!'),
actions: <Widget>[
CupertinoDialogAction(
child: Text('Grant'),
onPressed: () async {
Navigator.of(context).pop();
await Permission.photos.request();
}),
CupertinoDialogAction(
child: Text('Settings'),
onPressed: () => openAppSettings(),
),
]));
}
}),
But the permission stays at status 'denied' although the iPhone-Settings allows the usage of gallery. How can I change that status in the application and does the rest of the code makes sense to upload the image to firebase? How is it possible to change the image live on the screen? Thought it works with .setState()...

Related

Upload and store image into firebase using flutter

I am trying to work out how a user can upload a eg a profile image and have this store in firebase. this is the code I have so far which shows the image picker but I cannot get the path nor have the image uploaded
dynamic _showSelectImageDialog() {
return Platform.isIOS ? _iosBottomSheet() : _androidDialog();
}
Future _iosBottomSheet() async => showCupertinoModalPopup(
context: context,
builder: (context) {
return CupertinoActionSheet(
// title: Text('Add Photo'),
actions: <Widget>[
CupertinoActionSheetAction(
onPressed: () => _upload(ImageSource.camera),
child: const Text('Take Photo'),
),
CupertinoActionSheetAction(
onPressed: () => _upload(ImageSource.gallery),
child: const Text('Choose Photo'),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
);
},
);
_androidDialog() {
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: const Text('Add Photo'),
children: <Widget>[
SimpleDialogOption(
onPressed: () => _upload(ImageSource.camera),
child: const Text('Take Photo'),
),
SimpleDialogOption(
onPressed: () => _upload(ImageSource.gallery),
child: const Text('Choose From Gallery'),
),
SimpleDialogOption(
onPressed: () => Navigator.pop(context),
child: const Text(
'Cancel',
style: TextStyle(
color: Colors.redAccent,
),
),
),
],
);
},
);
}
// Select and image from the gallery or take a picture with the camera
// Then upload to Firebase Storage
_upload(ImageSource source) async {
var picker = ImagePicker();
PickedFile pickedImage;
try {
pickedImage = (await picker.pickImage(source: source, maxWidth: 1920))
as PickedFile;
File imageFile = File(pickedImage.path);
try {
// Uploading the selected image with some custom meta data
await storageRef
.child('uploads/user/avatar/${widget.user.id}/$imageFile.jpg')
.putFile(imageFile);
print(imageFile);
// Refresh the UI
setState(() {});
} on FirebaseException {
// print(error);
}
} catch (err) {
print(err);
}
Navigator.pop(context);
}
_displayProfileImage() {
// No new profile image
if (_profileImage == null) {
// No existing profile image
if (widget.user.profileImageUrl.isEmpty) {
// Display placeholder
return AssetImage('assets/images/user_placeholder.jpg');
} else {
// User profile image exists
return CachedNetworkImageProvider(widget.user.profileImageUrl);
}
} else {
// New profile image
return FileImage(File(_profileImage.path));
}
}
1) Pick image using image picker
Put this package in your pubspec.yaml
image_picker: ^0.8.4+4
2) Use this code to pick image
image = await _picker.pickImage(source: ImageSource.gallery);
3) save the image in firebase cloud and get the image URL
Put these packages in your pubspec.yaml
cloud_firestore: ^3.1.0
firebase_storage: ^10.1.0
firebase_core: ^1.10.0
Use this code to upload the image to cloud storage
var imageFile = File(image!.path);
String fileName = basename(imageFile.path);
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref =
storage.ref().child("Image-" + productname.text);
Use this code to get the URL
UploadTask uploadTask = ref.putFile(imageFile);
await uploadTask.whenComplete(() async {
var url = await ref.getDownloadURL();
image_url = url.toString();
}).catchError((onError) {
print(onError);
});
4) Finally add image url to firebase database
Code to add data to firebase database
Map<String, dynamic> demodata = {
"image_url": imageurl
};
CollectionReference collectionreference =
FirebaseFirestore.instance.collection(image);
collectionreference.add(demodata);

My question is about how to preview an image before it is uploaded to firebase in flutter? (image_picker_package)(provider)

In firebase Storage Service Dart File:
//storage reference
Future<String> uploadUserImage(File? image, String path) async {
String imageUrl;
Reference ref =
_firebaseStorage.ref().child('userImages').child('$path.jpg');
await ref.putFile(image!);
imageUrl = await ref.getDownloadURL();
return imageUrl;
}
In User Provider Dart File(State management) using ChangeNotifier:
//getter
void changeImageUrl(String imageUrl) {
_imageUrl = imageUrl;
notifyListeners();
}
//method
uploadPhotoToFirebase(File fileImage) async {
//used firebase user uid for path and File fileImage for File
try {
var imageUrl = await _storageService.uploadUserImage(fileImage, _userUid);
//assign downloadedUrl to getter
changeImageUrl(imageUrl);
} on PlatformException catch (e) {
print('Failed to pick image: $e');
}
}
User Profile Screen(UI):
//method for Image_picker
Future _pickPhotos(ImageSource imageSource) async {
final userProvider = Provider.of<UserProvider>(context, listen: false);
try {
final image = (await _picker.pickImage(source: imageSource));
if (image == null) return;
final imageTem = File(image.path);
//assigning File(String path) to firebase storage
await userProvider.uploadPhotoToFirebase(imageTem);
} on PlatformException catch (e) {
print('Failed to pick image: $e');
}
}
//Inside the build
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
backgroundColor: darkGrey,
radius: 54.0,
//assign private String _imageUrl
child: _imageUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Image.network(
_imageUrl!,
width: 100,
height: 200,
fit: BoxFit.cover,
),
)
: Center(
child: Icon(Icons.person_rounded,
color: appColor, size: 80.0))),
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateColor.resolveWith(
(states) => Colors.transparent)),
child: EditPhotoText(),
onPressed: () {
_pickPhotos(ImageSource.gallery);
/* Expecting this to change the local state to show
image preview but doesn't work.
*/
userProvider.changeImageUrl(_imageUrl!);
}),
Image is being stored in firebase and fetching image is working too. I am not able to figure out how to preview the image before uploading it to firebase. I think I have to use the set state to preview locally but I don't know how or is there a way I can do that through the provider package?

Flutter : Firebase PhoneAuthentication Problem

I have just developed an app which requires phone authentication. Inside login screen I can able to achieve to login via phone. But my concern is : for the first time when I enter phone number and enter verification number it comes back to login which in reality expected to navigate to homescreen. For the second try system is able to work and navigate to home screen as expected. Here is my code block. I am wondering which part of the code I make mistake since login info pop back again and system is able to navigate to home screen after second try:
My code block :
class _LoginScreenState extends State<LoginScreen> {
String phoneNo, smssent, verificationId;
get verifiedSuccess => null;
Future<void> verifyPhone() async {
final PhoneCodeAutoRetrievalTimeout autoRetrieve = (String verId) {
this.verificationId = verId;
};
final PhoneCodeSent smsCodeSent = (String verId, [int forceCodeResent]) {
this.verificationId = verId;
smsCodeDialoge(context).then((value) {
print("Doğrulama Kodu Gönderildi");
});
};
final PhoneVerificationCompleted verifiedSuccess = (AuthCredential auth) {};
final PhoneVerificationFailed verifyFailed = (AuthException e) {
print('${e.message}');
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNo,
timeout: const Duration(seconds: 5),
verificationCompleted: verifiedSuccess,
verificationFailed: verifyFailed,
codeSent: smsCodeSent,
codeAutoRetrievalTimeout: autoRetrieve,
);
}
Future<bool> smsCodeDialoge(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new AlertDialog(
title: Text('Doğrulama Kodunu Giriniz'),
content: TextField(
onChanged: (value) {
this.smssent = value;
},
),
contentPadding: EdgeInsets.all(10.0),
actions: <Widget>[
FlatButton(
onPressed: () {
FirebaseAuth.instance.currentUser().then((user) {
if (user != null) {
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
} else {
Navigator.of(context).pop();
signIn(smssent);
}
});
},
child: Text(
'Doğrulama Yap',
style: TextStyle(color: Colors.blue),
),
),
],
);
});
}
Future<void> signIn(String smsCode) async {
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode,
);
await FirebaseAuth.instance.signInWithCredential(credential).then((user)
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
}).catchError((e) {
print(e);
});
}
if (user != null) {
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
Here you are calling pop, which will take you to the previous screen if the user is not null, and after that, you are pushing the HomeScreen into the stack. Try not poping and just pushing, by the way, are you using routes and streamprovider in your code?

Flutter Firebase updated display name successfully but still can't access. It is null

display name of firebase user is updated but still the getter was called on 'null'.
I registered with email and password.
The function used for registering is....
void _registerAccount() async {
final User user = (await _auth.createUserWithEmailAndPassword(
email: emailC.text,
password: passwordC.text,
))
.user;
if (user != null) {
if (!user.emailVerified) {
await user.sendEmailVerification();
}
await user.updateProfile(displayName: usernameC.text);
final user1 = _auth.currentUser;
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => Home(
user: user1,
username: usernameC.text,
)));
} else {
bool _isSuccess = false;
}
}
passwordC,usernameC and emailC are the controllers.
After, signing up I wanted to show 'Display name' on the screen. but I tried to print it and see first, it showed null.
This is my build method of homepage...
#override
Widget build(BuildContext context) {
initialize();
print('Reached here');
print(widget.user.displayName);
return Scaffold(
body: Center(
child: Container(
child: Column(
children: [
Text(''),
Center(
child: RaisedButton(
child: Text("Logout"),
onPressed: () {
FirebaseAuth.instance.signOut();
}),
),
],
)),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
setState(() {
currentindex = index;
});
},
currentIndex: currentindex,
items: [
BottomNavigationBarItem(
title: Text("Home"), icon: Icon(Icons.account_balance_wallet)),
BottomNavigationBarItem(
title: Text("Home"), icon: Icon(Icons.search)),
BottomNavigationBarItem(
title: Text("Home"), icon: Icon(Icons.account_circle)),
]),
);
initialize method here is to call these two methods
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
initialize method cannot be problem anyway.
Thank you.
FirebaseAuth.currentUser() will only detect changes made locally to the user, but if any server-side changes occur, it won't detect them, unless FirebaseUser.reload() is called first so you have to call
await user.reload();
after the update() is called.

Navigate to a page after user sign up in the android app in flutter using FirebaseAuth

I am working on phone authentication using flutter and firebase. So, when a user 'Register or Sign Up' for the app an OTP is received and then when he clicks the 'Login' button the phone number gets saved in the firebase but the page does not get loaded. After clicking the Login button, the account gets created but the page doesn't change and I have to close the app and open it again and the main page gets displayed. Please, tell me how to do it.
Code for "Class SignUpView" :
class SignUpView extends StatefulWidget {
#override
_SignUpViewState createState() => _SignUpViewState();
}
class _SignUpViewState extends State<SignUpView> {
final formKey = new GlobalKey<FormState>();
String phoneNo, verificationId, smsCode;
bool codeSent = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 25, right: 25),
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(hintText: "Enter Phone Number"),
onChanged: (val) {
setState(() {
this.phoneNo = val;
});
},
),
),
codeSent ? Padding(
padding: EdgeInsets.only(left: 25, right: 25),
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(hintText: "Enter OTP"),
onChanged: (val) {
setState(() {
this.smsCode = val;
});
},
),
): Container(),
Padding(
padding: EdgeInsets.only(left: 25, right: 25),
child: RaisedButton(
child: Center(
child: codeSent ? Text("Login") : Text("Login"),
),
onPressed: () {
codeSent? AuthService().signInWithOTP(smsCode, verificationId):verifyPhone(phoneNo);
},
),
),
],
),
),
);
}
Future<void> verifyPhone(phoneNo) async {
final PhoneVerificationCompleted verified = (AuthCredential authResult) {
AuthService().signIn(authResult);
};
final PhoneVerificationFailed verificationFailed = (
AuthException authException) {
print('${authException.message}');
};
final PhoneCodeSent smsSent = (String verId, [int forceResend]) {
this.verificationId = verId;
setState(() {
this.codeSent = true;
});
};
final PhoneCodeAutoRetrievalTimeout autoTimeOut = (String verId) {
this.verificationId = verId;
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNo,
timeout: const Duration(seconds: 5),
verificationCompleted: verified,
verificationFailed: verificationFailed,
codeSent: smsSent,
codeAutoRetrievalTimeout: autoTimeOut);
}
}
The part of code where I need to add the navigation is:
onPressed: () {
codeSent? AuthService().signInWithOTP(smsCode, verificationId):verifyPhone(phoneNo);
}
There is another part of code- Class AuthService:
class AuthService {
handleAuth() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if(snapshot.hasData) {
return Home_Page();
}
else {
return first_screen(); //Login();
}
},
);
}
signOut() {
FirebaseAuth.instance.signOut();
}
signIn(AuthCredential authCreds) {
if(authCreds != null){
FirebaseAuth.instance.signInWithCredential(authCreds);
}
}
signInWithOTP(smsCode, verId) {
AuthCredential authCreds = PhoneAuthProvider.getCredential(verificationId: verId, smsCode: smsCode);
signIn(authCreds);
}
}
I tried to add navigation inside:
onPressed: () {
codeSent? AuthService().signInWithOTP(smsCode, verificationId):verifyPhone(phoneNo);
Navigator.of(context).pushReplacementNamed('/create_account');
}
But this didn't worked as the above code would navigate to the page and the account won't be created.
I want that when the user type the OTP and then click the Login button, then his phone number should get verified and account should be created on firebase and then the user should be displayed another page. You can either use: Navigator.of(context).pushReplacementNamed('/create_account'); or Account_setup_page() for displaying the page.
I'd really be thankful for all the help I can get.
First of all you need to setup a stream for authentication changes in your AuthService class, i.e.
Stream<FirebaseUser> get user {
return _auth.onAuthStateChanged;
}
Then in your home screen (better in a wrapper widget) you could listen to that stream, so if the user is logged in, it will be redirected to the home screen, else it will be redirected to the sign in screen. This can be accomplished with the following code:
final user = Provider.of<FirebaseUser>(context); // listener for auth state
if (user == null) {
return SignUpView(); // or the sign in view
} else {
return Home(user: user,);
// home screen with the user as argument for easier access in the future
}
Maybe in the future when you will implement the sign out feature, this piece of code will automatically redirect the user to the sign in page

Resources