kotlinstateandroid-jetpack-composeandroid-viewmodelime

Is there a way to get State Management working for Abstract Compose View in Jetpack Compose?


I'm currently creating a keyboard that needs to be able to get Data from a View Model that I've created.

The problem arises to the fact that the Keyboard is not able to be recomposed when any type of state is changed.

Is there a way to be able to implement recomposes/updates as any state is changed for the Abstract Compose View?

For reference this is the code I'm using for the InputMethodService

class IMEService:
    InputMethodService(),
    LifecycleOwner,
    ViewModelStoreOwner,
    SavedStateRegistryOwner {
    private val _lifecycleRegistry: LifecycleRegistry by lazy { LifecycleRegistry(this) }
    private val _store by lazy { ViewModelStore() }
    override fun getLifecycle(): Lifecycle = _lifecycleRegistry
    override fun getViewModelStore(): ViewModelStore = _store
    override val savedStateRegistry: SavedStateRegistry = SavedStateRegistryController.create(this).savedStateRegistry
    private fun handleLifecycleEvent(event: Lifecycle.Event) =
        _lifecycleRegistry.handleLifecycleEvent(event)

    @CallSuper
    override fun onCreate() {
        super.onCreate()
        // You must call performAttach() before calling performRestore(Bundle)
        savedStateRegistry.performAttach(lifecycle)
        savedStateRegistry.performRestore(null)
        handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
    }

    @CallSuper
    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        stopSelf()
    }

    @CallSuper
    override fun onDestroy() {
        super.onDestroy()
        handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    }

    lateinit var viewModel: RepoViewModel
    override fun onCreateInputView(): View {
        val app = (application as Nitroless)
        val view = ComposeKeyboardView(this)
        viewModel = ViewModelProvider(this, RepoViewModelFactory(app.repository, app.communityReposRepository)).get(RepoViewModel::class.java)

        this.attachToDecorView(
            window?.window?.decorView
        )
        return view
    }

    fun attachToDecorView(decorView: View?) {
        if (decorView == null) return

        ViewTreeLifecycleOwner.set(decorView, this)
        ViewTreeViewModelStoreOwner.set(decorView, this)
        decorView.setViewTreeSavedStateRegistryOwner(this)
    }
}

As you can see I've added the View Model as a Provider onCreateInputView() function. The problem is not that I'm not able to access it, the problem is I can't seem to update the ComposeKeyboardView.

Here's my ComposeKeyboardView -

class ComposeKeyboardView(context: Context): AbstractComposeView(context) {
    @Composable
    override fun Content() {
        val viewModel: RepoViewModel = (context as IMEService).viewModel

        KeyboardScreen()
    }
}

Solution

  • I found a solution, I was looking at the Docs for InputMethodService and found setInputView(View: View?), this way I can force refresh the compose view onto the keyboard.

    So I just do

    (context as IMEService).setInputView(ComposeKeyboardVie(context))

    I also found a way to use Hilt inside the IMEService class, by using @AndroidEntryPoint annotation for the Service and using @Inject annotation to inject the View Model.

    
    @AndroidEntryPoint
    class IMEService:
        InputMethodService(),
        LifecycleOwner,
        ViewModelStoreOwner,
        SavedStateRegistryOwner {
        private val _lifecycleRegistry: LifecycleRegistry by lazy { LifecycleRegistry(this) }
        private val _store by lazy { ViewModelStore() }
        override fun getLifecycle(): Lifecycle = _lifecycleRegistry
        override fun getViewModelStore(): ViewModelStore = _store
        override val savedStateRegistry: SavedStateRegistry = SavedStateRegistryController.create(this).savedStateRegistry
        private fun handleLifecycleEvent(event: Lifecycle.Event) =
            _lifecycleRegistry.handleLifecycleEvent(event)
    
        @CallSuper
        override fun onCreate() {
            super.onCreate()
            // You must call performAttach() before calling performRestore(Bundle)
            savedStateRegistry.performAttach(lifecycle)
            savedStateRegistry.performRestore(null)
            handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
        }
    
        @CallSuper
        override fun onTaskRemoved(rootIntent: Intent?) {
            super.onTaskRemoved(rootIntent)
            stopSelf()
        }
    
        @CallSuper
        override fun onDestroy() {
            super.onDestroy()
            handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        }
    
        @Inject
        lateinit var repoViewModel: RepoViewModel
    
        override fun onCreateInputView(): View {
            val view = ComposeKeyboardView(this)
    
            this.attachToDecorView(
                window?.window?.decorView
            )
            return view
        }
    
        fun attachToDecorView(decorView: View?) {
            if (decorView == null) return
    
            ViewTreeLifecycleOwner.set(decorView, this)
            ViewTreeViewModelStoreOwner.set(decorView, this)
            decorView.setViewTreeSavedStateRegistryOwner(this)
        }
    }
    

    And inside the App Module I just added

    @Singleton
    @Provides
    fun providesRepoViewModel(repoDatabaseDao: RepoDatabaseDao, communityReposRepository: CommunityReposRepository) = RepoViewModel(repository = RepoRepository(repoDatabaseDao), communityReposRepository = communityReposRepository)