Open MortadhaFadhlaoui opened 1 year ago
Hi @MortadhaFadhlaoui, could you please provide us with an easy to reproduce example. Either as a zip or a repository that includes a .riv that reproduces the issue.
This will help us look into your issue quicker, thanks!
Hi @MortadhaFadhlaoui, could you please provide us with an easy-to-reproduce example. Either as a zip or a repository that includes a .riv that reproduces the issue.
This will help us look into your issue quicker, thanks!
Thank you very much for checking out with me
here you find the repo.
@MortadhaFadhlaoui I see you're using the url
parameter to pass in the local resource URI for the expo asset.
Note that the url
parameter is intended for loading Rive assets over the internet. It seems though that the iOS runtime just so happens to play nicely with local resources as well, but our Android runtime does not. This is something that we could look into improving on Android's side, but I can't guarantee that this will be added or would be possible.
Note that for local resources we recommend using the resourceName
parameter, which requires you to add the resources to the Android and iOS folders (which is the current process for a normal React Native app). I'm not very familiar with Expo, so I'm not sure if there is an alternative way to add resources than how you're doing it.
But to get your Rive animation working you will need to add the resource to: android->app->src->main->res->raw->resource_name.riv
On Android you cannot use capital letters for these resource files.
On iOS you need to open the project in Xcode and drag in the file to any folder and be sure to link it to the application.
Then you can use the resourceName
parameter (note it does not require the .riv extension):
<Rive
style={{ backgroundColor: "red", flex: 1, margin: 20 }}
ref={riveRef}
resourceName="download_animation"
autoplay={false}
stateMachineName="downloadAnimation"
onStateChanged={(stateMachineName, stateName) => {
console.log(
"onStateChanged: ",
"stateMachineName: ",
stateMachineName,
"stateName: ",
stateName
);
}}
/>
Sounds good man thank you for explaining and helping me, I create a quick plugin that worked perfectly for Android to add assets as a resource under raw 🚀.
const { withDangerousMod } = require("@expo/config-plugins");
const fs = require("fs-extra");
const path = require("path");
const withCustomAssets = (config) => {
config = modifyResourcesAndroid(config);
return config;
};
function modifyResourcesAndroid(config) {
// Specify the source directory of your assets
const assetSourceDir = "assets/riv";
return withDangerousMod(config, [
"android",
async (config) => {
// Get the path to the Android project directory
const projectRoot = config.modRequest.projectRoot;
// Get the path to the Android resources directory
const resDir = path.join(
projectRoot,
"android",
"app",
"src",
"main",
"res"
);
// Create the 'raw' directory if it doesn't exist
const rawDir = path.join(resDir, "raw");
fs.ensureDirSync(rawDir);
// Get the path to the assets directory
const assetSourcePath = path.join(projectRoot, assetSourceDir);
// Retrieve all files in the assets directory
const assetFiles = await fs.readdir(assetSourcePath);
// Move each asset file to the resources 'raw' directory
for (const assetFile of assetFiles) {
const srcAssetPath = path.join(assetSourcePath, assetFile);
const destAssetPath = path.join(rawDir, assetFile);
fs.copyFileSync(srcAssetPath, destAssetPath);
}
// Update the Android resources XML file
const resourcesXmlPath = path.join(resDir, "values", "resources.xml");
let resourcesXml;
if (fs.existsSync(resourcesXmlPath)) {
resourcesXml = await fs.readFile(resourcesXmlPath, "utf-8");
} else {
resourcesXml = "<resources>\n</resources>";
}
const rawResourcesTags = assetFiles.map(
(assetFile) =>
`<string name="${assetFile.substring(
0,
assetFile.lastIndexOf(".")
)}">${assetFile}</string>`
);
const rawResourcesRegex = /<string name="[^"]+">[^<]+<\/string>/;
for (const rawResourcesTag of rawResourcesTags) {
if (resourcesXml.match(rawResourcesRegex)) {
resourcesXml = resourcesXml.replace(
rawResourcesRegex,
rawResourcesTag
);
} else {
const stringResourcesRegex = /<\/resources>/;
resourcesXml = resourcesXml.replace(
stringResourcesRegex,
`${rawResourcesTag}\n </resources>`
);
}
}
await fs.writeFile(resourcesXmlPath, resourcesXml);
return config;
},
]);
}
module.exports = withCustomAssets;
Awesome 👍
I'm going to rename the issue for better discoverability and leave it open until we have a better solution.
@MortadhaFadhlaoui if it's not too much trouble, do you mind updating the repository you made with your plugin you made here. So that someone can easily find a working example
Thank you again, repo updated
@MortadhaFadhlaoui i am trying to implement your solution and when referencing the plugin in the app.json file, it does not seem to want to recognize it.
update: I was able to get this to work, but I had to make the ContentAssetPlugin file .js and not .ts
Might be worth the Rive team's time to implement a official config plugin for expo, would be useful and bring rive to a larger audience (More and more people are using an Expo Managed flow)
https://docs.expo.dev/config-plugins/introduction/ For reference
Also might be worth copying over the resources to iOS as well since iOS loading the local resource seems slightly unexpected.
I have completed what @MortadhaFadhlaoui made by adding the iOS version here. Would be happy to implement it officially if interested
Hello everyone, I’ve made a plugin for both os’s https://github.com/Malaa-tech/expo-custom-assets
it used @MortadhaFadhlaoui code for anroid with small change to add multiple files
if this can be built in rive-react-native i would love to do so
@mzaien 's plugin works like magic. Thanks for the community !
@mzaien 's plugin works like magic. Thanks for the community !
It is my pleasure to help
Plugin works great @mzaien thank you! I wonder if it's possible to make this work with hot-reloading when developing with a development client? So that we don't have to rebuild the development client every time we make changes or add new .riv
files to the assets.
Plugin works great @mzaien thank you! I wonder if it's possible to make this work with hot-reloading when developing with a development client? So that we don't have to rebuild the development client every time we make changes or add new
.riv
files to the assets.
I would love to have this, but to my knowledge to run a config plugin you have to do a prebuild
if you know something that can help me to do it on hot reload I will try to implement it
Just to summarize the state of things: @mzaien has a plugin that can do this, but it requires a new native build every time you add a new .riv
asset, Is that correct?
Ideally we'd fix that issue, then also write that plugin into the rive RN package itself as a PR. Is that the work that needs to be done here?
I also want to add Rive animations in my Expo app. From what I've discovered, there are three methods to achieve this:
Detailed in the "Loading in Rive Files" article, this method involves:
Rive resourceName
Advantages: ✅ Works for Android ✅ Works for iOS
Disadvantages:
❌ Requires XCode and Android Studio
❌ Requires managing native projects and their ios
and android
directories yourself (bare workflow)
As demonstrated in the "Using Rive in Expo" tutorial by @jellyninjadev (sample repository), this approach involves:
.riv
files: config.resolver.assetExts.push("riv");
Rive url
with assets[0].localUri
Advantages: ✅ Works for iOS ✅ No manual resource copying ✅ Doesn't involve XCode or Android Studio ✅ Allows managed Expo workflow
Disadvantages:
❌ Rive url
does not work with local resources when building for Android (see comment)
This method involves using a config plugin named expo-custom-assets, which activates when running npx expo prebuild
. Steps include:
Rive resourceName
Advantages: ✅ Works for Android ✅ Works for iOS ✅ No manual resource copying ✅ Doesn't involve XCode or Android Studio ✅ Allows managed Expo workflow
Disadvantages: N/A
Is this the current state of the art, or is there something I've missed?
I finally have a solution. Hope this makes its way into Rive eventually...but it requires 2 steps:
1) edit your metro config so that it knows to bundle *.riv
files:
config.resolver["assetExts"] = [
...(config.resolver.assetExts || []),
// for rive animations
"riv",
];
2) Write a wrapper around the default Rive component in order to convert the required asset:
import React, { forwardRef } from 'react';
import Rive, { RiveRef } from "rive-react-native";
// @ts-ignore
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
type RiveComponentProps = Omit<React.ComponentProps<typeof Rive>, 'url' | 'resourceName'> & {
source: any
}
// detects if it is an http or file url
const stringIsUrl = (uri: string) => {
return uri.startsWith('http') || uri.startsWith('file');
}
export const RiveAnimation = forwardRef<RiveRef, RiveComponentProps>(
(props, ref) => {
const { source, ...riveProps } = props;
const resolved = resolveAssetSource(source);
const uriIsUrl = stringIsUrl(resolved.uri);
return (
<Rive
ref={ref}
{...riveProps}
resourceName={!uriIsUrl ? resolved.uri : undefined}
url={uriIsUrl ? resolved.uri : undefined}
/>
);
}
);
This works just like images in expo and react-native. In development, the file is served by metro, so it's super fast and dynamic to change images. In production, the asset is bundled and the appropriate local uri is handled for you. It just works. I just tested in both environments and it works great. SOOOO much better than every other solution I've seen here on github.
*note that this is edited. The original code was missing the resourceName prop for bundled Android apps and was only working in dev mode
Doesn't "Using expo-custom-assets" require you to do new native build to add new assets? That's what turned me off. It's also an extra package. I believe there's a patch to support Android option 2, but it requires sending all of the data over the bridge.
I do think the solution I shared here is the simplest and least invasive. Also most consistent with how people are used to working with image or video assets in react-native or expo. I'd like to see it canonized and incorporated if others agree.
@tslater does it work on android in production? This works for us in dev mode on Android but not in prod! Thanks a lot!
expo-custom-assets
Using expo-custom-assets
works in prod. I have released https://play.google.com/store/apps/details?id=com.welovecoding.app with it! 🌟
If you tell me your issues, I can maybe help you setting it up.
Nice that expo-custom-assets
is working in prod! If I understood well though, it does not provide hot reloading so we need to build a new app version every time we add an animation right?
I'm trying to combine the two approaches above to get both:
I'll revert back if I have any findings!
@anatoleblanc I forgot to update the issue, I did get it working on production with one more tweak, here is the final version that gives hotrealoding in dev and works in production:
import React, { forwardRef } from 'react';
import Rive, { RiveRef } from "rive-react-native";
// @ts-ignore
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import { Platform } from 'react-native';
type RiveComponentProps = Omit<React.ComponentProps<typeof Rive>, 'url' | 'resourceName'> & {
source: any
}
// detects if it is an http or file url
const stringIsUrl = (uri: string) => {
return uri.startsWith('http') || uri.startsWith('file');
}
export const RiveAnimation = forwardRef<RiveRef, RiveComponentProps>(
(props, ref) => {
const { source, ...riveProps } = props;
const resolved = resolveAssetSource(source);
const uriIsUrl = stringIsUrl(resolved.uri);
return (
<Rive
ref={ref}
{...riveProps}
resourceName={!uriIsUrl ? resolved.uri : undefined}
url={uriIsUrl ? resolved.uri : undefined}
/>
);
}
);
Not sure you have copy pasted all the code, you do not seem to use Platform here?
@anatoleblanc Sorry, I tried to blend a custom version with something more generic. Either way, I was wrong and the version I posted earlier is actually edited to be the latest. It is working for us both in production and in development for iOS and Android, with hot reloading on both platforms as well.
Can you try logging the value of resolved
on a production build to debug?
I wrote an article for the Expo blog on integrating Rive animations in React Native apps: https://expo.dev/blog/how-to-add-an-animated-splash-screen-with-expo-custom-assets
Description
I'm using rive-react-native in the expo dev build and it's working perfectly for IOS but I have a blank page on Android
Device & Versions (please complete the following information)
Additional context
The rive file output after using useAssets is
This is the source code:
app.config.js