Skip to content

Commit 81d8889

Browse files
committed
refactor(ios): shared property listener implementation
1 parent 080bac3 commit 81d8889

7 files changed

Lines changed: 126 additions & 105 deletions
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import Foundation
2+
import RiveRuntime
3+
4+
/// Protocol for Rive property types that support listener management
5+
protocol RivePropertyWithListeners: AnyObject {
6+
func removeListener(_ id: UUID)
7+
}
8+
9+
typealias BooleanPropertyType = RiveDataBindingViewModel.Instance.BooleanProperty
10+
typealias NumberPropertyType = RiveDataBindingViewModel.Instance.NumberProperty
11+
typealias StringPropertyType = RiveDataBindingViewModel.Instance.StringProperty
12+
typealias EnumPropertyType = RiveDataBindingViewModel.Instance.EnumProperty
13+
typealias ColorPropertyType = RiveDataBindingViewModel.Instance.ColorProperty
14+
typealias TriggerPropertyType = RiveDataBindingViewModel.Instance.TriggerProperty
15+
typealias ImagePropertyType = RiveDataBindingViewModel.Instance.ImageProperty
16+
17+
// 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 {}
25+
26+
/// Helper class for managing ViewModel property listeners
27+
/// Similar to Android's BaseHybridViewModelPropertyImpl, provides reusable listener management
28+
class PropertyListenerHelper<PropertyType: RivePropertyWithListeners> {
29+
private var listenerIds: [UUID] = []
30+
weak var property: PropertyType?
31+
32+
init(property: PropertyType) {
33+
self.property = property
34+
}
35+
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) {
39+
guard let property = property else { return }
40+
let id = addListener(property)
41+
listenerIds.append(id)
42+
}
43+
44+
func removeListeners() throws {
45+
guard let property = property else { return }
46+
for id in listenerIds {
47+
property.removeListener(id)
48+
}
49+
listenerIds.removeAll()
50+
}
51+
52+
func dispose() throws {
53+
try? removeListeners()
54+
}
55+
}
56+
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 {
60+
associatedtype PropertyType: RivePropertyWithListeners
61+
62+
var helper: PropertyListenerHelper<PropertyType> { get }
63+
64+
func removeListeners() throws
65+
func dispose() throws
66+
}
67+
68+
/// Default implementations for common listener management methods
69+
extension ViewModelPropertyProtocol {
70+
func removeListeners() throws {
71+
try helper.removeListeners()
72+
}
73+
74+
func dispose() throws {
75+
try helper.dispose()
76+
}
77+
}
78+
Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import NitroModules
22
import RiveRuntime
33

