Another month of being hammered by KMM.

Last time I mentioned MVIKotlin, it was hard to use because Jetpack AAC is so easy to use. I decided to manually port Jetpack AAC to KMM. Since LiveData can be replaced with Kotlinx.coroutines StateFlow/SharedFlow, Lifecycle and ViewModel can theoretically be ported, Lifecycle annotating a function so that it can run at the time of execution of the corresponding Lifecycle is also temporarily deprecated because annotation processors must use KCP to be usable on the KMM platform, which is difficult and can be left for later.

This adds up to a neutered Lifecycle and ViewModel library that needs porting. I want the port’s public API to be as consistent as possible with the Jetpack version, and preferably fully Jetpack compatible. The basic migration is to declare the public API in the Common Source set using Expect. If the API is a top-level function/static method, The Android source set’s actual implementation calls the Jetpack version directly, while the iOS Source set’s actual implementation copies the Jetpack implementation code and modifies the platform-specific code. If the API is a class/interface or other type declaration, the actual implementation of the Android Source Set directly Bridges typeAlias equivalents to types in Jetpack. The iOS Source set is still a copy of Jetpack’s declaration and implementation code. Theoretically, because types bridge to declarations in Jetpack using TypeAliases, Lifecycle and ViewModel in the Common layer at compile time and runtime are completely equivalent to declarations in Jetpack, and we can omit a lot of implementation code. For example, Fragment and ComponentActivity are platform-specific components that implement interfaces such as ViewModelStoreOwner.

Although the theory is very ideal, but the implementation encountered two unsolvable problems:

Jetpack Lifecycle and ViewModel public APIS reference strong platform dependent types

For example, the Class<? > :

public open class ViewModelProvider( private val store: ViewModelStore, private val factory: Factory ) { /** * Implementations of `Factory` interface are responsible to instantiate ViewModels. */ public interface Factory { /** * Creates a new instance of the given `Class`. * * @param modelClass a `Class` whose instance is requested  * @return a newly created ViewModel */ public fun <T : ViewModel> create(modelClass: Class<T>): T }Copy the code

For example, in the ViewModelProvider Factory, the create function calls Class<? < span style = “max-width: 100%; clear: both; min-height: 1em; But once overwritten, we can’t bridge the Factory through TypeAlias, and if we define the Factory interface ourselves, compatibility with Jetpack will be broken.

Reflection is not available on Kotlin/Native

Mentioned above that Jetpack needs Class<? > object, whose fundamental purpose is to use reflection, for example to create objects using reflection:

// actually there is getInstance()    
@Suppress("SingletonConstructor")    
public open class NewInstanceFactory : Factory {        
    @Suppress("DocumentExceptions")        
    override fun <T : ViewModel> create(modelClass: Class<T>): T {            
        return try {                
            modelClass.newInstance()            
        } catch (e: InstantiationException) {                
            throw RuntimeException("Cannot create an instance of $modelClass", e)               } catch (e: IllegalAccessException) {                
            throw RuntimeException("Cannot create an instance of $modelClass", e)               }        
    }
Copy the code

In Kotlin, reflection is fully supported only on the JVM platform, with limited support on JS platforms and almost no support on Native platforms. So many API designs had to be completely changed due to the lack of reflection. For example, creating an object might require taking a constructor or factory function as an argument.

What do we do?

We initially wanted to achieve Jetpack compatibility for three reasons:

1. Implementation with Jetpack on Android is more stable.

2. Migrating existing Android Kotlin code to KMM is easier.

  1. Root-level UI components such as fragments or activities that implement interfaces such as LifecycleOwner or ViewModelStoreOwner do not need to be redefined to reduce workload.

Since compatibility is almost impossible, we can abandon compatibility in favor of ease of use. So the solution was to write a KMM version of the architecture components with Reference to Lifecycle and the ViewModel design.

Open a pit here, and then open a project on Github later. If the idea is big enough, record the update.