Skip to content

Commit 6d77a5c

Browse files
committed
refactor: conditional addListener on type match
1 parent 81d8889 commit 6d77a5c

7 files changed

Lines changed: 69 additions & 67 deletions

ios/BaseHybridViewModelProperty.swift

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import RiveRuntime
33

44
/// Protocol for Rive property types that support listener management
55
protocol RivePropertyWithListeners: AnyObject {
6+
associatedtype ListenerValueType
7+
8+
func addListener(_ callback: @escaping (ListenerValueType) -> Void) -> UUID
69
func removeListener(_ id: UUID)
710
}
811

@@ -15,16 +18,24 @@ typealias TriggerPropertyType = RiveDataBindingViewModel.Instance.TriggerPropert
1518
typealias ImagePropertyType = RiveDataBindingViewModel.Instance.ImageProperty
1619

1720
// Make all Rive property types conform to the protocol
18-
extension BooleanPropertyType: RivePropertyWithListeners {}
19-
extension NumberPropertyType: RivePropertyWithListeners {}
20-
extension StringPropertyType: RivePropertyWithListeners {}
21-
extension EnumPropertyType: RivePropertyWithListeners {}
22-
extension ColorPropertyType: RivePropertyWithListeners {}
23-
extension TriggerPropertyType: RivePropertyWithListeners {}
24-
extension ImagePropertyType: RivePropertyWithListeners {}
21+
extension BooleanPropertyType: RivePropertyWithListeners {
22+
typealias ListenerValueType = Bool // Native: Bool → Bool (no conversion)
23+
}
24+
extension NumberPropertyType: RivePropertyWithListeners {
25+
typealias ListenerValueType = Float // Native: Float → Double (needs conversion)
26+
}
27+
extension StringPropertyType: RivePropertyWithListeners {
28+
typealias ListenerValueType = String // Native: String → String (no conversion)
29+
}
30+
extension EnumPropertyType: RivePropertyWithListeners {
31+
typealias ListenerValueType = String // Native: String → String (no conversion)
32+
}
33+
extension ColorPropertyType: RivePropertyWithListeners {
34+
typealias ListenerValueType = UIColor // Native: UIColor → Double (needs conversion)
35+
}
36+
// Note: TriggerProperty doesn't fit the pattern - it has () -> Void listeners, not (Void) -> Void
2537

