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

Out of band assets #285

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

Out of band assets #285

wants to merge 12 commits into from

Conversation

HayesGordon
Copy link
Contributor

@HayesGordon HayesGordon commented Jan 9, 2025

This PR adds out of band asset support to Rive React Native.

  • Please take a look at the video below for an overview of the implementation and an exploration of the provided example.
  • Feedback is appreciated while this is a work in progress

rev file used in the video:
out_of_band.rev.zip

Video:
https://drive.google.com/file/d/1jDZY2MRLhLfV8EOpRz3rVcCRbTdkv6sI/view?usp=sharing

Edit:

  • Added android


val source = assetData?.getMap("source") ?: return false // Do not handle the asset.

CoroutineScope(Dispatchers.IO).launch {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ErikUggeldahl, your input here would be great on the best way to implement coroutine scope management (canceling it if the view is destroyed).


// Handle release mode (URL instead of asset id)
// Resource needs to be loaded in release mode
// https://github.com/facebook/react-native/issues/24963#issuecomment-532168307
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lancesnider would be great if we can give extra testing on this. It's handled differently between a dev and release build.

@@ -104,6 +108,10 @@ type Props = {
testID?: string;
alignment?: Alignment;
artboardName?: string;
/**
* @experimental This is an experimental feature and may change without a major version update (breaking change).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opinions on marking this as experimental?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's probably a good idea, especially since we're likely to have a major overhaul of the runtime relatively soon.

Copy link
Contributor

@lancesnider lancesnider left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great!

I really like the assetsHandled object. I think it'll be easier for users to grasp than assetLoader which requires some extra steps. If I'm understanding correctly the downside is that you can only define a referenced asset at load time, but not at runtime. I'm assuming that has to do with the limitation you mentioned in the video where we can't have a callback?

I love that you don't need to add assets to Bundled Assets in iOS, but I'm not sure I understand why.

// path: 'fonts', // only needed for Android assets
// },
},
'referenced-image-2929282': {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I'd prefer not to need the ID, I think using it by default is probably necessary, given the fact that it's possible to have multiple assets with the same name. I could see users being very confused when both fonts named Tomorrow display the same even though one of them is actually supposed to be a different weight.

I really like the idea of being able to override the default behavior with includeId. Do you have thoughts on that being a global attribute, rather than on a per-asset basis?

//
// The key of the map is the unique asset identifier (as exported in the Editor),
// which is a combination of the asset name and its unique identifier.
assetsHandled={{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's your thinking on the name assetsHandled? My first thought would be to call this referencedAssets to align it with the naming in the editor, but maybe that's too restricting.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also been toying with thinking of a different name for this.

@@ -104,6 +108,10 @@ type Props = {
testID?: string;
alignment?: Alignment;
artboardName?: string;
/**
* @experimental This is an experimental feature and may change without a major version update (breaking change).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's probably a good idea, especially since we're likely to have a major overhaul of the runtime relatively soon.

sourceAsset?: string;
sourceAssetId?: string;
path?: string;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth being explicit that exactly one of sourceUrl, sourceAsset and path is required?

type FileAssetSource =
  | {
      sourceUrl: string
      sourceAsset?: never
      path?: never
      sourceAssetId: string
    }
  | {
      sourceAsset: string
      sourceUrl?: never
      path?: never
      sourceAssetId: string
    }
  | {
      path: string
      sourceUrl?: never
      sourceAsset?: never
      sourceAssetId: string
    }
    ```

// path: 'images', // only needed for Android assets
// },
},
'referenced_audio-2929340': {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no scenario where the asset IDs might change, is there? For example, if the Rive file gets copied or if you reimport a PSD? That could be painful if you have a giant object with ID's included.

Adds clarity and formatting.
Couple more quick formatting changes.
@dan-phantom
Copy link

Great work and thank you so much for doing this ❤️

I'm not sure what is the "ID" tbh, how would we obtain it so we know what to set the key to, but if it it depends on the riv animation file I see an issue where some assets (fonts probably more common) is shared between multiple animation files. (we actually have this case #271 (comment))

Thank you

Copy link

@dskuza dskuza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be some more RN-specific comments left, but generally from the Swift side of things, things look great! I left some comments about things we previously discussed during a live walkthrough.

@@ -64,6 +64,10 @@ func createMalformedFileError() -> NSError {
return NSError(domain: RiveErrorDomain, code: RiveErrorCode.malformedFile.rawValue, userInfo: [NSLocalizedDescriptionKey: "Malformed Rive File", "name": "Malformed"])
}

func createAssetFileError(_ assetName: String) -> NSError {
return NSError(domain: RiveErrorDomain, code: RiveErrorCode.malformedFile.rawValue, userInfo: [NSLocalizedDescriptionKey: "Could not load Rive asset: \(assetName)", "name": "Malformed"])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicky, but I would maybe use .fileNotFound rather than malformed as the error code.

//
// The key of the map is the unique asset identifier (as exported in the Editor),
// which is a combination of the asset name and its unique identifier.
assetsHandled={{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also been toying with thinking of a different name for this.

}
}

private func processAssetBytes(_ data: Data, asset: RiveFileAsset, factory: RiveFactory) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth having another check here to validate that data.isEmpty == false.

}

private func splitFileNameAndExtension(fileName: String) -> (name: String?, ext: String?)? {
let components = fileName.split(separator: ".")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any documentation surrounding naming? This might not work as intended, for example, if a user has an asset name with a . in it.

return (name: String(components[0]), ext: String(components[1]))
}

private func loadResourceAsset(sourceAsset: String, path: String?, listener: @escaping (Data) -> Void) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed live, but path is currently ignored. This is understandable as a first-pass, with the expectation being that the file is already in the bundle's Resources directory. There may be a time where we wish to expand this API to support further paths.

@tslater
Copy link

tslater commented Jan 15, 2025

Love the ability to load assets different ways. As mentioned by others, we still really need the ability to replace an asset at run time. For our use case, we have an image that represents a topic. The user can shuffle this topic, so the current image gets replaced with another image. These images are dynamic (UGC). So we only know them at run time. In our case we'd want have two referenced images that we toggle between, allowing a the theoretically infinite number or images to be displayed as there is no hard limit to the number of shuffles.

If that was too long to follow: we have a need for the API to also allow us to replace an image after the animation loads any number of times, like the JS web rumtime supports (though I do think this API is simpler/cleaner so if there's a way to combine the ideas that would be awesome).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants