Skip to content

Commit c810553

Browse files
mfazekasclaude
andauthored
feat: onError property for RiveView (#25)
* feat: onError property for RiveView * detectErrorType * fix: error types onError callback * fix: improve error handling and fix import cycle - Fix import cycle by extracting NitroRiveView to separate file - Make error types human-readable in logs (show enum names instead of numbers) - Improve iOS error detection using RiveErrorCode enum values instead of string comparisons - Rename NativeRiveError to NitroRiveError for consistency with Nitro Modules naming - Make detectErrorType return tuple with both error type and message - Add NitroRiveError.fileNotFound case for asset loading errors - Remove non-existent error types (TextRunNotFoundError, IncorrectAnimationName) - Map instanceNotFound errors to DataBindingError type 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add missing error types and correct error mappings - Add IncorrectAnimationName and IncorrectStateMachineInputName error types - Fix iOS to map noStateMachineInputFound to IncorrectStateMachineInputName instead of IncorrectAnimationName - Fix Android to use correct error types instead of UNKNOWN - Regenerate Nitrogen bindings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: simplify error types and add tuple return to Android - Rename DataBindingError to ViewModelInstanceNotFound - Remove IncorrectAnimationName (map to Unknown) - Remove TextRunNotFoundError (map to Unknown) - Make Android detectErrorType return Pair<RiveErrorType, String> for consistency with iOS - Update both platforms to use cleaner error messages - Regenerate Nitrogen bindings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: remove deprecated deep import for resolveAsset * chore: fix the lib to build on ios and droid * nitrogen --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3e21607 commit c810553

40 files changed

Lines changed: 1048 additions & 126 deletions

android/src/main/java/com/margelo/nitro/rive/HybridRiveView.kt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.rive.RiveReactNativeView
1010
import com.rive.ViewConfiguration
1111
import app.rive.runtime.kotlin.core.Fit as RiveFit
1212
import app.rive.runtime.kotlin.core.Alignment as RiveAlignment
13+
import app.rive.runtime.kotlin.core.errors.*
1314
import kotlinx.coroutines.Dispatchers
1415
import kotlinx.coroutines.withContext
1516

@@ -89,6 +90,7 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
8990
dataBindingChanged = true
9091
}
9192
}
93+
override var onError: (error: RiveError) -> Unit = {}
9294
//endregion
9395

9496
//region View Methods
@@ -227,12 +229,33 @@ class HybridRiveView(val context: ThemedReactContext) : HybridRiveViewSpec() {
227229
}
228230
}
229231

232+
private fun detectErrorType(exception: Exception): Pair<RiveErrorType, String> {
233+
val message = exception.message ?: exception.toString()
234+
val type = when (exception) {
235+
is ArtboardException -> RiveErrorType.INCORRECTARTBOARDNAME
236+
is StateMachineException -> RiveErrorType.INCORRECTSTATEMACHINENAME
237+
is AnimationException -> RiveErrorType.UNKNOWN
238+
is MalformedFileException -> RiveErrorType.MALFORMEDFILE
239+
is StateMachineInputException -> RiveErrorType.INCORRECTSTATEMACHINEINPUTNAME
240+
is TextValueRunException -> RiveErrorType.UNKNOWN
241+
is ViewModelException -> RiveErrorType.VIEWMODELINSTANCENOTFOUND
242+
else -> RiveErrorType.UNKNOWN
243+
}
244+
return Pair(type, message)
245+
}
246+
230247
fun logged(tag: String, note: String? = null, fn: () -> Unit) {
231248
try {
232249
fn()
233250
} catch (e: Exception) {
234-
// TODO add onError callback
235-
Log.e("[RIVE]", "$tag ${note ?: ""} $e")
251+
val (errorType, errorDescription) = detectErrorType(e)
252+
val noteString = note?.let { " $it" } ?: ""
253+
val errorMessage = "[RIVE] $tag$noteString $errorDescription"
254+
val riveError = RiveError(
255+
type = errorType,
256+
message = errorMessage
257+
)
258+
onError(riveError)
236259
}
237260
}
238261
//endregion

