I have an UploadTask to send an image to Firebase Storage and it works but now I want to get the respective download URL but I'm facing some issues.
var upload = childRef.DownloadURL;
upload.AddOnSuccessListener(this);
I'm implementing the IOnSuccessListener interface so the function has to be:
public void OnSuccess(Java.Lang.Object result){
//get url from result
}
How can I cast the result to access the URL?
I got a solution in debugging mode
i saw the downloadurl's properties and found the Scheme and SchemeSpecificPart
Scheme = "https"
SchemeSpecificPart = "//firebasestorage.googleapis.com/v0/b/maplog-e4ba5.appspot.com/o/-L0AMbihF23YKxsL1uss?alt=media&token=5c7ccef1-c857-4982-a288-fded2f0ff1aa"
so here is my code:
void IOnSuccessListener.OnSuccess(Java.Lang.Object result)
{
var snapShot = (UploadTask.TaskSnapshot)result;
string imgUrl = snapShot.DownloadUrl.Scheme
+ ":"
+ snapShot.DownloadUrl.SchemeSpecificPart;
}
and it works!
i was looking for the solution :((
but i finally found it myself XD
I'm having the same issue, but was able to catch the error when adding the AddOnFailureListener. The error message wasn't helpful.
An unknown error occurred, please check the HTTP result code and inner
exception for server response.
The result code was 0. The error code as -13000, which is an unknown error according to Firebase error table.
FirebaseApp.InitializeApp(Application.Context);
FirebaseStorage storage = FirebaseStorage.Instance;
StorageReference storageRef = storage.GetReferenceFromUrl("gs://");
StorageReference imageRef = storageRef.Child("folder/image.jpg");
var downloadURL = imageRef.DownloadUrl.AddOnSuccessListener(this, this).AddOnFailureListener(this, this);
public void OnSuccess(Java.Lang.Object result)
{
string downloadURL = result.ToString();
}
public void OnFailure(Java.Lang.Exception e)
{
Log.Warn("FirebaseStorage", "Download Failure", e);
}
I had the same issue of the error code as -13000 which is an unknown error according to Firebase error table , i resolved by updating the playstore app in the device
I would say EncodedSchemeSpecificPart worked perfect for me
var snapShot = (UploadTask.TaskSnapshot)result;
string downloadURL =snapShot.DownloadUrl.Scheme + ":" +snapShot.DownloadUrl.EncodedSchemeSpecificPart;
For the latest Xamarin Android this is the correct answer
public async void OnSuccess(Java.Lang.Object result)
{
var snapShot = (UploadTask.TaskSnapshot)result;
if (snapShot != null)
{
var url = await snapShot.Storage.GetDownloadUrlAsync();
imageUrl = url?.ToString();
}
}
Related
I see similar questions on this site but most of the questions are related to fetching the download URL for a single uploaded image. Taking help from those posts, now I can get the download URL of a single image.
But I face a problem when I try to get download URL for multiple images uploaded together. I want to do three things...
1. Select three images
2. Upload them to Firebase Cloud Storage
3. Get the URLs of the uploaded images and save them in an ArrayList.
I can do the first two things successfully, but have not managed to achieve the third thing. When I click the "update" button, all images are perfectly stored in Cloud Storage, but show an error when requesting the download URL of all images.
Here is the code for when I click the "update" button:
upload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressDialog.setMessage("Uploading .... ");
progressDialog.show();
storageReference = FirebaseStorage.getInstance().getReference().child("Pictures");
int uploadCount = 0;
// imageList is an ArrayList<Uri> which holds the address of selected 3 images.
// imageAddress is an ArrayList<String> where I want to save all downloadUrls of images (each url is saved as a string).
// imagePath is a StorageReference
while(uploadCount < imageList.size()) {
Log.d("UploadCount", uploadCount+"");
Uri uri_Image = imageList.get(uploadCount);
imagePath = storageReference.child(uri_Image.getLastPathSegment());
imagePath.putFile(uri_Image).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
imagePath.getDownloadUrl().addOnSuccessListener(newOnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
Uri downloadUri = uri;
imageAddress.add(downloadUri.toString());
Log.d("ImageAddress Size: ", imageAddress.size()+"");
}
});
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(SignOutActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
progressDialog.dismiss();
}
}); //.............
if(uploadCount == (imageList.size()-1)) {
Log.d("Good", "HELLO HELLO");
Toast.makeText(SignOutActivity.this, "Successfully Uploaded", Toast.LENGTH_LONG).show();
upload.setClickable(false);
progressDialog.dismiss();
}
else {
Log.d("BAD", "NOT HELLO "+uploadCount);
}
uploadCount = uploadCount + 1;
}
}
});
Here is the error:
2020-02-15 17:02:26.945 28207-28735/com.example.practiceapplication E/StorageException: StorageException has occurred.
Object does not exist at location.
Code: -13010 HttpResult: 404
2020-02-15 17:02:26.946 28207-28735/com.example.practiceapplication E/StorageException: {"error": {"code": 404, "message": "Not Found. Could not get object", "status": "GET_OBJECT"}}
java.io.IOException: {"error": {"code": 404, "message": "Not Found. Could not get object", "status": "GET_OBJECT"}}
at com.google.firebase.storage.network.NetworkRequest.parseResponse(com.google.firebase:firebase-storage##19.1.1:433)
at com.google.firebase.storage.network.NetworkRequest.parseErrorResponse(com.google.firebase:firebase-storage##19.1.1:450)
at com.google.firebase.storage.network.NetworkRequest.processResponseStream(com.google.firebase:firebase-storage##19.1.1:441)
at com.google.firebase.storage.network.NetworkRequest.performRequest(com.google.firebase:firebase-storage##19.1.1:272)
at com.google.firebase.storage.network.NetworkRequest.performRequest(com.google.firebase:firebase-storage##19.1.1:286)
at com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(com.google.firebase:firebase-storage##19.1.1:70)
at com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(com.google.firebase:firebase-storage##19.1.1:62)
at com.google.firebase.storage.GetDownloadUrlTask.run(com.google.firebase:firebase-storage##19.1.1:76)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
2020-02-15 17:02:30.712 28207-28207/com.example.practiceapplication D/ImageAddress Size:: 1
It will be very helpful to me if anyone tells me the correction. Thank you
Your code suffers from using a mix of local variables and shared global variables whilst dealing with asynchronous code and for loops.
In the code above, you use the global variables imagePath, imageAddress and imageList inside a for loop which ultimately is the key cause of that Exception.
Code breakdown
When you click the upload button, your code performs the following steps with errors shown in bold:
Gets the first image's URI
Updates the value of imagePath to point at that image's upload location
Starts the upload of the first image
Logs "NOT HELLO 0"
Gets the second image's URI
Updates the value of imagePath to point at that image's upload location
Starts the upload of the second image
Logs "NOT HELLO 1"
Gets the third image's URI
Updates the value of imagePath to point at that image's upload location
Starts the upload of the third image
Logs "HELLO HELLO" and Toasts "Successfully Uploaded" (not actually finished yet)
[a few moments later]
The first image finishes uploading
The download URL of the third image is requested (which throws the StorageException)
The second image finishes uploading
The download URL of the third image is requested (which throws another StorageException)
The third image finishes uploading
The download URL of the third image is requested (and would work correctly)
Fixes
To fix this, the following things must be done:
Use a local variable copy of imageList
Use a local variable for storageReference
Use a local variable for imagePath, and rename to imageRef to accurately reflect it's type
Rename imageAddress to imageAddressList to accurately reflect it's type (recommended)
Remove the while() loop and use a for iterator instead
Disable the upload button immediately instead of at the end
Upload each image and fetch the download URLs in parallel, without conflicting with each other
Only display "Successfully uploaded" or "Upload failed" messages after the uploads have actually completed
Update imageAddressList only once, rather than asynchronously.
To be done:
Handle activity lifecycle changes
Tap into currentUploadTask and bind it to a view dialog/notification to show file upload progress
Update the UI once all the uploads are done
Updated code
Note: This was typed free-hand - expect a few typos.
upload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressDialog.setMessage("Uploading .... ");
progressDialog.show();
upload.setClickable(false); // disable upload button whilst uploading
final StorageReference storageReference = FirebaseStorage.getInstance().getReference().child("Pictures");
final List<Uri> clonedImageList = new ArrayList<>(imageList);
imageList.clear(); // empty old list?
int imageListSize = clonedImageList.size();
List<Task<Uri>> uploadedImageUrlTasks = new ArrayList<>(imageListSize);
for (Uri imageUri : clonedImageList) {
final String imageFilename = imageUri.getLastPathSegment();
Log.d("upload.onClick()", "Starting upload for \"" + imageFilename + "\"...");
StorageReference imageRef = storageReference.child(imageFilename); // Warning: potential for collisions/overwrite
UploadTask currentUploadTask = imageRef.putFile(imageUri);
Task<Uri> currentUrlTask = currentUploadTask
.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
#Override
public Task<Uri> then(#NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
if (!task.isSuccessful()) {
Log.d("upload.onClick()", "Upload for \"" + imageFilename + "\" failed!");
throw task.getException(); // rethrow any errors
}
Log.d("upload.onClick()", "Upload for \"" + imageFilename + "\" finished. Fetching download URL...");
return imageRef.getDownloadUrl();
}
})
.continueWithTask(new Continuation<Uri, Uri>() { // purely for logging to debug, recommended to remove
#Override
public Task<Uri> then(#NonNull Task<Uri> task) throws Exception {
if (!task.isSuccessful()) {
Log.d("upload.onClick()", "Could not get download URL for \"" + imageFilename + "\"!");
throw task.getException(); // rethrow any errors
}
Log.d("upload.onClick()", "Download URL for \"" + imageFilename + "\" is \"" + task.getResult() + "\".");
return task.getResult();
}
});
uploadedImageUrlTasks.add(currentUrlTask);
}
// At this point, all the files are being uploaded in parallel
// Each upload is tracked by the tasks in uploadedImageUrlTasks
Tasks.whenAllComplete(uploadedImageUrlTasks)
.addOnCompleteListener(new OnCompleteListener<List<Task<Uri>>>() {
#Override
public void onComplete(#NonNull List<Task<Uri>> tasks) {
int tasksCount = tasks.size();
List<Uri> failedUploads = new ArrayList<>();
imageAddressList.clear(); // empty old entries?
for (Task<Uri> task : tasks) {
if (task.isSuccessful()) {
successCount++;
Uri downloadUri = task.getResult();
imageAddressList.add(downloadUri.toString());
} else {
Uri imageUri = clonedImageList.get(tasks.indexOf(task));
failedUploads.add(imageUri);
Log.e("upload.onClick()", "Failed to upload/fetch URL for \"" + imageUri.getLastPathSegment() + "\" with exception", task.getException()); // log exception
}
}
progressDialog.dismiss(); // dismiss upload dialog
if (failedUploads.size() > 0) {
Toast.makeText(SignOutActivity.this, failedUploads.size() + "/" + tasksCount + " uploads failed.", Toast.LENGTH_LONG).show();
// TODO: Do something with list of failed uploads such as readd to the now empty upload list
imageList.addAll(failedUploads);
upload.setClickable(true);
} else {
Toast.makeText(SignOutActivity.this, "Successfully uploaded all " + tasksCount + " files.", Toast.LENGTH_LONG).show();
}
// TODO: Now that imageAddressList has been updated, update the UI - e.g tell recycler view to refresh
}
});
}
});
I am developing a feature to remove uploaded files. I can currently remove the reference within Cloud Firestore but I am unable to remove it from Storage. The code bellow should remove from Storage but it does not work
private void deleteImage(String id, final int position, String fileUrl) {
StorageReference storageRef = FirebaseStorage.getInstance().getReference("uploads");
// Create a reference to the file to delete
StorageReference fileRef = storageRef.child(fileUrl);
// Delete the file
fileRef.delete().addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// File deleted successfully
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
// Uh-oh, an error occurred!
}
});
}
The error message is "No auth token for request. StorageException has occurred. Object does not exist at location. HttpResult: 404". I thought that the problem was related with my fileUrl field but I'm not sure. The fileUrl is the download image url, for example, if I copy and paste the url on web browser I can see the image. Why is not working?
As #blackapps commented, the download url that I was using was not suitable as reference to a file so I changed the value of fileUrl parameter to "filename.file_extension". The code still the same, i only changed the parameter value.
I am developing android application where a user clicks image, it gets stored in firebase, cloud functions process this image and stores the output back in the firebase in the form of text file. In order to display the output in android, application keeps checking for output file if it exists or not. If yes, then it displays the output in the application. If no, I have to keep waiting for the file till it is available.
I'm unable to find any documentation for checking if any file is exists in Firebase or not. Any help or pointers will be helpful.
Thanks.
You can use getDownloadURL which returns a Promise, which can in turn be used to catch a "not found" error, or process the file if it exists. For example:
storageRef.child("file.png").getDownloadURL().then(onResolve, onReject);
function onResolve(foundURL) {
//stuff
}
function onReject(error){
//fill not found
console.log(error.code);
}
Updated
This is another simpler and cleaner solution.
storageRef.child("users/me/file.png").getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
// Got the download URL for 'users/me/profile.png'
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
// File not found
}
});
Firebase storage API is setup in a way that the user only request a file that exists.
Thus a non-existing file will have to be handled as an error:
You can check the documentation here
If the file doesn't exist, then it will raise StorageException; however the StorageException can be raised by different reasons, each of which has a unique error code defined as a constant of StorageException class.
If the file doesn't exist, then you will get Error code of StorageException.ERROR_OBJECT_NOT_FOUND
If you've a complete URL reference of the file, then you can check whether it exists or not by:
String url = "https://firebasestorage.googleapis.com/v0/b/******************"
StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(url);
ref.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
Log.d(LOG_TAG, "File exists");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
if (exception instanceof StorageException &&
((StorageException) exception).getErrorCode() == StorageException.ERROR_OBJECT_NOT_FOUND) {
Log.d(LOG_TAG, "File not exist");
}
}
});
The rest of error codes can be checked at here
my code for this
void getReferenceAndLoadNewBackground(String photoName) {
final StorageReference storageReference = FirebaseStorage.getInstance().getReference().child("Photos").child(photoName + ".png");
storageReference.getDownloadUrl()
.addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
loadBackground(storageReference);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
int errorCode = ((StorageException) exception).getErrorCode();
if (errorCode == StorageException.ERROR_OBJECT_NOT_FOUND) {
StorageReference storageReference2 = FirebaseStorage.getInstance().getReference().child("Photos").child("photo_1.png");
loadBackground(storageReference2);
}
}
});
}
This is how I am currently checking to see if the file Exists.
The this.auth.user$ pulls an observable that displays the current user's data from the FireStore database.
I store the FileStorage profile image reference in the FireStore database.
I then use the File Path in the user's data and use it for the FileStorage reference.
Now use the observable and check to see if the downloadURL length is less than or equal to 0.
If it is indeed greater than zero then the file exists; then go do something. Else do something else.
ngOnInit() {
this.userSubscription = this.auth.user$.subscribe((x) => {
console.log(x);
this.userUID = x.userId;
this.userPhotoRef = x.appPhotoRef;
this.userDownloadURL = x.appPhotoURL;
});
const storageRef = this.storage.ref(this.userPhotoRef);
console.log(storageRef);
if (storageRef.getDownloadURL.length <= 0) {
console.log('File Does not Exist');
} else {
console.log('File Exists');
}
}
I have the following code to make requests to a REST API, using Xamarin and an Android device:
public class ApiBase
{
HttpClient m_HttpClient;
public ApiBase(string baseAddress, string username, string password)
{
if (!baseAddress.EndsWith("/"))
{
baseAddress += "/";
}
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = DecompressionMethods.GZip;
}
m_HttpClient = new HttpClient(handler);
m_HttpClient.BaseAddress = new Uri(baseAddress);
var credentialsString = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password));
m_HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentialsString);
m_HttpClient.Timeout = new TimeSpan(0, 0, 30);
}
protected async Task<XElement> HttpGetAsync(string method)
{
try
{
HttpResponseMessage response = await m_HttpClient.GetAsync(method);
if (response.IsSuccessStatusCode)
{
// the request was successful, parse the returned string as xml and return the XElement
var xml = await response.Content.ReadAsAsync<XElement>();
return xml;
}
// the request was not successful -> return null
else
{
return null;
}
}
// some exception occured -> return null
catch (Exception)
{
return null;
}
}
}
If i have it like this, the first and the second call to HttpGetAsync work perfectly, but from the 3rd on the GetAsyncstalls and eventually throws an exception due to the timeout. I send these calls consecutively, there are not 2 of them running simultaneously since the results of the previous call are needed to decide the next call.
I tried using the app Packet Capture to look at the requests and responses to find out if i'm sending an incorrect request. But it looks like the request which fails in the end is never even sent.
Through experimentation i found out that everything works fine if don't set the AutomaticDecompression.
It also works fine if i change the HttpGetAsync method to this:
protected async Task<XElement> HttpGetAsync(string method)
{
try
{
// send the request
var response = await m_HttpClient.GetStringAsync(method);
if (string.IsNullOrEmpty(response))
{
return null;
}
var xml = XElement.Parse(response);
return xml;
}
// some exception occured -> return null
catch (Exception)
{
return null;
}
}
So basically using i'm m_HttpClient.GetStringAsync instead of m_HttpClient.GetAsync and then change the fluff around it to work with the different return type. If i do it like this, everything works without any problems.
Does anyone have an idea why GetAsync doesn't work properly (doesn't seem to send the 3rd request) with AutomaticDecompression, where as GetStringAsync works flawlessly?
There are bug reports about this exact issue:
https://bugzilla.xamarin.com/show_bug.cgi?id=21477
The bug is marked as RESOLVED FIXED and the recomended action is to update to the latest stable build. But there are other (newer) bugreports that indicate the same thing that are still open, ex:
https://bugzilla.xamarin.com/show_bug.cgi?id=34747
I made a workaround by implementing my own HttpHandler like so:
public class DecompressionHttpClientHandler : HttpClientHandler
{
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
var msg = await base.SendAsync(request, cancellationToken);
if (msg.Content.Headers.ContentEncoding.Contains("gzip"))
{
var compressedStream = await msg.Content.ReadAsStreamAsync();
var uncompresedStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Decompress);
msg.Content = new StreamContent(uncompresedStream);
}
return msg;
}
}
Note that the code above is just an example and not a final solution. For example the request will not be compressed and all headers will be striped from the result. But you get the idea.
I am using FirebaseAuth to login user through FB. Here is the code:
private FirebaseAuth mAuth;
private FirebaseAuth.AuthStateListener mAuthListener;
private CallbackManager mCallbackManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FacebookSdk.sdkInitialize(getApplicationContext());
// Initialize Firebase Auth
mAuth = FirebaseAuth.getInstance();
mAuthListener = firebaseAuth -> {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
}
if (user != null) {
Log.d(TAG, "User details : " + user.getDisplayName() + user.getEmail() + "\n" + user.getPhotoUrl() + "\n"
+ user.getUid() + "\n" + user.getToken(true) + "\n" + user.getProviderId());
}
};
}
The issue is that the photo in I get from using user.getPhotoUrl() is very small. I need a larger image and can't find a way to do that. Any help would be highly appreciated.
I have already tried this
Get larger facebook image through firebase login
but it's not working although they are for swift I don't think the API should differ.
It is not possible to obtain a profile picture from Firebase that is larger than the one provided by getPhotoUrl(). However, the Facebook graph makes it pretty simple to get a user's profile picture in any size you want, as long as you have the user's Facebook ID.
String facebookUserId = "";
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
ImageView profilePicture = (ImageView) findViewById(R.id.image_profile_picture);
// find the Facebook profile and get the user's id
for(UserInfo profile : user.getProviderData()) {
// check if the provider id matches "facebook.com"
if(FacebookAuthProvider.PROVIDER_ID.equals(profile.getProviderId())) {
facebookUserId = profile.getUid();
}
}
// construct the URL to the profile picture, with a custom height
// alternatively, use '?type=small|medium|large' instead of ?height=
String photoUrl = "https://graph.facebook.com/" + facebookUserId + "/picture?height=500";
// (optional) use Picasso to download and show to image
Picasso.with(this).load(photoUrl).into(profilePicture);
Two lines of code. FirebaseUser user = firebaseAuth.getCurrentUser();
String photoUrl = user.getPhotoUrl().toString();
photoUrl = photoUrl + "?height=500";
simply append "?height=500" at the end
If someone is looking for this but for Google account using FirebaseAuth. I have found a workaround for this. If you detail the picture URL:
https://lh4.googleusercontent.com/../.../.../.../s96-c/photo.jpg
The /s96-c/ specifies the image size (96x96 in this case)so you just need to replace that value with the desired size.
String url= FirebaseAuth.getInstance().getCurrentUser().getPhotoUrl();
url = url.replace("/s96-c/","/s300-c/");
You can analyze your photo URL to see if there is any other way to change its size.
As I said in the begining, this only works for Google accounts. Check #Mathias Brandt 's answer to get a custom facebook profile picture size.
EDIT 2020:
Thanks to Andres SK and #alextouzel for pointing this out. Photo URLs format have changed and now you can pass URL params to get different sizes of the picture. Check https://developers.google.com/people/image-sizing.
photoUrl = "https://graph.facebook.com/" + facebookId+ "/picture?height=500"
You can store this link to firebase database with user facebookId and use this in app.
Also you can change height as a parameter
Not for Android, but for iOS, but I thought it could be helpful for other people (I didn't find a iOS version of this question).
Based the provided answers I created a Swift 4.0 extension that adds a function urlForProfileImageFor(imageResolution:) to the Firebase User object. You can either ask for the standard thumbnail, a high resolution (I put this to 1024px but easily changed) or a custom resolution image. Enjoy:
extension User {
enum LoginType {
case anonymous
case email
case facebook
case google
case unknown
}
var loginType: LoginType {
if isAnonymous { return .anonymous }
for userInfo in providerData {
switch userInfo.providerID {
case FacebookAuthProviderID: return .facebook
case GoogleAuthProviderID : return .google
case EmailAuthProviderID : return .email
default : break
}
}
return .unknown
}
enum ImageResolution {
case thumbnail
case highres
case custom(size: UInt)
}
var facebookUserId : String? {
for userInfo in providerData {
switch userInfo.providerID {
case FacebookAuthProviderID: return userInfo.uid
default : break
}
}
return nil
}
func urlForProfileImageFor(imageResolution: ImageResolution) -> URL? {
switch imageResolution {
//for thumnail we just return the std photoUrl
case .thumbnail : return photoURL
//for high res we use a hardcoded value of 1024 pixels
case .highres : return urlForProfileImageFor(imageResolution:.custom(size: 1024))
//custom size is where the user specified its own value
case .custom(let size) :
switch loginType {
//for facebook we assemble the photoUrl based on the facebookUserId via the graph API
case .facebook :
guard let facebookUserId = facebookUserId else { return photoURL }
return URL(string: "https://graph.facebook.com/\(facebookUserId)/picture?height=\(size)")
//for google the trick is to replace the s96-c with our own requested size...
case .google :
guard var url = photoURL?.absoluteString else { return photoURL }
url = url.replacingOccurrences(of: "/s96-c/", with: "/s\(size)-c/")
return URL(string:url)
//all other providers we do not support anything special (yet) so return the standard photoURL
default : return photoURL
}
}
}
}
Note: From Graph API v8.0 you must provide the access token for every UserID request you do.
Hitting the graph API:
https://graph.facebook.com/<user_id>/picture?height=1000&access_token=<any_of_above_token>
With firebase:
FirebaseUser user = mAuth.getCurrentUser();
String photoUrl = user.getPhotoUrl() + "/picture?height=1000&access_token=" +
loginResult.getAccessToken().getToken();
You get the token from registerCallback just like this
LoginManager.getInstance().registerCallback(mCallbackManager, new FacebookCallback<LoginResult>() {
#Override
public void onSuccess(LoginResult loginResult) {
FirebaseUser user = mAuth.getCurrentUser();
String photoUrl = user.getPhotoUrl() + "/picture?height=1000&access_token=" + loginResult.getAccessToken().getToken();
}
#Override
public void onCancel() {
Log.d("Fb on Login", "facebook:onCancel");
}
#Override
public void onError(FacebookException error) {
Log.e("Fb on Login", "facebook:onError", error);
}
});
This is what documentation says:
Beginning October 24, 2020, an access token will be required for all
UID-based queries. If you query a UID and thus must include a token:
use a User access token for Facebook Login authenticated requests
use a Page access token for page-scoped requests
use an App access token for server-side requests
use a Client access token for mobile or web client-side requests
We recommend that you only use a Client token if you are unable to use
one of the other token types.
I use this code in a Second Activity, after having already logged in, for me the Token that is obtained in loginResult.getAccessToken().getToken(); It expires after a while, so researching I found this and it has served me
final String img = mAuthProvider.imgUsuario().toString(); // is = mAuth.getCurrentUser().getPhotoUrl().toString;
final String newToken = "?height=1000&access_token=" + AccessToken.getCurrentAccessToken().getToken();
Picasso.get().load(img + newToken).into("Image reference");
Check below response
final graphResponse = await http.get(
'https://graph.facebook.com/v2.12/me?fields=name,picture.width(800).height(800),first_name,last_name,email&access_token=${fbToken}');