Ender
Ender

Reputation: 127

Android Firebase multiple image upload

I am writing an app to test out firebase where a user can list a product with images. I'm having issues with the upload as although the pictures are stored, they are not linked to the product (images array not being passed?) and LeakCanary signals an outofmemory error. All help and input appreciated.

Here's my Product Model

@IgnoreExtraProperties
public class Product {

    public String uid;
    public String seller;
    public String name;
    public String description;
    public String city;
    public double price = 0.0;
    public List<Uri> images = new ArrayList<>();

    public Product () {

    }

    public Product(String uid, String seller, String name, String description, String city, double price, List<Uri> images) {
        this.uid = uid;
        this.seller = seller;
        this.name = name;
        this.description = description;
        this.city = city;
        this.price = price;
        this.images = images;
    }

    // [START post_to_map]
    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("seller", seller);
        result.put("name", name);
        result.put("description", description);
        result.put("city", city);
        result.put("price", price);
        result.put("images", images);

        return result;
    }
}

And here are is my AddProductActivity

public class AddProductActivity extends BaseActivity implements AddProductContract.View, View.OnClickListener {

    private AddProductContract.Presenter mPresenter;

    private Bitmap mBitmap;
    private byte[] mByteArray;
    private List<String> mPhotos;
    private Button mPublishBtn;
    private EditText mProductNameField;
    private EditText mProductDescriptionField;
    private EditText mProductPriceField;
    private DatabaseReference mFirebaseDatabase;
    private StorageReference mFirebaseStorage;
    private StorageTask mUploadTask;
    private List<Uri> uploadedImages = new ArrayList<>();
    private LinearLayout mLinearLayout;
    private ImageButton mImageButton;

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (data == null) {
            showEmptyImageError();
        } else {
            mPhotos = (List<String>) data.getSerializableExtra(GalleryActivity.PHOTOS);
        }

    }

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

        mFirebaseDatabase = FirebaseDatabase.getInstance().getReference();
        mFirebaseStorage = FirebaseStorage.getInstance().getReference();

        Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayShowTitleEnabled(false);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        mToolbar.setTitle(R.string.addItemTextview);

        mLinearLayout = (LinearLayout) findViewById(R.id.activity_add_product);
        mLinearLayout.setOnClickListener(this);

        mImageButton = (ImageButton) findViewById(R.id.imageButton);
        mImageButton.setOnClickListener(this);

        mPublishBtn = (Button) findViewById(R.id.publishItemBtn);
        mPublishBtn.setOnClickListener(this);

        mProductNameField = (EditText) findViewById(R.id.productNameField);
        mProductDescriptionField = (EditText) findViewById(R.id.productDescriptionField);
        mProductPriceField = (EditText) findViewById(R.id.priceField);

       // mPresenter = new AddProductPresenter(this);
    }

    @Override
    public void onClick(View view) {
        if ((view == mLinearLayout)) {
            hideKeyboard();
        } else if (view == mImageButton) {
            GalleryConfig config = new GalleryConfig.Build()
                    .limitPickPhoto(8)
                    .singlePhoto(false)
                    .hintOfPick("You can pick up to 8 pictures.")
                    .filterMimeTypes(new String[]{"image/*"})
                    .build();
            GalleryActivity.openActivity(AddProductActivity.this, 2, config);
        } else if (view == mPublishBtn) {
            final String name = mProductNameField.getText().toString();
            final String description = mProductDescriptionField.getText().toString();
            final double price = Double.parseDouble(mProductPriceField.getText().toString());
            setPublishingEnabled(false);
            showErrorToast(getResources().getString(R.string.publishing));
            final String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
            mFirebaseDatabase.child("users").child(userId).addListenerForSingleValueEvent(
                    new ValueEventListener() {
                        @Override
                        public void onDataChange(DataSnapshot dataSnapshot) {
                            User user = dataSnapshot.getValue(User.class);
                            if (user == null) {
                                showErrorToast(getResources().getString(R.string.empty_user));
                            } else {
                                publishProduct(userId, user.getUsername(), name, description,
                                        user.getCity(), price, mPhotos);
                            }
                            setPublishingEnabled(true);
                            finish();
                        }

                        @Override
                        public void onCancelled(DatabaseError databaseError) {
                            Log.w("AddProductActivity", "getUser:onCancelled", databaseError.toException());
                            setPublishingEnabled(true);
                        }
                    }
            );
        }
    }


    private void setPublishingEnabled(boolean enabled) {
        if (enabled) {
            mPublishBtn.setVisibility(View.VISIBLE);
        } else {
            mPublishBtn.setVisibility(View.GONE);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // handle arrow click here
        if (item.getItemId() == android.R.id.home) {
            finish();
        }

        return super.onOptionsItemSelected(item);
    }


    @Override
    public void showEmptyImageError() {
        Toast.makeText(getApplicationContext(), R.string.empty_image_error, Toast.LENGTH_SHORT).show();
    }

    private void publishProduct(String userId, String seller, String name, String description,
                                String city, double price, List<String> images) {

        for (String photo : images) {
            Uri file = Uri.fromFile(new File(photo));
            StorageReference photoRef = mFirebaseStorage.child("images/" + file.getLastPathSegment());
            mUploadTask = photoRef.putFile(file);

            mUploadTask.addOnFailureListener(exception -> Log.i("It didn't work", "double check"))
                    .addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                        @Override
                        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                            Uri downloadUrl = taskSnapshot.getDownloadUrl();
                            uploadedImages.add(downloadUrl);
                        }
                    });
        }


        String key = mFirebaseDatabase.child("products").push().getKey();
        Product product = new Product(userId, seller, name, description, city, price, uploadedImages);
        Map<String, Object> productValues = product.toMap();

        Map<String, Object> childUpdates = new HashMap<>();
        childUpdates.put("/products/" + key, productValues);
        childUpdates.put("/user-products/" + userId + "/" + key, productValues);

        mFirebaseDatabase.updateChildren(childUpdates);
    }

//
//    private List<Uri> uploadPhotos(List<String> input) {
//        images = new ArrayList<>();
//        Observable.just(input)
//                .map(this::doInBackground)
//                .subscribeOn(Schedulers.io())
//                .observeOn(AndroidSchedulers.mainThread())
//                .doOnSubscribe(this::onPreExecute)
//                .subscribe(this::onPostExecute);
//        return images;
//    }
//
//    private void onPreExecute() {
//        Log.i("Start time", "SET");
//        Toast.makeText(this, R.string.image_formatting_toast, Toast.LENGTH_SHORT).show();
//    }
//
//    private List<Uri> doInBackground(List<String> photos) {
//        for (String photo : mPhotos) {
//            Uri file = Uri.fromFile(new File(photo));
//            StorageReference photoRef = mFirebaseStorage.child("images/" + file.getLastPathSegment());
//            mUploadTask = photoRef.putFile(file);
//
//            mUploadTask.addOnFailureListener(exception -> Log.i("It didn't work", "double check"))
//                    .addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
//                @Override
//                public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
//                    Uri downloadUrl = taskSnapshot.getDownloadUrl();
//                    images.add(downloadUrl);
//                }
//            });
//        }
//        return  images;
//    }
//
//    private void onPostExecute(List<Uri> uriSet) {
//        Toast.makeText(this, R.string.product_visibility_toast, Toast.LENGTH_SHORT).show();
//        Log.i("End time", "SET");
//    }

}

Upvotes: 3

Views: 6842

Answers (1)

Valentino
Valentino

Reputation: 2135

Trying to understand your code flow, I can see one thing:

inside your publishProduct method you should put the code (to update the childeren in Firebase) into the addOnSuccessListener, like this:

private void publishProduct(String userId, String seller, String name, String description,
                            String city, double price, List<String> images) {

    String key = mFirebaseDatabase.child("products").push().getKey();

    for (String photo : images) {
        Uri file = Uri.fromFile(new File(photo));
        StorageReference photoRef = mFirebaseStorage.child("images/" + file.getLastPathSegment());
        mUploadTask = photoRef.putFile(file);

        mUploadTask.addOnFailureListener(exception -> Log.i("It didn't work", "double check"))
                .addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                    @Override
                    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                        Uri downloadUrl = taskSnapshot.getDownloadUrl();
                        uploadedImages.add(downloadUrl);

                        Product product = new Product(userId, seller, name, description, city, price, uploadedImages);
                        Map<String, Object> productValues = product.toMap();

                        Map<String, Object> childUpdates = new HashMap<>();
                        childUpdates.put("/products/" + key, productValues);
                        childUpdates.put("/user-products/" + userId + "/" + key, productValues);

                        mFirebaseDatabase.updateChildren(childUpdates);
                    }
                });
    }

}

And, if you want to know if the updateChildren operation is completed or not, add the onComplete and onFailure listeners, like this:

mFirebaseDatabase.updateChildren(childUpdates).addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {

            }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {

            }
});

Update

I think you can try to change your database structure, removing the list of images from the product's nodes, adding instead a node in your database that will store only the list of images associated with each product:

"/product-images/" + yourKey + "/" + imageKey

contains the list of his images. imageKey is different from one image the another (it could be for example the image name). yourKey could be the userId or the key associated with each product, it depends on how the database is structured. Then, you can try to use setValue instead of updateChildren into your OnSuccessListener, something like this:

mUploadTask.addOnFailureListener(exception -> Log.i("It didn't work", "double check"))
                    .addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                        @Override
                        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                            Uri downloadUrl = taskSnapshot.getDownloadUrl();            

                            mFirebaseDatabase.child("product-images").child(yourKey).child(imageKey).setValue(downloadUrl.toString());

                        }
                });

Hope this helps!

Upvotes: 5

Related Questions