1

I have been struggling to get download url for an image uploaded to firebase storage from my app. I want to send this url to firestore databse (not realtime database). I am setting itemImageUri to uri.toString() but in onCreate() method itemImageUrl is null and shows null in firestore. I cannot use CollectionRefernece addItemRef in onSuccess method as it gives error for all string variables: Variable is accessed from within inner class needs to be declared final.

public class AddItemActivity extends AppCompatActivity {

    public class AddItemActivity extends AppCompatActivity {
    public static final int PICK_IMAGE_REQUEST = 1;
    public static final String TAG = "Error!";
    public static final String UPLOAD_TAG = "Image uploaded";
    private Uri imageUri = null;
    private TextInputEditText textFieldTitle;
    private TextInputEditText textFieldDesc;
    private AutoCompleteTextView dropdownItemType;
    private TextInputEditText textFieldAddress;
    private TextInputEditText textFieldAvailability;
    private MaterialButton buttonSubmitItem;
    private MaterialButton buttonAddImage;
    private ImageView imageViewItem;
    private String itemImageUrl;
    private Bitmap bitmap;
    private Uri itemImageUri;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_item);

        imageViewItem = findViewById(R.id.imageView_camera);
        textFieldTitle = findViewById(R.id.textField_title);
        textFieldDesc = findViewById(R.id.textField_description);
        dropdownItemType = findViewById(R.id.dropdown_itemType);
        //Select type dropdown
        String[] itemTypes = new String[] {
                "Food",
                "Clothing",
                "Footwear"
        };
        ArrayAdapter<String> itemsDropdownAdpater = new ArrayAdapter<>(AddItemActivity.this, R.layout.dropdown_item_type, itemTypes);
        dropdownItemType.setAdapter(itemsDropdownAdpater);

        textFieldAddress = findViewById(R.id.textField_address);
        textFieldAvailability = findViewById(R.id.textField_availability);
        buttonAddImage = findViewById(R.id.button_addImage);
        buttonSubmitItem = findViewById(R.id.button_submitItem);

        buttonAddImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (ContextCompat.checkSelfPermission(AddItemActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(AddItemActivity.this, "Permission Denied", Toast.LENGTH_LONG).show();
                        ActivityCompat.requestPermissions(AddItemActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
                    } else {
                        choseImage();
                    }
                } else {
                    choseImage();
                }
            }
        });

        buttonSubmitItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                submitItem();
            }
        });
    }

    private void choseImage() {
        CropImage.activity()
                .setGuidelines(CropImageView.Guidelines.ON)
                .setAspectRatio(1, 1)
                .start(AddItemActivity.this);
    }

    private void submitItem() {
        String title = textFieldTitle.getText().toString();
        String desc = textFieldDesc.getText().toString();
        String type = dropdownItemType.getText().toString();
        String address = textFieldAddress.getText().toString();
        String availability = textFieldAvailability.getText().toString();
        
        if (title.trim().isEmpty() ||
                desc.trim().isEmpty() ||
                type.trim().isEmpty() ||
                availability.trim().isEmpty()) {
            Toast.makeText(this, "Fields cant be empty", Toast.LENGTH_SHORT).show();
            return;
        }
       
        handleUpload(bitmap);
        CollectionReference addItemRef = FirebaseFirestore.getInstance()
                .collection("ItemList");
        addItemRef.add(new ItemListModel(title, desc, type, address, availability, itemImageUrl));
        Toast.makeText(this, "Item added", Toast.LENGTH_SHORT).show();
        finish();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
            CropImage.ActivityResult result = CropImage.getActivityResult(data);
            if (resultCode == RESULT_OK) {
                imageUri = result.getUri();
                imageViewItem.setImageURI(imageUri);
                imageViewItem.invalidate();
                BitmapDrawable drawable = (BitmapDrawable) imageViewItem.getDrawable();
                bitmap = drawable.getBitmap();
            } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
                Exception error = result.getError();
            }
        }
    }

    private void handleUpload(Bitmap bitmap) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        final StorageReference reference = FirebaseStorage.getInstance().getReference()
                .child("itemImages")
                .child(System.currentTimeMillis() + ".jpeg");

        reference.putBytes(baos.toByteArray())
                .addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                    @Override
                    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                        getItemImageUrl(reference);
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Log.e(TAG, "onFailure: ", e.getCause());
                    }
                });
    }

    private void getItemImageUrl(StorageReference reference) {
        reference.getDownloadUrl()
                .addOnSuccessListener(new OnSuccessListener<Uri>() {
                    @Override
                    public void onSuccess(Uri uri) {
                        itemImageUrl = uri.toString();
                    }
                });

    }
}
Taha
  • 33
  • 1
  • 6
  • Does this answer your question? [After upload a file in Android Firebase Storage how get the file download Url? getDownloadUrl() not working](https://stackoverflow.com/questions/50570893/after-upload-a-file-in-android-firebase-storage-how-get-the-file-download-url-g) – Walid Jul 02 '20 at 15:15
  • @Walid No, this doesnt explain how I would send downloadUrl to firestore database, in my code above I am getting the uri using getDownloadUrl but unable to set ```String itemImageUrl``` to this value. – Taha Jul 02 '20 at 15:22
  • So as I understood, you need to upload a file to storage, then get the download link and then upload your object to the database, right ? – Walid Jul 02 '20 at 15:32
  • Yes! How can I do this? – Taha Jul 02 '20 at 16:05

3 Answers3

1

Determining the download URL requires a call to the servers, which means it happens asynchronously. For this reason, any code that needs the download URL needs to be inside the onSuccess of getDownloadURL() or be called from there.

So:

private void getItemImageUrl(StorageReference reference) {
    reference.getDownloadUrl()
            .addOnSuccessListener(new OnSuccessListener<Uri>() {
                @Override
                public void onSuccess(Uri uri) {
                    itemImageUrl = uri.toString();

                    ... here you can write itemImageUrl to the database
                }
            });

}

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • If I cant use addItemRef.add() in this async task, how would I set itemImageUrl to database? – Taha Jul 02 '20 at 16:00
  • I'm not sure what you mean by that. You can either create a new document, or add to the existing document inside the `onSuccess`. What problem/error do you get when you try that? – Frank van Puffelen Jul 02 '20 at 16:21
  • Thanks! I called a method from reference.getDownloadUrl.addOnSuccessListener() that creates a firestore document . ```private void getItemImageUrl(final StorageReference reference) { reference.getDownloadUrl() .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Uri uri) { String downloadUrl = uri.toString(); addItem(downloadUrl); } }); } ``` – Taha Jul 02 '20 at 16:39
0

In NodeJS, to get a link for a document stored in Firebase Storage.

const options = {
        version: 'v2', // defaults to 'v2' if missing.
        action: 'read',
        expires: Date.now() + 1000 * 60 * 60, // one hour
    };

let url = storage
    .bucket(bucketname)
    .file(filename)
    .getSignedUrl(options);
Varun Rayen
  • 346
  • 2
  • 7
0

First upload the file to the Storage, then call your upload method to the database on the addOnCompleteListener :

final StorageReference fileReference = storageRef.child(System.currentTimeMillis()
                + "." + getFileExtension(uriList.get(i)));
        uploadTask = fileReference.putFile(uriList.get(i));

        uploadTask.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
            @Override
            public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
                if (!task.isSuccessful() && task.getException() != null) {
                    throw task.getException();
                }

                return fileReference.getDownloadUrl();
            }
        }).addOnCompleteListener(new OnCompleteListener<Uri>() {
            @Override
            public void onComplete(@NonNull Task<Uri> task) {
                if (task.isSuccessful()) {
                    Uri downloadUri = task.getResult();
                    if(downloadUri != null) {
                        itemImageUrl = downloadUri.toString()
                        
                        // create your object
                        // upload your object to the database here
                    }

                }
            }
        });
Walid
  • 700
  • 2
  • 10
  • 29
  • I have mentioned creating a Collectionreference int this async task gives error: Variable is accessed from within inner class needs to be declared final. – Taha Jul 02 '20 at 16:04
  • you can make it a global variable, or create a method for your upload task and call it there. – Walid Jul 02 '20 at 16:07
  • Thanks alot! been struggling for 2 days with such an obvious solution. I called a method from ```reference.getDownloadUrl.addOnSuccessListener()``` that creates a firestore document – Taha Jul 02 '20 at 16:37