mozilla-mobile / android-components

⚠️ This project moved to a new repository. It is now developed and maintained at: https://github.com/mozilla-mobile/firefox-android

Home Page:https://github.com/mozilla-mobile/firefox-android

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

WebContent does not allow for tab switching via Target.Tab

AllanWang opened this issue · comments

WebContent works fine for Target.SelectedTab, but if we want to change tabs ourselves via Target.Tab with different ids, it won't listen to changes.

val selectedTab = target.observeAsComposableStateFrom(
store = store,
observe = { tab ->
// Render if the tab itself changed or when the state of the linked engine session changes
arrayOf(
tab?.id,
tab?.engineState?.engineSession,
tab?.engineState?.crashed,
tab?.content?.firstContentfulPaint
)
}
)

This is because the portion above is only disposed on store and lifecycleOwner changes, and not also when the target changes. As a result, it always tests against the first target.tabId. Occasionally, editing compose will trigger a soft restart, which actually updates the value; this is unrelated to the issue.


Use case:

I have an app with:

  • One WebContent for the main screen, with tabs (TabLayout) to switch between sessions
  • One pop up screen, which is not always present, with its own WebContent

At any given moment, only one session is active. The one in the main screen with the corresponding tab, or the pop up screen. However, if I show the pop up screen, I don't want the main screen to also show that tab, which is why I'm using Target.Tab directly. Is this an appropriate use case, or is Target.Tab incorrect? Should I be using two browser stores instead?

┆Issue is synchronized with this Jira Task

@Composable
fun WebContent(engine: Engine, store: BrowserStore, target: Target) {

  val selectedTab =
    target.observeAsComposableStateFrom2(
      store = store,
      observe = { tab ->
        // Render if the tab itself changed or when the state of the linked engine session changes
        arrayOf(
          tab?.id,
          tab?.engineState?.engineSession,
          tab?.engineState?.crashed,
          tab?.content?.firstContentfulPaint
        )
      }
    )

  // No changes here
}

@Composable
private fun <R> Target.observeAsComposableStateFrom2(
  store: BrowserStore,
  observe: (SessionState?) -> R
): State<SessionState?> {
  return store.observeAsComposableState(
    this,
    map = { state -> lookupIn(state) },
    observe = { state -> observe(lookupIn(state)) }
  )
}

@Composable
private fun <S : mozilla.components.lib.state.State, A : Action, K, O, R> Store<S, A>
  .observeAsComposableState(key: K, observe: (S) -> O, map: (S) -> R): State<R?> {
  val lifecycleOwner = LocalLifecycleOwner.current
  var lastValue = observe(state)
  val state = remember { mutableStateOf<R?>(map(state)) }

  DisposableEffect(this, key, lifecycleOwner) {
    val subscription =
      observe(lifecycleOwner) { browserState ->
        val newValue = observe(browserState)
        if (newValue != lastValue) {
          state.value = map(browserState)
          lastValue = newValue
        }
      }
    onDispose { subscription?.unsubscribe() }
  }

  return state
}

This is the easiest change I could think of. Add target as key to DisposableEffect in observeAsComposableState, given that changing tabs has no effect on the Store. I imagine this means this is not the correct approach, and that I should be using SelectedTab