ios/HybridRiveView.swift

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class HybridRiveView: HybridRiveViewSpec {
5656
var alignment: Alignment?
5757
var fit: Fit?
5858
var layoutScaleFactor: Double?
59+
var onError: (RiveError) -> Void = { _ in }
5960

6061
func awaitViewReady() throws -> Promise<Bool> {
6162
return Promise.async { [self] in
@@ -141,7 +142,7 @@ class HybridRiveView: HybridRiveViewSpec {
141142
)
142143

143144
let riveView = try getRiveView()
144-
riveView.configure(
145+
try riveView.configure(
145146
config, dataBindingChanged: dataBindingChanged, reload: needsReload,
146147
initialUpdate: initialUpdate)
147148
needsReload = false
@@ -187,3 +188,51 @@ class HybridRiveView: HybridRiveViewSpec {
187188
}
188189
}
189190
}
191+
192+
extension HybridRiveView {
193+
func logged(tag: String, note: String? = nil, _ fn: () throws -> Void) {
194+
do {
195+
return try fn()
196+
} catch (let e) {
197+
let (errorType, errorDescription) = detectErrorType(e)
198+
let noteString = note.map { " \($0)" } ?? ""
199+
let errorMessage = "[RIVE] \(tag)\(noteString) \(errorDescription)"
200+
201+
let riveError = RiveError(
202+
message: errorMessage,
203+
type: errorType
204+
)
205+
onError(riveError)
206+
}
207+
}
208+
209+
private func detectErrorType(_ error: Error) -> (RiveErrorType, String) {
210+
switch error {
211+
case NitroRiveError.instanceNotFound(let message):
212+
return (.viewmodelinstancenotfound, message)
213+
case NitroRiveError.fileNotFound(let message):
214+
return (.filenotfound, message)
215+
default:
216+
break
217+
}
218+
219+
let nsError = error as NSError
220+
let message = nsError.localizedDescription
221+
222+
// RiveErrorCode from RiveRuntime
223+
switch nsError.code {
224+
case RiveErrorCode.noArtboardFound.rawValue:
225+
return (.incorrectartboardname, message)
226+
case RiveErrorCode.noStateMachineFound.rawValue:
227+
return (.incorrectstatemachinename, message)
228+
case RiveErrorCode.noAnimationFound.rawValue:
229+
return (.unknown, message)
230+
case RiveErrorCode.malformedFile.rawValue:
231+
return (.malformedfile, message)
232+
case RiveErrorCode.noStateMachineInputFound.rawValue:
233+
return (.incorrectstatemachineinputname, message)
234+
default:
235+
return (.unknown, message)
236+
}
237+
}
238+
}

ios/ReferencedAssetLoader.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,14 @@ func createIncorrectRiveURL(_ url: String) -> NSError {
2323
])
2424
}
2525

26-
func createAssetFileError(_ assetName: String) -> NSError {
27-
return NSError(
28-
domain: RiveErrorDomain, code: 801,
29-
userInfo: [
30-
NSLocalizedDescriptionKey: "Could not load Rive asset: \(assetName)", "name": "FileNotFound",
31-
])
26+
func createAssetFileError(_ assetName: String) -> NitroRiveError {
27+
return NitroRiveError.fileNotFound(message: "Could not load Rive asset: \(assetName)")
3228
}
3329

3430
final class ReferencedAssetLoader {
35-
private func handleRiveError(error: NSError) {
31+
private func handleRiveError(error: Error) {
3632
// TODO allow user to specify onError callback
37-
RCTLogError(error.localizedDescription)
33+
RCTLogError("\(error)")
3834
}
3935

4036
private func handleInvalidUrlError(url: String) {

ios/RiveReactNativeView.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ struct ViewConfiguration {
2626
let bindData: BindData
2727
}
2828

29+
enum NitroRiveError: Error {
30+
case instanceNotFound(message: String)
31+
case fileNotFound(message: String)
32+
}
33+
2934
class RiveReactNativeView: UIView, RiveStateMachineDelegate {
3035
// MARK: Internal Properties
3136
private var riveView: RiveView?
@@ -50,7 +55,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate {
5055
return true
5156
}
5257

53-
func configure(_ config: ViewConfiguration, dataBindingChanged: Bool = false, reload: Bool = false, initialUpdate: Bool = false) {
58+
func configure(_ config: ViewConfiguration, dataBindingChanged: Bool = false, reload: Bool = false, initialUpdate: Bool = false) throws {
5459
if reload {
5560
cleanup()
5661
let model = RiveModel(riveFile: config.riveFile)
@@ -75,7 +80,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate {
7580
}
7681

7782
if dataBindingChanged || initialUpdate {
78-
applyDataBinding(config.bindData)
83+
try applyDataBinding(config.bindData)
7984
}
8085
}
8186

@@ -87,7 +92,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate {
8792
return baseViewModel?.riveModel?.stateMachine?.viewModelInstance
8893
}
8994

90-
func applyDataBinding(_ bindData: BindData) {
95+
func applyDataBinding(_ bindData: BindData) throws {
9196
let stateMachine = baseViewModel?.riveModel?.stateMachine
9297
let artboard = baseViewModel?.riveModel?.artboard
9398

@@ -106,7 +111,7 @@ class RiveReactNativeView: UIView, RiveStateMachineDelegate {
106111
let viewModel = riveFile.defaultViewModel(for: artboard),
107112
let instance = viewModel.createInstance(fromName: name)
108113
else {
109-
return
114+
throw NitroRiveError.instanceNotFound(message: "\(name) instance not found")
110115
}
111116
stateMachine?.bind(viewModelInstance: instance)
112117
// this should be added if we support only playing artboards on their own - https://github.com/rive-app/rive-nitro-react-native/pull/23#discussion_r2534698281

ios/Utils.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,3 @@ final class SendableRef<T: Sendable>: @unchecked Sendable {
1313
self.value = value
1414
}
1515
}
16-
17-
/// Executes a closure, logging any thrown error with an optional note and tag using RCTLogError.
18-
func logged(tag: String, note: String? = nil, _ fn: () throws -> Void) {
19-
do {
20-
return try fn()
21-
} catch (let e) {
22-
RCTLogError("[RIVE] \(tag) \(note ?? "") \(e)")
23-
}
24-
}

nitrogen/generated/android/c++/JFunc_void_RiveError.hpp

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nitrogen/generated/android/c++/JHybridRiveViewSpec.cpp

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nitrogen/generated/android/c++/JHybridRiveViewSpec.hpp

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)