Skip to content

refactor(Tabs, Android): Split TabsHost into TabsHost and TabsContainer#3766

Merged
kkafar merged 3 commits intomainfrom
@kkafar/tabs-refactor-5
Mar 25, 2026
Merged

refactor(Tabs, Android): Split TabsHost into TabsHost and TabsContainer#3766
kkafar merged 3 commits intomainfrom
@kkafar/tabs-refactor-5

Conversation

@kkafar
Copy link
Copy Markdown
Member

@kkafar kkafar commented Mar 16, 2026

Description

This PR splits TabsHost native class on Android into two: TabsHost and TabsContainer.

This follows RFC-1028

Changes

I've moved all functionalities related to:

  1. safe area,
  2. color scheme,
  3. tab bar hidden,
  4. container update,
  5. a11y,
  6. appearance

(and possibly more) to TasbContainer.

TabsHost is now only a source of configuration, layout and TabsScreens.

Notably, I've left the logic related to layoutCallback and layout
refresh inside TabsHost. I'm not sure where this logic should live.
In the future we might to move it from there. One thing to note here is
that TabsContainer relies on it - now via call to
this.requestLayout, which calls the overridden function on the TabsHost (view parent).

For the transition period, I also pass to TabsContainer a reference to TabsHost. This obviously
creates circular dependency, which is never resolved, but as I've learned recently from @kligarski
it shouldn't be a problem for the GC. This will be changed later, when I
refactor communication patterns.

Please don't mind commented out code / leftovers in this commit. I
intend to clean them up later, in follow up commits.

Test plan

Regarding functionality - ideally everything should work just as before.
I don't want to perform full manual scan right now, as more "big"
changes are planned before we land this one. I've only asserted that
basic functionality works as intended on TestBottomTabs.

@LKuchno
Copy link
Copy Markdown
Collaborator

LKuchno commented Mar 17, 2026

Manual test were perform using Android emulator and FabricExample App.
No issues found.

Functionalities covered in tests:
using screens from Single-feature-tests/tabs:

  • safe area - IME insets screen (test-tabs-ime-insets)
  • color scheme -> Color Scheme screen (test-tabs-color-scheme), different configuration, overwritten system setting using RN and TabsHost values
  • tab bar hidden - Tab Bar Hidden screen (tab-bar-hidden)
    using TestBottomTabs screen:
  • appearance - all props available for android were checked except state-based appearance configuration for disabled state
    -additionally checked navigation between tabs and screens per tab

Not covered in tests:

  • container update
  • a11y

@kkafar kkafar force-pushed the @kkafar/tabs-refactor-4 branch from c0a9bb2 to e015bb5 Compare March 24, 2026 14:43
Base automatically changed from @kkafar/tabs-refactor-4 to main March 24, 2026 15:55
kkafar added 3 commits March 24, 2026 17:03
I've moved all functionalities related to:

1. safe area,
2. color scheme,
3. tab bar hidden,
4. container update,
5. a11y,
6. appearance

(and possibly more) to `TasbContainer`.

`TabsHost` is now only a source of configuration, layout and `TabsScreens`.

Notably, I've left the logic related to `layoutCallback` and layout
refresh inside `TabsHost`. I'm not sure where this logic should live.
In the future we might to move it from there. One thing to note here is
that `TabsContainer` relies on it - now via call to
`this.requestLayout`, which calls the overridden function on the `TabsHost` (view parent).

**For the transition period**, I also pass to `TabsContainer` a reference to `TabsHost`. This obviously
creates circular dependency, which is never resolved, but as I've learned recently from @kligarski
it shouldn't be a problem for the GC. This will be changed later, when I
refactor communication patterns.

Please don't mind commented out code / leftovers in this commit. I
intend to clean them up later, in follow up commits.

Regarding functionality - ideally everything should work just as before.
I don't want to perform full manual scan right now, as more "big"
changes are planned before we land this one. I've only asserted that
basic functionality works as intended on `TestBottomTabs`.
It does not overryde anything yet
@kkafar kkafar force-pushed the @kkafar/tabs-refactor-5 branch from 354d8ee to ac90649 Compare March 24, 2026 16:03
Copy link
Copy Markdown
Member Author

@kkafar kkafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notes for reviewers


@SuppressLint("ViewConstructor") // Created only by us. Should never be restored.
internal class TabsContainer(private val context: Context,
internal val tabsHost: TabsHost // TODO: TEMPORARY, REMOVE THIS DEPENDENCY
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is removed in the follow-up PR

Comment on lines +72 to +73
// TODO: AFTER REFACTOR COMPLETES, THIS SHOULD BE PRIVATE
internal val tabsModel: MutableList<TabsScreenFragment> = arrayListOf()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this will be made private in follow-up: #3776

Comment on lines +156 to +159
// containerUpdateCoordinator.let {
// it.invalidateAll()
// it.runContainerUpdate()
// }
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up later

}

post {
// refreshLayout()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up later

Comment on lines +331 to +334
// containerUpdateCoordinator.let {
// it.invalidateNavigationMenu()
// it.postContainerUpdateIfNeeded()
// }
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up later

Comment on lines +346 to +349
// containerUpdateCoordinator.let {
// it.invalidateAll()
// it.postContainerUpdateIfNeeded()
// }
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up later

isSelectedTabInvalidated = true
isBottomNavigationMenuInvalidated = true
}
} No newline at end of file
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in follow-up

Comment on lines +69 to +75
// override fun setNavState(view: TabsHost, value: ReadableMap?) {
// val navStateMap = requireNotNull(value) { "[RNScreens] NavState must not be nullish" }
// val selectedScreenKey = requireNotNull(navStateMap.getString("selectedScreenKey"))
// val provenance = requireNotNull(navStateMap.getInt("provenance"))
// // view.setNavStateFromJS(TabsHost.NavState(selectedScreenKey, provenance))
// }
//
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up in follow-up #3776

@kkafar
Copy link
Copy Markdown
Member Author

kkafar commented Mar 24, 2026

I'll tackle linting & other smaller cleanup problems later, in follow-up PR.

@kkafar kkafar marked this pull request as ready for review March 24, 2026 16:08
Comment on lines +130 to +132
post {
performContainerUpdateIfNeeded()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this code is repeated 3 times across the whole file

}

private fun updateBottomNavigationViewAppearance() {
RNSLog.d(TabsHost.Companion.TAG, "updateBottomNavigationViewAppearance")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should we use TabsHost's TAG outside TabsHost?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing 1 possible leftover

private fun onMenuItemSelected(item: MenuItem): Boolean {
        RNSLog.d(TabsHost.Companion.TAG, "Item selected $item")

@kkafar kkafar requested a review from t0maboro March 25, 2026 09:13
@kkafar kkafar merged commit 98a5c44 into main Mar 25, 2026
4 of 5 checks passed
@kkafar kkafar deleted the @kkafar/tabs-refactor-5 branch March 25, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants