Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android - Newly created (empty) albums don't show up in getAlbums() #6

Closed
axis7818 opened this issue Jun 28, 2020 · 28 comments
Closed
Labels
help wanted Extra attention is needed

Comments

@axis7818
Copy link

Describe the bug

On android, the media.getAlbums() call returns an empty albums array. Then media.createAlbum({ name }) returns the error message: "Album already exists"

To Reproduce

Here is the code I am using (running on a physical android device). Perhaps I am using the API incorrectly? But I would expect this code to get an album and create it if it does not already exist.

class MyClass {
 
  private static readonly ALBUM_NAME: string = "My Album";

  private readonly media = new Media();

  public async getAlbum(retires: number = 3): Promise<MediaAlbum> {
    logger.debug(`Getting album`);
    const name = MyClass.ALBUM_NAME;
    const response = await this.media.getAlbums();
    const albums = response.albums;
    logger.debug(`Found ${albums.length} albums`);  // This will always show 0 albums returned
    const album = albums.find((a) => a.name === name);
    if (album !== undefined) {  // Since 0 albums are returned, this statement never triggers
      return album;
    }

    try {
      logger.debug(`Creating album ${name}`);
      await this.media.createAlbum({ name });  // A new album is created, but...
    } catch (e) {
      logger.error(`Failed to create album`);  // It always fails
      logger.error(JSON.stringify(e));  // with the message: "Album already exists"
    }

    // A retry is triggered, but each attempt behaves the same until it eventually gives up.
    if (retires > 0) {
      return this.getAlbum(retires - 1);
    } else {
      throw new Error(`MyClass.getAlbum: retries exceeded`);
    }
  }

}

Expected behavior

I would expect the code above to get the media album named "My Album" and create it if it does not already exist.

Desktop (please complete the following information):

  • OS: MacOS Catalina

Smartphone (please complete the following information):

  • Device: Galaxy J3 Orbit (Android)
  • OS: Android 8.0.0
  • Browser: Hosted in Capacitor version 2.0

Additional context
The same plugin and code is functioning properly on an iPhone.

@axis7818
Copy link
Author

I just tried on an emulator (Pixel 3 API 29) to see if a newer android version would work, and the app crashed during the first getAlbums() call. Here is the stack trace:

2020-06-28 12:05:30.588 13491-13491/com.worldspinner.portraits I/Capacitor/Console: File: http://localhost/App.js - Line 161953 - Msg: �[34mdebug�[39m: Getting album
2020-06-28 12:05:30.588 13491-13585/com.worldspinner.portraits V/Capacitor/Plugin: To native (Capacitor plugin): callbackId: 19483038, pluginId: MediaPlugin, methodName: getAlbums
2020-06-28 12:05:30.588 13491-13585/com.worldspinner.portraits V/Capacitor: callback: 19483038, pluginId: MediaPlugin, methodName: getAlbums, methodData: {}
2020-06-28 12:05:30.588 13491-13567/com.worldspinner.portraits D/DEBUG LOG: GET ALBUMS
2020-06-28 12:05:30.589 13491-13567/com.worldspinner.portraits D/DEBUG LOG: HAS PERMISSION
2020-06-28 12:05:30.590 13491-13567/com.worldspinner.portraits D/DEBUG LOG: ___GET ALBUMS
2020-06-28 12:05:30.595 13491-13571/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 1 (1) 17 6
2020-06-28 12:05:30.596 13491-13571/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 3 (3) 32 0
2020-06-28 12:05:30.608 13491-13525/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 1 (1) 7 4
2020-06-28 12:05:30.609 13491-13525/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 0 (0) 7 8
2020-06-28 12:05:30.622 13491-13571/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 1 (1) 17 6
2020-06-28 12:05:30.624 13491-13571/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 3 (3) 32 0
2020-06-28 12:05:30.642 13491-13525/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 1 (1) 7 4
2020-06-28 12:05:30.644 13491-13525/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 0 (0) 7 8
2020-06-28 12:05:30.646 13491-13571/com.worldspinner.portraits D/eglCodecCommon: setVertexArrayObject: set vao to 1 (1) 17 6
2020-06-28 12:05:30.646 13491-13567/com.worldspinner.portraits E/Capacitor: Serious error executing plugin
    java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:99)
        at com.getcapacitor.Bridge$1.run(Bridge.java:515)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.os.HandlerThread.run(HandlerThread.java:67)
     Caused by: java.lang.IllegalArgumentException: Invalid column DISTINCT bucket_display_name
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:170)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
        at android.content.ContentProviderProxy.query(ContentProviderNative.java:423)
        at android.content.ContentResolver.query(ContentResolver.java:944)
        at android.content.ContentResolver.query(ContentResolver.java:880)
        at android.content.ContentResolver.query(ContentResolver.java:836)
        at io.stewan.capacitor.media.MediaPlugin._getAlbums(MediaPlugin.java:62)
        at io.stewan.capacitor.media.MediaPlugin.getAlbums(MediaPlugin.java:46)
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:99) 
        at com.getcapacitor.Bridge$1.run(Bridge.java:515) 
        at android.os.Handler.handleCallback(Handler.java:883) 
        at android.os.Handler.dispatchMessage(Handler.java:100) 
        at android.os.Looper.loop(Looper.java:214) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 

