bytedance / scene

Android Single Activity Framework compatible with Fragment.
Apache License 2.0
2.08k stars 198 forks source link

Scene cannot be cast to android.content.ComponentCallbacks #35

Closed chachako closed 4 years ago

chachako commented 4 years ago

在使用依赖注入框架 Koin 注入 ViewModel 时报 ClassCastException 异常,是否应该为 Scene 继承 ComponentCallbacks 接口https://github.com/bytedance/scene/blob/3dc15e1f2e5ce0acc662c65613475ee386341a24/library/scene/src/main/java/com/bytedance/scene/Scene.java#L112 image

qii commented 4 years ago

蛋疼啊,Scene 不想有 onConfigurationChanged 这些

chachako commented 4 years ago

蛋疼啊,Scene 不想有 onConfigurationChanged 这些

好的,那我手动改改吧...

qii commented 4 years ago

你可以搞个子类实现这个接口,然后 onActivityCreateed 里面通过 NavigationScene.addConfigurationXXXX 的转发给自己的方法,其实我感觉 koin 的这个要求很奇怪

Ccixyj commented 4 years ago

另外一种是自己写一个LifecycleOwner.getKoin()的扩展方法。然后通过getkoin().getViewModel(lifecycleowner....)手动实现一个viewmodl代理方法。

fun LifecycleOwner.getKoin(): Koin = when (this) {
    is KoinComponent -> this.getKoin()
    is Scene -> ((this as? KoinComponent)?.getKoin()
        ?: (this.requireActivity() as? KoinComponent)?.getKoin()
        ?: error("scene attach parent must be KoinComponent"))
    else -> KoinContextHandler.get()
}
emredagci commented 3 years ago

If you would like to use two frameworks together, you should define a new extension then you can use the koin framework with scene framework.


import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import com.bytedance.scene.Scene
import org.koin.android.viewmodel.*
import org.koin.core.Koin
import org.koin.core.context.GlobalContext
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.core.scope.Scope
import kotlin.reflect.KClass

inline fun <reified T : ViewModel> LifecycleOwner.customViewModel(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): Lazy<T> = lazy { getViewModelCustom(qualifier, parameters) }

inline fun <reified T : ViewModel> LifecycleOwner.getViewModelCustom(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): T {
    return getViewModelCustom(T::class, qualifier, parameters)
}

fun <T : ViewModel> LifecycleOwner.getViewModelCustom(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    parameters: ParametersDefinition? = null
): T {
    return GlobalContext.get().koin.getViewModelCustom(
        ViewModelParameters(
            clazz,
            this@getViewModelCustom,
            qualifier,
            parameters = parameters
        )
    )
}

fun <T : ViewModel> Koin.getViewModelCustom(parameters: ViewModelParameters<T>): T {
    val vmStore: ViewModelStore = parameters.owner.getViewModelStoreCustom(parameters)
    val viewModelProvider = rootScope.createViewModelProviderCustom(vmStore, parameters)
    return viewModelProvider.getInstanceCustom(parameters)
}

fun <T : ViewModel> ViewModelProvider.getInstanceCustom(parameters: ViewModelParameters<T>): T {
    val javaClass = parameters.clazz.java
    return if (parameters.qualifier != null) {
        this.get(parameters.qualifier.toString(), javaClass)
    } else {
        this.get(javaClass)
    }
}

fun <T : ViewModel> LifecycleOwner.getViewModelStoreCustom(
    parameters: ViewModelParameters<T>
): ViewModelStore =
    when {
        parameters.from != null -> parameters.from!!.invoke().viewModelStore
        this is FragmentActivity -> ViewModelStores.of(this)
        this is Fragment -> ViewModelStores.of(this)
        this is Scene -> this.viewModelStore
        else -> error("Can't getByClass ViewModel '${parameters.clazz}' on $this - Is not a FragmentActivity nor a Fragment neither a valid ViewModelStoreOwner")
    }

fun <T : ViewModel> Scope.createViewModelProviderCustom(
    vmStore: ViewModelStore,
    parameters: ViewModelParameters<T>
): ViewModelProvider {
    return ViewModelProvider(
        vmStore,
        object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return get(parameters.clazz, parameters.qualifier, parameters.parameters)
            }
        })
}

After you created the extension you can use like code shown below.

class SplashScene : Scene() {

    private val splashViewModel: SplashViewModel by customViewModel()

}
ZhaoSiBo commented 2 years ago

i use koin version is 3.2.0,scene version is 1.3.1 i used code

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelStoreOwner
import com.bytedance.scene.Scene
import com.bytedance.scene.ktx.viewModels
import org.koin.android.scope.getScopeOrNull
import org.koin.androidx.viewmodel.ext.android.getViewModelFactory
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.component.getScopeId
import org.koin.core.component.getScopeName
import org.koin.core.context.GlobalContext
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier

@OptIn(KoinInternalApi::class)
inline fun <reified T : ViewModel> Scene.viewModel(
    qualifier: Qualifier? = null,
    owner : ViewModelStoreOwner = this,
    noinline parameters: ParametersDefinition? = null
): Lazy<T> {
    val scope = GlobalContext.get().createScope(getScopeId(), getScopeName())
    val activityScope = activity?.getScopeOrNull()
    activityScope?.let { scope.linkTo(it) }
    return viewModels{
        getViewModelFactory<T>(owner, qualifier, parameters, scope = scope)
    }
}

after i used like code below

class DiscoverScene : AppCompatScene() {

    private val model: DiscoverViewModel by viewModel()

}