2638
/// Helper class for managing ViewModel property listeners
27-
/// Similar to Android's BaseHybridViewModelPropertyImpl, provides reusable listener management
2839
class PropertyListenerHelper<PropertyType: RivePropertyWithListeners> {
2940
private var listenerIds: [UUID] = []
3041
weak var property: PropertyType?
@@ -33,11 +44,10 @@ class PropertyListenerHelper<PropertyType: RivePropertyWithListeners> {
3344
self.property = property
3445
}
3546

36-
/// Adds a listener and automatically tracks its ID for later cleanup
37-
/// - Parameter addListener: Closure that adds the listener to the property and returns the listener ID
38-
func trackListener(_ addListener: (PropertyType) -> UUID) {
47+
/// Adds a listener to the property and automatically tracks its ID for cleanup
48+
func addListener(_ callback: @escaping (PropertyType.ListenerValueType) -> Void) {
3949
guard let property = property else { return }
40-
let id = addListener(property)
50+
let id = property.addListener(callback)
4151
listenerIds.append(id)
4252
}
4353

@@ -54,19 +64,23 @@ class PropertyListenerHelper<PropertyType: RivePropertyWithListeners> {
5464
}
5565
}
5666

57-
/// Protocol that provides common functionality for all hybrid ViewModel property classes
58-
/// Reduces boilerplate by providing default implementations for listener management
59-
protocol ViewModelPropertyProtocol {
67+
/// Protocol for properties that have typed values (Bool, String, Double, etc.)
68+
/// Provides a default addListener implementation
69+
protocol ValuedPropertyProtocol<ValueType>{
6070
associatedtype PropertyType: RivePropertyWithListeners
71+
associatedtype ValueType
6172

73+
var property: PropertyType! { get }
6274
var helper: PropertyListenerHelper<PropertyType> { get }
63-
75+
76+
func addListener(onChanged: @escaping (ValueType) -> Void) throws
6477
func removeListeners() throws
6578
func dispose() throws
6679
}
6780

68-
/// Default implementations for common listener management methods
69-
extension ViewModelPropertyProtocol {
81+
82+
/// Default implementations for lifecycle methods (always available)
83+
extension ValuedPropertyProtocol {
7084
func removeListeners() throws {
7185
try helper.removeListeners()
7286
}
@@ -76,3 +90,10 @@ extension ViewModelPropertyProtocol {
7690
}
7791
}
7892

93+
/// Automatic addListener() ONLY when ListenerValueType == ValueType (no conversion needed)
94+
extension ValuedPropertyProtocol where PropertyType.ListenerValueType == ValueType {
95+
func addListener(onChanged: @escaping (ValueType) -> Void) throws {
96+
helper.addListener(onChanged) // Types match, just forward directly!
97+
}
98+
}
99+

ios/HybridViewModelBooleanProperty.swift

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import NitroModules
22
import RiveRuntime
33

4-
class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec, ViewModelPropertyProtocol {
5-
private var property: BooleanPropertyType!
4+
class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec, ValuedPropertyProtocol {
5+
var property: BooleanPropertyType!
66
lazy var helper = PropertyListenerHelper(property: property!)
77

88
init(property: BooleanPropertyType) {
@@ -26,12 +26,4 @@ class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec, ViewMo
2626
property.value = newValue
2727
}
2828
}
29-
30-
func addListener(onChanged: @escaping (Bool) -> Void) throws {
31-
helper.trackListener { property in
32-
property.addListener { value in
33-
onChanged(value)
34-
}
35-
}
36-
}
3729
}

ios/HybridViewModelColorProperty.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import NitroModules
22
import RiveRuntime
33

4-
class HybridViewModelColorProperty: HybridViewModelColorPropertySpec, ViewModelPropertyProtocol {
5-
private var property: ColorPropertyType!
4+
class HybridViewModelColorProperty: HybridViewModelColorPropertySpec, ValuedPropertyProtocol {
5+
var property: ColorPropertyType!
66
lazy var helper = PropertyListenerHelper(property: property!)
77

88
init(property: ColorPropertyType) {
@@ -27,11 +27,10 @@ class HybridViewModelColorProperty: HybridViewModelColorPropertySpec, ViewModelP
2727
}
2828
}
2929

30+
// Custom addListener because we need to convert UIColor → Double
3031
func addListener(onChanged: @escaping (Double) -> Void) throws {
31-
helper.trackListener { property in
32-
property.addListener { value in
33-
onChanged(value.toHexDouble())
34-
}
32+
helper.addListener { (color: UIColor) in
33+
onChanged(color.toHexDouble())
3534
}
3635
}
3736
}

ios/HybridViewModelEnumProperty.swift

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import NitroModules
22
import RiveRuntime
33

4-
class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec, ViewModelPropertyProtocol {
5-
private var property: EnumPropertyType!
4+
class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec, ValuedPropertyProtocol {
5+
var property: EnumPropertyType!
66
lazy var helper = PropertyListenerHelper(property: property!)
77

88
init(property: EnumPropertyType) {
@@ -26,12 +26,4 @@ class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec, ViewModelPro
2626
property.value = newValue
2727
}
2828
}
29-
30-
func addListener(onChanged: @escaping (String) -> Void) throws {
31-
helper.trackListener { property in
32-
property.addListener { value in
33-
onChanged(value)
34-
}
35-
}
36-
}
3729
}

ios/HybridViewModelNumberProperty.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import RiveRuntime
22

3-
class HybridViewModelNumberProperty: HybridViewModelNumberPropertySpec, ViewModelPropertyProtocol {
3+
class HybridViewModelNumberProperty: HybridViewModelNumberPropertySpec, ValuedPropertyProtocol {
44
var property: NumberPropertyType!
55
lazy var helper = PropertyListenerHelper(property: property!)
66

@@ -26,12 +26,10 @@ class HybridViewModelNumberProperty: HybridViewModelNumberPropertySpec, ViewMode
2626
}
2727
}
2828

29+
// Custom addListener needed because ListenerValueType (Float) != ValueType (Double)
2930
func addListener(onChanged: @escaping (Double) -> Void) throws {
30-
helper.trackListener { property in
31-
property.addListener { value in
32-
onChanged(Double(value))
33-
}
31+
helper.addListener { (value: Float) in
32+
onChanged(Double(value))
3433
}
3534
}
36-
3735
}

ios/HybridViewModelStringProperty.swift

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import NitroModules
22
import RiveRuntime
33

4-
class HybridViewModelStringProperty: HybridViewModelStringPropertySpec, ViewModelPropertyProtocol {
5-
private var property: StringPropertyType!
4+
class HybridViewModelStringProperty: HybridViewModelStringPropertySpec, ValuedPropertyProtocol {
5+
var property: StringPropertyType!
66
lazy var helper = PropertyListenerHelper(property: property!)
77

88
init(property: StringPropertyType) {
@@ -26,13 +26,4 @@ class HybridViewModelStringProperty: HybridViewModelStringPropertySpec, ViewMode
2626
property.value = newValue
2727
}
2828
}
29-
30-
func addListener(onChanged: @escaping (String) -> Void) throws {
31-
helper.trackListener { property in
32-
property.addListener { value in
33-
onChanged(value)
34-
}
35-
}
36-
}
37-
3829
}

ios/HybridViewModelTriggerProperty.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import NitroModules
22
import RiveRuntime
33

4-
class HybridViewModelTriggerProperty: HybridViewModelTriggerPropertySpec, ViewModelPropertyProtocol {
4+
class HybridViewModelTriggerProperty: HybridViewModelTriggerPropertySpec {
55
private var property: TriggerPropertyType!
6-
lazy var helper = PropertyListenerHelper(property: property!)
6+
private var listenerIds: [UUID] = []
77

88
init(property: TriggerPropertyType) {
99
self.property = property
@@ -23,11 +23,20 @@ class HybridViewModelTriggerProperty: HybridViewModelTriggerPropertySpec, ViewMo
2323
}
2424

2525
func addListener(onChanged: @escaping () -> Void) throws {
26-
helper.trackListener { property in
27-
property.addListener {
28-
onChanged()
29-
}
26+
let id = property.addListener {
27+
onChanged()
3028
}
29+
listenerIds.append(id)
3130
}
3231

32+
func removeListeners() throws {
33+
for id in listenerIds {
34+
property.removeListener(id)
35+
}
36+
listenerIds.removeAll()
37+
}
38+
39+
func dispose() throws {
40+
try? removeListeners()
41+
}
3342
}

0 commit comments

Comments
 (0)