Как реализовать общий Intent, State и базовые классы в MVI?

Перехожу с MVVM на MVI в своем pet-проекте и пока проблемы с тонкостями реализации ;) Вводные: есть несколько общих блоков кода, которые я использую на разных экранах, затолкав их в базовые классы. Например, абстрактный фрагмент и viewModel для логики searchBar, которая используется на N экранах. MVVM имеет нестрогую организацию - я мог вызвать нужный метод из SearchViewModel в любой из реализаций, но в MVI я должен заменить этот вызов на строгий dispatchIntent. Проблема в том, что все интенты и состояния в моем понимании являются запечатанными классами, а для них вроде как нельзя создать общий класс (один на несколько экранов. В идеале, если бы я смог расшарить условный SearchIntent(val keyWord: String) с несколькими экранами. Вот код:

SearchViewModel:

abstract class SearchViewModel<INTENT : MviIntent, STATE : MviState>(defaultState: STATE) :
    BaseSubscriptionViewModel<INTENT, STATE>(defaultState) {
    private var keyWord: String = ""

    override suspend fun dispatchIntent(intent: INTENT) {
        this.keyWord = intent.keyWord ?: ""  // <= Error
    }
    /**
     * Changed key word async.
     *
     * @param keyWord Key word
     */
    fun updateKeyWordAsync(keyWord: String?) = launchInBackground { // <= Should be replaced by dispatchIntent.
        keyWordWasChanged(keyWord)
    }

    /**
     * Key word was changed.
     *
     * @param keyWord Key word
     */
    protected open suspend fun keyWordWasChanged(keyWord: String?) {
        this.keyWord = keyWord ?: ""
    }
}

Fragment:

abstract class SearchToolbarFragment<INTENT : MviIntent, STATE : MviState, VM : SearchViewModel<INTENT, STATE>>(
    private val vmKClass: KClass<VM>
) : ToolbarFragment<INTENT, STATE, VM>() {
    open val menuId: Int = R.menu.menu_search

    private val queryTextListener = object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            viewModel.dispatchIntentAsync(INTENT(query)) // <= Error. How to resolve it?
            // viewModel.updateKeyWordAsync(query)            
            return true
        }

        override fun onQueryTextChange(newText: String?): Boolean {
            viewModel.dispatchIntentAsync(INTENT(query)) // <= Error. How to resolve it?
            // viewModel.updateKeyWordAsync(query)   
            return true
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(
            store = viewModelStore,
            factory = viewModelFactory,
            defaultCreationExtras = defaultViewModelCreationExtras
        )[vmKClass.java]
    }

    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        super.onCreateMenu(menu, menuInflater)
        menuInflater.inflate(menuId, menu)
        initSearch(menu)
    }

    /**
     * Init search.
     *
     * @param menu Menu
     */
    private fun initSearch(menu: Menu) {
        // ....
    }
}

В добавок появился еще один вопрос: в какой момент вызывать initSearch(menu)? С одной стороны мне нужен объект темы, но с другой внутри у есть зависимость от текущего keyWord. PS Если есть хороший полный гайд по MVI - буду рад. На статьи с простейшими примерам я уже насмотрелся, они ответа на мои вопросы не дают.


Ответы (0 шт):