Path to Assets?

I am not sure what you want to do? :sweat_smile: Do you want to read an asset file?

Hang on, real world getting in the way...:slight_smile:

1 Like

My ViewPDF extension requires an absolute path to display a pdf. This works just fine for files in, for example, the ASD, but if i have files in the assets, I have to copy them over in order to display them. I would like to be able to provide a path to display directly from assets. (Image is using companion, but you should get the idea)

1 Like

It might be better to internally use an InputStream to read the PDF rather than rely on a path in the file system. Assets are not stored in the file system in the traditional way--they're read from the AssetManager and come from inside of the APK file. Likewise, in some cases you might have a content:// URI which internally might map to a file on the file system but may not be accessible by your app unless you use the content URI. In both situations, Android provides an API to get an InputStream to read the content.

1 Like

Thank you Evan

What else do I need then to create a block I can plug in to my pathToPdf socket?

public void GetAsset(String filename) throws IOException {
     InputStream stream = form.openAsset(filename);

     }

I have tried a return but that throws an error in the above.

I guess it depends on what you're trying to do? Based on your blocks, it seems like you might be trying to render the PDF using a webviewer. In this case, I'd do something like:

import android.util.Base64;
import com.google.appinventor.components.runtime.util.IOUtils;

// ...

InputStream in;
try {
  in = form.openAsset(filename);
  byte[] contents = IOUtils.readStream(in);
  String content64 = Base64.encodeToString(contents, Base64.NO_WRAP | Base64.URL_SAFE);
  content64 = "data:application/pdf;base64," + content64;
  // Set web viewer URL to content64
} catch (IOException e) {
  // Report error via form.dispatchErrorOccurred(...)
} finally {
  IOUtils.closeQuietly(in);
}

The extension needs the pdf.... (which it converts to a series of images, which are built in to an html file to then display in the webviewer)

Without knowing what the PDF API that you are using looks like, it is hard to guess. Hopefully, it would take an InputStream as an input in some form and would just read the contents from the stream.

All seems like a sledgehammer to crack a nut :wink:

Back to my original question really.

and a follow up
Why can't the file component return the path to the asset so that it can be used? It can do it for a text file using // so why not for other files?

When compiled and run on an Android 12 device

image

these blocks return:

False
/android_asset

in companion

True
/storage/emulated/0/Android/data/edu.mit.appinventor.aicompanion3/files/assets

Where do you want to use it?
Any example?

Taifun

Like so

image

The reason it works is because the core components through the various utility APIs are able to consume file://android_asset/ URIs. The APIs handle opening the resource through the AssetManager class. /android_asset/ doesn't actually exist anywhere in the file system. It is an implementation detail of the Android WebView that we have adopted as well. Generally, I would say that if you call MakeFullPath, it is better to treat the result as opaque and not try to decompose it. On iOS, the value will be different to Android. Compiled apps will return different paths than the companion. This is the nature of making a system that both allows people to test their developments in real time and compile them for later use.

1 Like

but it doesn't work for a compiled app...

you will need an if statement like this

if running in companion 
then use /storage/emulated/0/Android/data/edu.mit.appinventor.aicompanion3/files/assets
else use

EDIT: actually for the companion it is a little bit more complicated if you want to get the correct result for all Android versions and if you want it to run in Kodular as well...

       if (Build.VERSION.SDK_INT >= 29) {
         completeFileName = ApplicationSpecificDirectory() + "/assets/" + fileName;
       } else if (context.getPackageName().contains("makeroid")) {
         completeFileName =  "/storage/emulated/0/Kodular/assets/" + fileName;                
       } else {
         completeFileName = ApplicationSpecificDirectory() + "/AppInventor/assets/" + fileName; 
       }

to get the ApplicationSpecificDirectory see here

Taifun

1 Like

Yes, exactly whatI said in post #7.

Thanks people.

I do not have a problem with accessing assets when in development/companion mode. The issue arises when an app is compiled, assets can no longer be accessed by a "file path".

@Taifun, in your File extension, you can return the files in assets, hence there must be a pathway to return these filenames ? I understand it would have to be done in code and returned as a block in an extension.

As an alternative, how about getting the inputStream of an asset and saving it to a tempFile, using that to load into the pdfViewer, then deleting said file after use ? Something like:

@SimpleFunction(description = "")
  public String GetAsset(String filename) throws IOException {

    String basename = filename.split(".")[0];
    String extn = "." + filename.split(".")[1];
    File file = File.createTempFile(basename,extn);
    filePath = file.getAbsolutePath();

    InputStream in = form.openAsset(filename);
    OutputStream out = new FileOutputStream(filePath);

    //...
    
    return filePath;
  }

Yeah that is a good idea.

hmmmm, I am getting:

Runtime error: length = 0; index=0

from:

@SimpleFunction(description = "returns asset from a temporary File")
  public String GetAsset(String filename) throws IOException {

    String basename = filename.split(".")[0];
    String extn = "." + filename.split(".")[1];
    File file = File.createTempFile(basename,extn);
    filePath = file.getAbsolutePath();

    InputStream in = form.openAsset(filename);
    int size = in.available();
    byte[] buffer = new byte[size];
    in.read(buffer);
    in.close();

    FileOutputStream out = new FileOutputStream(filePath);
    out.write(buffer);
    out.close();

    return filePath;
  }

Cracked it :slight_smile:

@SimpleFunction(description = "returns asset from a temporary File")
  public String GetAsset(String filename) throws IOException {
    
    File file = File.createTempFile("temp",".pdf");
    filePath = file.getAbsolutePath();

    InputStream in = context.getAssets().open(filename);
    int size = in.available();
    byte[] buffer = new byte[size];
    in.read(buffer);
    in.close();

    FileOutputStream out = new FileOutputStream(filePath);
    out.write(buffer);
    out.close();

    return filePath;
  }

This only works with compiled app

3 Likes
return Build.VERSION.SDK_INT > 28 ? this.context.getExternalFilesDir(null).getAbsolutePath() + "/assets/" : getExternalStoragePath() + "/AppInventor/assets/";
1 Like