@stewones
Copy link
Member

stewones commented Jul 1, 2020

hey @axis7818
can you check from latest ? 1.0.1
also check out changelog for a breaking change on java import

@axis7818
Copy link
Author

axis7818 commented Jul 2, 2020

Hi @stewwan, thanks for the reply!

I just updated to the new version (and modified MainActivity.java appropriately). Unfortunately, I am still seeing the same behavior described above.

@amakh
Copy link

amakh commented Jul 3, 2020

Hello,
I also have the same error using the latest version.
If you manually create the album tho, this error disappears (don't know if that can help you).

Here is the album directory path printed by the plugin when I create the album myself :
D/ENV LOG - ALBUM DIR: /storage/emulated/0/Pictures/Album Name.

But when I don't create the album myself, here is the path printed before the error occurs :
D/ENV STORAGE: /storage/emulated/0/Album Name

@stewones stewones reopened this Jul 3, 2020
@stewones
Copy link
Member

stewones commented Jul 3, 2020

are you able to reproduce it using this example?

@axis7818
Copy link
Author

axis7818 commented Jul 4, 2020

@stewwan, That example is working on my physical device, but I am seeing the same stack trace when running on the emulator. I think the difference in behavior might be related to this check.

I should have specified before, but the devices I am using are test devices that did not have any albums. If I create one manually, it is returned just like @amakh said. However, I cannot properly create albums with the plugin.

@amakh
Copy link

amakh commented Jul 8, 2020

Just to sum up, there are 2 different issues here:

  • On android 10+, the app crashes with the stack trace exposed by @axis7818 (Invalid column DISTINCT)
    I'm not into java but after some research, it looks like "DISTINCT" can't be use anymore so I tried to replace this line
    String[] projection = new String[]{"DISTINCT " + MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME};
    with
    String[] projection = new String[] {MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN }; (I think it should be optimised by a name grouping or something)
    And it did work on android 10 and still working on android 8.

  • The second issue is when the plugin tries to create an album Album already exists.
    I have no clue on this one but the fact that it does work when the user manually creates the album first. However I forgot to mention that even in that case, the plugin is creating a new album under the "More albums" section of the default photo app instead of using the manually created album.

@axis7818
Copy link
Author

@stewwan, is there any update on this? Or is there a suggested workaround?

@masterbd
Copy link

I'm also interested on an update regarding this. Thank's!

@amakh
Copy link

amakh commented Sep 29, 2020

Workaround for anyone trying to ignore Album already exists :
Replace this line with call.success();

This isn't the real issue, the problem is certainly coming from the fact that the creation path of the folder is different than the reading path's one but the little fix isn't affecting anything.

Just to sum up, there are 2 different issues here:

  • On android 10+, the app crashes with the stack trace exposed by @axis7818 (Invalid column DISTINCT)
    I'm not into java but after some research, it looks like "DISTINCT" can't be use anymore so I tried to replace this line
    String[] projection = new String[]{"DISTINCT " + MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME};
    with
    String[] projection = new String[] {MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN }; (I think it should be optimised by a name grouping or something)
    And it did work on android 10 and still working on android 8.
  • The second issue is when the plugin tries to create an album Album already exists.
    I have no clue on this one but the fact that it does work when the user manually creates the album first. However I forgot to mention that even in that case, the plugin is creating a new album under the "More albums" section of the default photo app instead of using the manually created album.

@createdbyconnor
Copy link

Hey @amakh - I just raised #10 before I saw your comment above.

I'm not too familiar with Java, but removing DISTINCT stops my app from crashing. I need to get an update out ASAP to fix this for my users... How have you done this, have you forked the project?

Thanks!

@amakh
Copy link

amakh commented Oct 1, 2020

Hi @createdbyconnor
Actually I directly changed the code inside my android project MediaPlugin.java.
Not the best way to do it, but it's a quick fix waiting for a definitive solution.
You just need to change the DISTINCT line as I mentioned here.

@dhwalin
Copy link

dhwalin commented Nov 26, 2020

I removed DISTINCT & it started working. But follow this to create my issue:

Go to Gallery & Delete album created Album. As per this example: "My Album"

Now, try to save the image, it will display the error that "Album is already exist" which is not.

& it will not work.

Now go to Gallery & make "My Album" manually & Save image code will start again but this time, it will also create another blank "My Album". So two "My Album" in gallery you will see.

Is there any workaround for this?

@amakh
Copy link

amakh commented Nov 26, 2020

@dhwalin Yes, check my older replies here

@calls9-mattwyld
Copy link

@stewones do you know when this issue might be sorted as this is a great plugin

@stewones
Copy link
Member

stewones commented Jun 8, 2022

can someone try with latest plugin v3 and let me know?

@crawft
Copy link

crawft commented Jun 16, 2022

I am looking at this now with v3 and see the same issue - it looks like all the albums get returned by getAlbums (and I can see none are the one I'm about to try and create). Then when I call createAlbum I get "Album already exists" error.

@merri-ment
Copy link

@stewones - i've encountered the same issue in v3

@kyoz
Copy link

kyoz commented Jul 14, 2022

On iOS, this plugin work great. But on Android, there are plenty problems.

For everyone who got stuck on this and are in rush, you can use capacitor-mediastore as a workaround for Android. I'v tested on almost all version of Android and it work just fine.

Changing native code is also good but it's also have some bugs inside is cause your app to crash on some Android version, sadly atm I don't have enough time to dig in this plugin code.

@stewones
Copy link
Member

got a new androidy now I can debug this issue, looking into it right now.

@stewones
Copy link
Member

stewones commented Sep 29, 2022

I'm having a hard time fixing this one.

as a workaround you can just use the Capacitor Filesystem API

 Filesystem.writeFile({
          recursive: true,
          data: base64 as string,
          path: `MY_ALBUM_NAME/my_image.png`,
          directory: Directory.Documents,
});

The result expected would be stored on "Documents/MY_ALBUM_NAME/my_image.png".
It's seen inside "Other Albums" on the Camera app, and users have access to those media through document apps also

Tested on API 29+

@stewones stewones added the help wanted Extra attention is needed label Sep 29, 2022
@nkalupahana
Copy link
Collaborator

nkalupahana commented Mar 17, 2023

@stewones or anyone else, is this still an issue? I'm unable to reproduce on my android device. You can try reproducing with the new example app; should be easy to use.

@amakh
Copy link

amakh commented Mar 24, 2023

@nkalupahana I upgraded to the latest version (capacitor 4 + this plugin) and Album already exists is still an issue.
However my dirty fix previously mentioned is still working:
Replace this line by call.success(); or call.resolve();

@nkalupahana
Copy link
Collaborator

@amakh Okay, I think I have an idea of what the problem is. When the plugin creates an "album", all that's doing is creating a folder in the filesystem. This won't show up in getAlbums, because there are no images in it (thanks, Android, that makes so much sense). However, when an image is put in that folder using the plugin, then the album shows up in all apps, and it'll come up in getAlbums.

Let me know if this logic makes sense, or if there's something you know that might dispute this. (You can look for these folders yourself in your filesystem.) If so, some solutions; let me know what might make the most sense:

  1. Modify getAlbums in some way to also scan for empty folders (I think this is the best idea, but I'm not sure how consistent it'll be)
  2. Allow createAlbum to not reject if the album folder to create is empty

@amakh
Copy link

amakh commented Mar 24, 2023

That is indeed what's happening because I remember trying to manually create an album (directly from the gallery app) and it wasn't in the same folder than the one created by the plugin.

I agree that solution 1 looks better but tbh I don't have a big scope on android development.

@nkalupahana
Copy link
Collaborator

Alright, I'm on it. Will try to do solution 1, and we'll see how it goes.

@nkalupahana nkalupahana changed the title Android - Existing MediaAlbum is not returned in getAlbums() Android - Newly created (empty) albums don't show up in getAlbums() Mar 24, 2023
@stewones
Copy link
Member

just to add context, another workaround would be something like this:

just ignore the getAlbums method, try to createAlbum every time with a catch and life goes on. tested on androids 11 and 13.

Screenshot 2023-03-24 at 20 40 17

@nkalupahana nkalupahana mentioned this issue May 8, 2023
4 tasks
@nkalupahana
Copy link
Collaborator

Solution one out in v5.0, testable with example app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests