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

OOB Asset Support #278

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions android/src/main/java/com/rivereactnative/RNAssetLoader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.rivereactnative

import android.content.Context
import android.content.res.Resources
import android.util.Log
import app.rive.runtime.kotlin.core.BytesRequest
import app.rive.runtime.kotlin.core.FileAsset
import app.rive.runtime.kotlin.core.FileAssetLoader
import com.android.volley.toolbox.Volley
import java.io.IOException

class RNAssetLoader(private val context: Context, private val urlAssets: MutableMap<String, String>?, private val bundledAssets: MutableMap<String, String>?) : FileAssetLoader() {
private val queue = Volley.newRequestQueue(context)
private val tag = "RNAssetLoader"

override fun loadContents(asset: FileAsset, inBandBytes: ByteArray): Boolean {
val url = urlAssets?.get(asset.name)
val name = bundledAssets?.get(asset.name)

if (url != null) {
val request = BytesRequest(
url,
{ bytes -> asset.decode(bytes) },
{
Log.e(tag, "failed to load url asset from $url")
it.printStackTrace()
}
)

queue.add(request)

return true
} else if (name != null) {
val assetId = context.resources.getIdentifier(name, "raw", context.packageName)

return try {
context.resources.openRawResource(assetId).use { inputStream ->
asset.decode(inputStream.readBytes())
true
}
} catch (e: IOException) {
Log.e(tag, "Error loading bundled asset: ${asset.name}", e)
false
} catch (e: Resources.NotFoundException) {
Log.e(tag, "Bundled asset not found: ${asset.name}", e)
false
}
}

return false
}
}
15 changes: 15 additions & 0 deletions android/src/main/java/com/rivereactnative/RiveReactNativeView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
private var exceptionManager: ExceptionsManagerModule? = null
private var isUserHandlingErrors = false
private var willDispose = false;
private var urlAssets: MutableMap<String, String>? = null
private var bundledAssets: MutableMap<String, String>? = null

enum class Events(private val mName: String) {
PLAY("onPlay"),
Expand Down Expand Up @@ -372,6 +374,10 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
} ?: run {
if (resId != -1) {
try {
if (this.urlAssets != null || this.bundledAssets != null) {
riveAnimationView.setAssetLoader(RNAssetLoader(context, this.urlAssets, this.bundledAssets))
}

riveAnimationView.setRiveResource(
resId,
fit = this.fit,
Expand Down Expand Up @@ -401,6 +407,9 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
}
} ?: run {
if (resId != -1) {
if (this.urlAssets != null || this.bundledAssets != null) {
riveAnimationView.setAssetLoader(RNAssetLoader(context, this.urlAssets, this.bundledAssets))
}
try {
riveAnimationView.setRiveResource(
resId,
Expand Down Expand Up @@ -477,6 +486,12 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
this.isUserHandlingErrors = isUserHandlingErrors
}

fun setHandledAssets(urlAssets: MutableMap<String, String>, bundledAssets: MutableMap<String, String>) {
this.urlAssets = urlAssets
this.bundledAssets = bundledAssets
shouldBeReloaded = true
}

fun fireState(stateMachineName: String, inputName: String) {
try {
riveAnimationView.fireState(stateMachineName, inputName)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.rivereactnative

import android.util.Log
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.common.MapBuilder
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
Expand Down Expand Up @@ -207,4 +209,30 @@ class RiveReactNativeViewManager : SimpleViewManager<RiveReactNativeView>() {
fun setIsUserHandlingErrors(view: RiveReactNativeView, isUserHandlingErrors: Boolean) {
view.setIsUserHandlingErrors(isUserHandlingErrors)
}

@ReactProp(name = "initialAssetsHandled")
fun setInitialAssetsHandled(view: RiveReactNativeView, initialAssetsHandled: ReadableMap?) {
val assetUrlMap = mutableMapOf<String, String>()
val assetNameMap = mutableMapOf<String, String>()

if (initialAssetsHandled != null) {
val iterator = initialAssetsHandled.keySetIterator()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
val configMap = initialAssetsHandled.getMap(key)

val assetUrl = configMap?.getString("assetUrl")
if (assetUrl != null) {
assetUrlMap[key] = assetUrl
}

val assetName = configMap?.getString("bundledAssetName")
if (assetName != null) {
assetNameMap[key] = assetName
}
}

view.setHandledAssets(assetUrlMap, assetNameMap)
}
}
}
6 changes: 6 additions & 0 deletions docs/rive-react-native-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ Specifies which stateMachine should be played when `autoplay` is set to `true`.
default: `undefined`
type: `string`

### initialAssetsHandled _(optional)_

Specifies a url or bundled asset name for handling out of band assets.
default: `undefined`
type: `HandledAssetsConfig`

### testID _(optional)_

Specifies testID which could be handy in tests.
Expand Down
16 changes: 16 additions & 0 deletions docs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ export enum Direction {
}
```

## HandledAssetsConfig

```ts
export type HandledAssetsConfig = {
[name: string]:
| {
assetUrl: string;
}
| {
// On iOS, this must include the file extension.
// On Android, this must exclude the file extension. The asset must be placed in the `android/app/src/main/res/raw` directory
bundledAssetName: string;
};
}
```

## RNRiveError

```ts
Expand Down
Binary file not shown.
Binary file added example/android/app/src/main/res/raw/cat_wall.riv
Binary file not shown.
Binary file added example/ios/Assets/cat_994454.webp
Binary file not shown.
Binary file added example/ios/Assets/cat_wall.riv
Binary file not shown.
8 changes: 8 additions & 0 deletions example/ios/RiveReactNativeExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
9D879D0D265BF2A400D01424 /* ui_swipe_left_to_delete.riv in Resources */ = {isa = PBXBuildFile; fileRef = 9D879D0C265BF2A400D01424 /* ui_swipe_left_to_delete.riv */; };
9DBF1CC52684937E0008391A /* v6_file.riv in Resources */ = {isa = PBXBuildFile; fileRef = 9DBF1CC42684937E0008391A /* v6_file.riv */; };
9EBE42F22CDD459200014668 /* layout_test.riv in Resources */ = {isa = PBXBuildFile; fileRef = 9EBE42F12CDD459200014668 /* layout_test.riv */; };
B8ECFD652CF63B3F0038B1BF /* cat_wall.riv in Resources */ = {isa = PBXBuildFile; fileRef = B8ECFD642CF63B3F0038B1BF /* cat_wall.riv */; };
B8ECFD662CF63B3F0038B1BF /* cat_994454.webp in Resources */ = {isa = PBXBuildFile; fileRef = B8ECFD632CF63B3F0038B1BF /* cat_994454.webp */; };
C3C07472283BE07300E8EB33 /* hero_editor.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3C07471283BE07300E8EB33 /* hero_editor.riv */; };
E554409B2A79DC8100D550DE /* hello_world_text.riv in Resources */ = {isa = PBXBuildFile; fileRef = E554409A2A79DC8100D550DE /* hello_world_text.riv */; };
E5637D7A292BD27F000CBC1E /* skills_listener.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5637D79292BD26D000CBC1E /* skills_listener.riv */; };
Expand Down Expand Up @@ -92,6 +94,8 @@
9EBE42F12CDD459200014668 /* layout_test.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = layout_test.riv; path = Assets/layout_test.riv; sourceTree = "<group>"; };
B4756AFE35A048338799A25D /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
B67E7AEF25D549FDAA873610 /* FontAwesome5_Brands.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Brands.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"; sourceTree = "<group>"; };
B8ECFD632CF63B3F0038B1BF /* cat_994454.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = cat_994454.webp; sourceTree = "<group>"; };
B8ECFD642CF63B3F0038B1BF /* cat_wall.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = cat_wall.riv; sourceTree = "<group>"; };
C3C07471283BE07300E8EB33 /* hero_editor.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = hero_editor.riv; sourceTree = "<group>"; };
C601F9E5E0B14241BD92A5CC /* Fontisto.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Fontisto.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf"; sourceTree = "<group>"; };
CA3E69C5B9553B26FBA2DF04 /* libPods-RiveReactNativeExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RiveReactNativeExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -256,6 +260,8 @@
9D4FE60A26493B460098BF6A /* Assets */ = {
isa = PBXGroup;
children = (
B8ECFD632CF63B3F0038B1BF /* cat_994454.webp */,
B8ECFD642CF63B3F0038B1BF /* cat_wall.riv */,
F8E2789D2CDCD6A200FAA8EF /* layouts_demo.riv */,
C3C07471283BE07300E8EB33 /* hero_editor.riv */,
E554409A2A79DC8100D550DE /* hello_world_text.riv */,
Expand Down Expand Up @@ -421,6 +427,8 @@
9D879D00265642BA00D01424 /* truck_v7.riv in Resources */,
E5FC4EAA2ABB975100D98158 /* rating.riv in Resources */,
042FD22726B81BD1004556A3 /* constrained.riv in Resources */,
B8ECFD652CF63B3F0038B1BF /* cat_wall.riv in Resources */,
B8ECFD662CF63B3F0038B1BF /* cat_994454.webp in Resources */,
9D879D0B26578A5E00D01424 /* artboard_animations.riv in Resources */,
F8AA4CA42C0F3FDB00C1A5FF /* runtime_nested_inputs.riv in Resources */,
E5637D7A292BD27F000CBC1E /* skills_listener.riv in Resources */,
Expand Down
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import MeshExample from './MeshExample';
import DynamicText from './DynamicText';
import NestedInputs from './NestedInputs';
import Events from './Events';
import OutOfBandAssets from './OutOfBandAssets';

import {
RiveRenderer,
Expand Down Expand Up @@ -65,6 +66,7 @@ function App() {
<Stack.Screen name="Events" component={Events} />
<Stack.Screen name="NestedInputs" component={NestedInputs} />
<Stack.Screen name="DynamicText" component={DynamicText} />
<Stack.Screen name="OutOfBandAssets" component={OutOfBandAssets} />
<Stack.Screen
name="MultipleArtboards"
component={MultipleArtboards}
Expand Down
8 changes: 8 additions & 0 deletions example/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ export default function Home({ navigation }) {
Dynamic Text
</Button>

<Button
mode="contained"
onPress={() => navigation.navigate('OutOfBandAssets')}
style={styles.buttonStyle}
>
Out of Band Assets
</Button>

<Button
mode="contained"
onPress={() => navigation.navigate('ErrorNotHandled')}
Expand Down
74 changes: 74 additions & 0 deletions example/src/OutOfBandAssets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react';
import {
Platform,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
} from 'react-native';
import Rive, { Fit } from 'rive-react-native';

export default function StateMachine() {
return (
<SafeAreaView style={styles.safeAreaViewContainer}>
<ScrollView contentContainerStyle={styles.container}>
<Rive
autoplay={true}
fit={Fit.Cover}
style={styles.box}
stateMachineName="State Machine 2"
artboardName="Picture 1"
initialAssetsHandled={{
'cat.webp': {
// assetUrl: 'https://www.gstatic.com/webp/gallery/1.webp',
bundledAssetName:
Platform.OS === 'ios' ? 'cat_994454.webp' : 'cat_994454',
},
}}
resourceName={'cat_wall'}
/>
<Text>
Load in an external asset from a URL or bundled asset on the native
platform
</Text>
</ScrollView>
</SafeAreaView>
);
}

const styles = StyleSheet.create({
safeAreaViewContainer: {
flex: 1,
},
container: {
flexGrow: 1,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 150,
padding: 10,
},
wrapper: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
marginBottom: 20,
},
box: {
width: '100%',
height: 500,
marginVertical: 20,
},
picker: {
width: '100%',
height: 50,
},
radioButtonsWrapper: {
flexDirection: 'row',
},
radioButtonWrapper: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 16,
},
});
Loading