Skip to content

Instantly share code, notes, and snippets.

@Alex009
Created March 31, 2019 16:09
Show Gist options
  • Save Alex009/532dc3770d58ea5c0da79df8f02fde13 to your computer and use it in GitHub Desktop.
Save Alex009/532dc3770d58ea5c0da79df8f02fde13 to your computer and use it in GitHub Desktop.
Kotlin/Native generics usage samples

Simple generics usage

class in kotlin - LiveData.kt

/**
 * Created by Aleksey Mikhailov <Aleksey.Mikhailov@icerockdev.com> on 09.07.2018.
 */
expect open class LiveData<T> {
    open val value: T

    fun <OT> map(function: (T) -> OT): LiveData<OT>

    fun <OT> flatMap(function: (T) -> LiveData<OT>): LiveData<OT>

    fun addObserver(observer: (T?) -> Unit)

    fun removeObserver(observer: (T?) -> Unit)
}

after compilation we get - MultiplatrformFramework.h

__attribute__((swift_name("LiveData")))
@interface MPLLiveData : KotlinBase
- (instancetype)initWithInitialValue:(id _Nullable)initialValue __attribute__((swift_name("init(initialValue:)"))) __attribute__((objc_designated_initializer));
- (void)addObserverObserver:(MPLKotlinUnit *(^)(id _Nullable))observer __attribute__((swift_name("addObserver(observer:)")));
- (void)addObserverObserver_:(id<MPLLiveDataObserver>)observer __attribute__((swift_name("addObserver(observer_:)")));
- (void)changeValueValue:(id _Nullable)value __attribute__((swift_name("changeValue(value:)")));
- (MPLLiveData *)flatMapFunction:(MPLLiveData *(^)(id _Nullable))function __attribute__((swift_name("flatMap(function:)")));
- (MPLLiveData *)mapFunction:(id _Nullable (^)(id _Nullable))function __attribute__((swift_name("map(function:)")));
- (void)removeObserverObserver:(MPLKotlinUnit *(^)(id _Nullable))observer __attribute__((swift_name("removeObserver(observer:)")));
- (void)removeObserverObserver_:(id<MPLLiveDataObserver>)observer __attribute__((swift_name("removeObserver(observer_:)")));
@property (readonly) id _Nullable value;
@end;
__attribute__((swift_name("LiveDataObserver")))
@protocol MPLLiveDataObserver
@required
- (void)valueChangedLiveData:(MPLLiveData *)liveData value:(id _Nullable)value __attribute__((swift_name("valueChanged(liveData:value:)")));
@end;

for use in swift with types we cast via extensions - MPMLiveData+Bind.swift

class LiveObserverTyped<T>: NSObject, LiveDataObserver, RemovableObserver {
  var closure: ((T?) -> Void)?
  var mapping: ((Any?) -> T?)?
  weak var liveData: LiveData?
  func valueChanged(liveData: LiveData, value: Any?) {
    self.liveData = liveData
    closure?(mapping?(value) ?? (value as? T))
  }
  
  func removeSelfFromObservers() {
    liveData?.removeObserver(observer_: self)
  }
  
  init(closure: ((T?) -> Void)?, _ mapping: ((Any?) -> T?)? = nil) {
    self.closure = closure
    self.mapping = mapping
  }
}

another casts - LiveData+typed.swift

extension LiveData {
  func type<T>(_ type: T.Type, action: @escaping (T) -> Void) -> LiveDataObserver {
    guard let data = value as? T else { abort() }
    action(data)
    
    let observer = LiveObserverTyped<T>(closure: { data in
      guard let data = data else { abort() }
      action(data)
    })
    addObserver(observer_: observer)
    return observer
  }
}

in code usage:

stateLiveData.type(MultiPlatformLibrary.State.self, action: { state in
        dataView.isHidden = !(state.isSuccess())
        emptyView.isHidden = !(state.isEmpty())
        loadingView.isHidden = !(state.isLoading())
        errorView.isHidden = !(state.isError())
      })

if in kotlin we change type of generic - swift side not fail at compilation. it fail in runtime.

More difficult generics

in kotlin we have:

sealed class State<out T, out E> {
    data class Data<out T, out E>(val data: T) : State<T, E>()
    data class Error<out T, out E>(val error: E) : State<T, E>()
    class Loading<out T, out E> : State<T, E>()
    class Empty<out T, out E> : State<T, E>()

    fun isLoading(): Boolean = this is Loading
    fun isSuccess(): Boolean = this is Data
    fun isEmpty(): Boolean = this is Empty
    fun isError(): Boolean = this is Error

    fun dataValue(): T? = (this as? Data)?.data

    fun errorValue(): E? = (this as? Error)?.error
}

but we not want to create instances at swift side. all creating at kotlin side. but when we have in kotlin public api this:

val state: LiveData<State<String, Throwable>>

we want to use it from swift without casts & with compiler types checks (if we change in kotlin String to Int - swift side at compilation should fail)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment