2

Was looking into Neo Launcher Feeder on github.

Found an issue where the compose view in the ComposeOverlayView is not recomposing despite state changes. I have modified the setContent in onCreate to specifically target and solve this issue and explicitly added LifecycleOwner interface to the class.

Generally implemented all the interfaces that should make composables recompose LifecycleOwner, SavedStateRegistryOwner, ViewModelStoreOwner

Tried various solutions including Jetpack Compose, recomposition won't trigger when using custom Lifecycle/ViewModelStore/SavedStateRegistry Owner


/*
 * This file is part of Neo Feed
 * Copyright (c) 2023   Saul Henriquez <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package com.saulhdev.feeder.overlay

import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.appcompat.view.ContextThemeWrapper
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.android.libraries.gsa.d.a.OverlayController
import com.saulhdev.feeder.NFApplication
import com.saulhdev.feeder.R
import com.saulhdev.feeder.compose.pages.OverlayPage
import com.saulhdev.feeder.theme.AppTheme
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.compose.withDI

class ComposeOverlayView(val context: Context) :
    OverlayController(context, R.style.AppTheme, R.style.WindowTheme), LifecycleOwner,
    SavedStateRegistryOwner, ViewModelStoreOwner, OnBackPressedDispatcherOwner,
    ActivityResultRegistryOwner, DIAware {
    private lateinit var rootView: View
    private lateinit var composeView: ComposeView
    private lateinit var navController: NavHostController
    private val lifecycleRegistry = LifecycleRegistry(this)
    override val lifecycle: Lifecycle get() = lifecycleRegistry
    override val onBackPressedDispatcher: OnBackPressedDispatcher
        get() = OnBackPressedDispatcher()

    private val savedStateRegistryController = SavedStateRegistryController.create(this)
    override val savedStateRegistry: SavedStateRegistry
        get() = savedStateRegistryController.savedStateRegistry
    private val parentDI: DI by closestDI()
    override val di: DI by DI.lazy { extend(parentDI) }

    init {
        savedStateRegistryController.performAttach()
        savedStateRegistryController.performRestore(null)
        lifecycleRegistry.currentState = Lifecycle.State.STARTED
    }

    override fun onCreate(bundle: Bundle?) {
        super.onCreate(bundle)
        rootView = View.inflate(
            ContextThemeWrapper(this, R.style.AppTheme),
            R.layout.compose_overlay,
            container
        )
        lifecycleRegistry.currentState = Lifecycle.State.CREATED
        rootView.setViewTreeSavedStateRegistryOwner(this)
        rootView.setViewTreeLifecycleOwner(this)
        rootView.setViewTreeViewModelStoreOwner(this)
        rootView.setViewTreeOnBackPressedDispatcherOwner(this)
        composeView = rootView.findViewById(R.id.compose_view)
        composeView.setContent {
            navController = rememberNavController()
            AppTheme {
                var t by remember {
                    mutableStateOf(false)
                }
                androidx.compose.material3.Surface {
                    androidx.compose.material3.Button(onClick = {
                        t = !t
                    }) {
                        androidx.compose.material3.Text(text = "Start: $t")
                    }
                }
            }

        }
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
    }

    override fun onResume() {
        super.onResume()
        lifecycleRegistry.currentState = Lifecycle.State.RESUMED
    }

    override val viewModelStore: ViewModelStore
        get() = ViewModelStore()
    override val activityResultRegistry: ActivityResultRegistry
        get() = NFApplication.mainActivity!!.activityResultRegistry

}

Links to similar issues

how to use Jetpack Compose in Service (Floating Window)

InputMethodService with Jetpack Compose - ComposeView causes: Composed into the View which doesn't propagate ViewTreeLifecycleOwner

EDIT 9/01/24 Around added a lifecycle observer in the compose setContent and as expected it was not being called

DisposableEffect(lifecycleOwner) {
   val observer = object : LifecycleObserver {
       @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
       fun onResume() {
           Log.v(TAG,"onResume Compose")
       }
   }

   lifecycleOwner.lifecycle.addObserver(observer)

   onDispose {
        lifecycleOwner.lifecycle.removeObserver(observer)
   }
}
2
  • is onResume getting called? I faced similar issue when lifecycle was not resumed Commented Jan 4, 2024 at 8:14
  • @PhilDukhov Yep. Just tested and onResume is being called Commented Jan 5, 2024 at 14:19

2 Answers 2

0

You're missing this


lifecycleRegistry.currentState = Lifecycle.State.ON_START
Sign up to request clarification or add additional context in comments.

Comments

-1

I assume that it doesn't recompose because the parameter that changes (t) is only accessed inside a string. Try to move the button into a composable function and pass t as the parameter like this:

composeView.setContent {
     navController = rememberNavController()
     AppTheme {
            var t by remember {
                mutableStateOf(false)
            }
            androidx.compose.material3.Surface {
                MyButton(t)
            }
     }
}


// ...

@Composable
fun MyButton(param: Boolean) {
   androidx.compose.material3.Button(onClick = {
         param = !param
   }) {
         androidx.compose.material3.Text(text = "Start: $param")
   }
}

1 Comment

Recomposition will, or better still, should happen to any composable using the variable regardless of how it's being used.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.