在 activity android 中传递对 ViewModel 的引用的好方法

Good way to pass a reference to a ViewModel in an activity android

我有一个从基础 class 继承的 ViewModel,我希望相应的 Activity 也从基础 class 继承。 activity 每次都会调用派生的 ViewModel 的相同方法。所以它会是这样的:

BaseViewModel:

abstract class BaseViewModel(application: Application) : AndroidViewModel(application) {
    protected val context = getApplication<Application>().applicationContext

    protected var speechManager: SpeechRecognizerManager? = null

    var _actionToTake : MutableLiveData<AnalyseVoiceResults.Actions> = MutableLiveData()
    var actionToTake : LiveData<AnalyseVoiceResults.Actions> = _actionToTake

    open fun stopListening() {
        if (speechManager != null) {
        speechManager?.destroy()
        speechManager = null
    }

    open fun startListening() {
        val isListening = speechManager?.ismIsListening() ?: false
        if (speechManager == null) {
           SetSpeechListener()
        } else if (!isListening) {
           speechManager?.destroy()
           SetSpeechListener()
        }

    }
}

基础Activity

class BaseActivity : AppCompatActivity() {

    private lateinit var baseViewModel: BaseViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    fun goback() {
        super.onBackPressed()
        baseViewModel.stopListening()
        finish()
    }

    fun startListening() { 
        baseViewModel.startListening()
    }

    override fun onDestroy() {
        super.onDestroy()
        baseViewModel.stopListening()
    }
}

派生Activity:

class DerivedActivity : BaseActivity() {

    private val nextActivityViewModel: NextActivityViewModel by inject()
                                    ///^^inherits from BaseViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        /*** pass reference ***/
        baseViewModel = nexActivityViewModel

        nextActivityViewModel.actionToTake.observe(this, object : Observer<AnalyseVoiceResults.Actions?> {
            override fun onChanged(t: AnalyseVoiceResults.Actions?) {
                if (t?.equals(AnalyseVoiceResults.Actions.GO_BACK) ?: false) {
                    goback()
                }
            }
         })

        startListening()
    }
}

这是否会导致内存泄漏,为此 activity 有两个视图模型实例?有一个更好的方法吗?我不想为我的所有活动重复相同的代码。 (如果我用一个碱基片段做这件事,我也会有同样的问题)。

使这个 var baseViewModel: BaseViewModel 成为一个抽象变量,所有子 class 都必须覆盖它。因此,当您调用 startListening 和 stopListening 时,这些方法将从子实现中调用。

编辑:

将 BaseActivity 设为抽象 class,将 baseViewModel 设为抽象变量

abstract class BaseActivity : AppCompatActivity() {

    private abstract var baseViewModel: BaseViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    fun goback() {
        super.onBackPressed()
        baseViewModel.stopListening()
        finish()
    }

    fun startListening() { 
        baseViewModel.startListening()
    }

    override fun onDestroy() {
        super.onDestroy()
        baseViewModel.stopListening()
    }
}

因此,您的 DerivedActivity 必须覆盖 baseViewModel,每次调用父亲的 class 都会触发子

class DerivedActivity : BaseActivity() {

    override val baseViewModel: NextActivityViewModel by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

Will this cause memory leaks to have two instances of a view model for this activity?

不,这种方法没有内存泄漏。您也没有相同 activity 的 2 个 ViewModel 实例。它是 ViewModel 的单个实例,在 BaseActivity 和 BaseViewModel 中被不同的变量引用。

Is there a better way to do this?

我看到的第一个问题是您在 ViewModels 中有 Android 特定代码,这不是一个好的做法。您应该将语音管理器代码移动到基础 activity 本身,并且 ViewModel 应该只保存您希望在方向更改时保留的 "state" 数据。这将确保所有语音管理方法(创建、恢复、销毁)都在基础 activity 中。具体 activity 只有在状态改变时才会有观察者。

如果您遵循任何架构模式(如 MVP),一旦您将 Speech Manager 代码移出到 activity,将其进一步移出到 Presenter 将变得显而易见。

编辑:我没有在生产中使用 MVVM 模式,但这是您可能想要的轻型变体:

基本思想是将语音管理代码移动到生命周期感知组件中。 view/activity 中的所有 UI 代码和视图模型中的业务逻辑/非 android 状态。根据您目前分享的要求,我认为使用基础 activity 或视图模型没有意义。

/**
 * All the speech related code is encapsulated here, so any new activity/fragment can use it by registering it's lifecycle
 */

class SpeechManager(private val context: Context): LifecycleObserver {

    val TAG = "SpeechManager"

    private var speechRecognizer: SpeechRecognizer? = null

    fun registerWithLifecycle(lifecycle: Lifecycle) {
        Log.e(TAG, "registerWithLifecycle")
        lifecycle.addObserver(this)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun start() {
        Log.e(TAG, "start")
        speechRecognizer = (speechRecognizer ?: SpeechRecognizer.createSpeechRecognizer(context)).apply {

//            setRecognitionListener(object : RecognitionListener {
//                //implement methods
//            })
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stop() {
        Log.e(TAG, "stop")
        speechRecognizer?.run {
            stopListening()
            destroy()
        }
    }
}

视图模型:

class SpeechViewModel: ViewModel() {

    val TAG = "SpeechViewModel"

    //List all your "data/state" that needs to be restores across activity restarts
    private val actions: MutableLiveData<Actions> = MutableLiveData<Actions>().apply { value = Actions.ActionA }

    //Public API for getting observables and all use-cases
    fun getActions() = actions
    fun doActionA(){
        //validations, biz logic
        Log.e(TAG, "doActionA")
        actions.value = Actions.ActionA
    }
    fun doActionB(){
        Log.e(TAG, "doActionB")
        actions.value = Actions.ActionB
    }
}

sealed class Actions{
    object ActionA: Actions()
    object ActionB: Actions()
}

Activity/View:

class SpeechActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_speech)
        setSupportActionBar(toolbar)
        initSpeechManager()
    }

    private lateinit var speechManager: SpeechManager
    private lateinit var speechViewModel: SpeechViewModel

    /**
     * Register lifecycle aware components and start observing state changes from ViewModel.
     * All UI related code should ideally be here (or your view equivalent in MVVM)
     */
    private fun initSpeechManager() {
        speechManager = SpeechManager(this).apply {registerWithLifecycle(lifecycle)}
        speechViewModel = ViewModelProviders.of(this).get(SpeechViewModel::class.java).apply {
            getActions().observe(this@SpeechActivity, Observer<Actions>{
                when(it){
                    is Actions.ActionA -> {
                        Log.e(TAG, "Perform ActionA")
                        speechManager.start()
                    }
                    is Actions.ActionB -> {
                        Log.e(TAG, "Perform ActionB")
                        speechManager.stop()
                        super.onBackPressed()
                    }
                }
            })
        }
    }
}