4-
class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec {
5-
private var property: RiveDataBindingViewModel.Instance.BooleanProperty!
6-
private var listenerIds: [UUID] = []
4+
class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec, ViewModelPropertyProtocol {
5+
private var property: BooleanPropertyType!
6+
lazy var helper = PropertyListenerHelper(property: property!)
77

8-
init(property: RiveDataBindingViewModel.Instance.BooleanProperty) {
8+
init(property: BooleanPropertyType) {
99
self.property = property
1010
super.init()
1111
}
@@ -28,20 +28,10 @@ class HybridViewModelBooleanProperty: HybridViewModelBooleanPropertySpec {
2828
}
2929

3030
func addListener(onChanged: @escaping (Bool) -> Void) throws {
31-
let id = property.addListener({ value in
32-
onChanged(value)
33-
})
34-
listenerIds.append(id)
35-
}
36-
37-
func removeListeners() throws {
38-
for id in listenerIds {
39-
property.removeListener(id)
31+
helper.trackListener { property in
32+
property.addListener { value in
33+
onChanged(value)
34+
}
4035
}
41-
listenerIds.removeAll()
42-
}
43-
44-
func dispose() throws {
45-
try? removeListeners()
4636
}
4737
}

ios/HybridViewModelColorProperty.swift

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

4-
class HybridViewModelColorProperty: HybridViewModelColorPropertySpec {
5-
private var property: RiveDataBindingViewModel.Instance.ColorProperty!
6-
private var listenerIds: [UUID] = []
4+
class HybridViewModelColorProperty: HybridViewModelColorPropertySpec, ViewModelPropertyProtocol {
5+
private var property: ColorPropertyType!
6+
lazy var helper = PropertyListenerHelper(property: property!)
77

8-
init(property: RiveDataBindingViewModel.Instance.ColorProperty) {
8+
init(property: ColorPropertyType) {
99
self.property = property
1010
super.init()
1111
}
@@ -28,21 +28,11 @@ class HybridViewModelColorProperty: HybridViewModelColorPropertySpec {
2828
}
2929

3030
func addListener(onChanged: @escaping (Double) -> Void) throws {
31-
let id = property.addListener({ value in
32-
onChanged(value.toHexDouble())
33-
})
34-
listenerIds.append(id)
35-
}
36-
37-
func removeListeners() throws {
38-
for id in listenerIds {
39-
property.removeListener(id)
31+
helper.trackListener { property in
32+
property.addListener { value in
33+
onChanged(value.toHexDouble())
34+
}
4035
}
41-
listenerIds.removeAll()
42-
}
43-
44-
func dispose() throws {
45-
try? removeListeners()
4636
}
4737
}
4838

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

4-
class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec {
5-
private var property: RiveDataBindingViewModel.Instance.EnumProperty!
6-
private var listenerIds: [UUID] = []
4+
class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec, ViewModelPropertyProtocol {
5+
private var property: EnumPropertyType!
6+
lazy var helper = PropertyListenerHelper(property: property!)
77

8-
init(property: RiveDataBindingViewModel.Instance.EnumProperty) {
8+
init(property: EnumPropertyType) {
99
self.property = property
1010
super.init()
1111
}
@@ -28,20 +28,10 @@ class HybridViewModelEnumProperty: HybridViewModelEnumPropertySpec {
2828
}
2929

3030
func addListener(onChanged: @escaping (String) -> Void) throws {
31-
let id = property.addListener({ value in
32-
onChanged(value)
33-
})
34-
listenerIds.append(id)
35-
}
36-
37-
func removeListeners() throws {
38-
for id in listenerIds {
39-
property.removeListener(id)
31+
helper.trackListener { property in
32+
property.addListener { value in
33+
onChanged(value)
34+
}
4035
}
41-
listenerIds.removeAll()
42-
}
43-
44-
func dispose() throws {
45-
try? removeListeners()
4636
}
4737
}
Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import RiveRuntime
22

3-
class HybridViewModelNumberProperty: HybridViewModelNumberPropertySpec {
4-
var property: RiveDataBindingViewModel.Instance.NumberProperty!
5-
private var listenerIds: [UUID] = []
3+
class HybridViewModelNumberProperty: HybridViewModelNumberPropertySpec, ViewModelPropertyProtocol {
4+
var property: NumberPropertyType!
5+
lazy var helper = PropertyListenerHelper(property: property!)
66

7-
init(property: RiveDataBindingViewModel.Instance.NumberProperty) {
7+
init(property: NumberPropertyType) {
88
self.property = property
99
super.init()
1010
}
@@ -27,22 +27,11 @@ class HybridViewModelNumberProperty: HybridViewModelNumberPropertySpec {
2727
}
2828

2929
func addListener(onChanged: @escaping (Double) -> Void) throws {
30-
let id = property.addListener({ value in
31-
onChanged(Double(value))
32-
})
33-
34-
listenerIds.append(id)
35-
36-
}
37-
38-
func removeListeners() throws {
39-
for id in listenerIds {
40-
property.removeListener(id)
30+
helper.trackListener { property in
31+
property.addListener { value in
32+
onChanged(Double(value))
33+
}
4134
}
42-
listenerIds.removeAll()
4335
}
4436

45-
func dispose() throws {
46-
try? removeListeners()
47-
}
4837
}
Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import NitroModules
22
import RiveRuntime
33

4-
class HybridViewModelStringProperty: HybridViewModelStringPropertySpec {
5-
private var property: RiveDataBindingViewModel.Instance.StringProperty!
6-
private var listenerIds: [UUID] = []
4+
class HybridViewModelStringProperty: HybridViewModelStringPropertySpec, ViewModelPropertyProtocol {
5+
private var property: StringPropertyType!
6+
lazy var helper = PropertyListenerHelper(property: property!)
77

8-
init(property: RiveDataBindingViewModel.Instance.StringProperty) {
8+
init(property: StringPropertyType) {
99
self.property = property
1010
super.init()
1111
}
@@ -28,20 +28,11 @@ class HybridViewModelStringProperty: HybridViewModelStringPropertySpec {
2828
}
2929

3030
func addListener(onChanged: @escaping (String) -> Void) throws {
31-
let id = property.addListener({ value in
32-
onChanged(value)
33-
})
34-
listenerIds.append(id)
35-
}
36-
37-
func removeListeners() throws {
38-
for id in listenerIds {
39-
property.removeListener(id)
31+
helper.trackListener { property in
32+
property.addListener { value in
33+
onChanged(value)
34+
}
4035
}
41-
listenerIds.removeAll()
4236
}
4337

44-
func dispose() throws {
45-
try? removeListeners()
46-
}
4738
}
Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import NitroModules
22
import RiveRuntime
33

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

8-
init(property: RiveDataBindingViewModel.Instance.TriggerProperty) {
8+
init(property: TriggerPropertyType) {
99
self.property = property
1010
super.init()
1111
}
@@ -23,18 +23,11 @@ class HybridViewModelTriggerProperty: HybridViewModelTriggerPropertySpec {
2323
}
2424

2525
func addListener(onChanged: @escaping () -> Void) throws {
26-
let id = property.addListener({ onChanged() })
27-
listenerIds.append(id)
28-
}
29-
30-
func removeListeners() throws {
31-
for id in listenerIds {
32-
property.removeListener(id)
26+
helper.trackListener { property in
27+
property.addListener {
28+
onChanged()
29+
}
3330
}
34-
listenerIds.removeAll()
3531
}
3632

37-
func dispose() throws {
38-
try? removeListeners()
39-
}
4033
}

0 commit comments

Comments
 (0)