How Did We Discover the Missing Android Icons Issue During Our React Native App Modernization?
During a recent project for a PropTech platform, we were tasked with modernizing the mobile user experience. The application, which handles complex property floor plans and gallery management, needed smoother navigation transitions and better performance. To achieve this, we decided to upgrade the architecture to Expo 56 and migrate from standard JavaScript-based navigation to the experimental native bottom tabs using the unstable package.
While the implementation went smoothly at first, an architectural discrepancy soon surfaced. Everything worked perfectly on iOS using native SF Symbols, and the tab bar itself was fully functional on Android. However, we encountered a situation where the Material Symbols simply refused to display on Android devices, leaving blank spaces where the navigation icons should have been.
In production environments, missing navigational cues severely degrade user experience and accessibility. When companies decide to hire software developer teams to handle complex migrations, they expect seamless cross-platform parity. This challenge required us to dig into the native layer of Expo’s new routing paradigm. We are sharing this experience so other engineering teams can avoid similar pitfalls when adopting bleeding-edge native capabilities in React Native.
Why Were Material Symbols Critical for the Native Bottom Tabs Architecture?
The business use case demanded a highly responsive, native-feeling application. Traditional JavaScript-based tab bars often suffer from frame drops during heavy state updates. By leveraging native bottom tabs, we offloaded the navigation UI to the native OS threads, ensuring 60 frames per second even when the JavaScript thread was busy processing heavy property data.
To implement this, we utilized the platform-specific symbol system provided by Expo. For iOS, we used sfSymbol, and for Android, we used materialSymbol. This approach is highly efficient because it leverages vector-based OS-level glyphs rather than shipping bulky image assets. This is exactly the kind of optimization organizations look for when they hire react native developers for mobile modernization.
Here is a sanitized look at how the navigation was originally configured in the architecture:
<Tab.Navigator
screenOptions={{
tabBarStyle: { backgroundColor: theme.background },
tabBarActiveTintColor: theme.primary,
tabBarInactiveTintColor: theme.secondary,
tabBarActiveIndicatorColor: theme.primary,
}}
>
<Tab.Screen
name="Dashboard"
component={DashboardScreen}
options={{
tabBarIcon: Platform.select({
ios: { type: "sfSymbol", name: "house" },
android: { type: "materialSymbol", name: "home", variant: "filled", weight: 400 },
}) as any,
}}
/>
{featureFlags.showFloorPlan && (
<Tab.Screen
name="PropertyGallery"
component={GalleryScreen}
options={{
tabBarIcon: Platform.select({
ios: { type: "sfSymbol", name: "square.grid.3x3" },
android: { type: "materialSymbol", name: "grid_view", variant: "filled", weight: 400 },
}) as any,
}}
/>
)}
<Tab.Screen
name="Preferences"
component={PreferencesScreen}
options={{
tabBarIcon: Platform.select({
ios: { type: "sfSymbol", name: "gear" },
android: { type: "materialSymbol", name: "settings", variant: "filled", weight: 400 },
}) as any,
}}
/>
</Tab.Navigator>
What Caused the Material Symbols to Fail on Android While iOS Worked Flawlessly?
The core of the issue lay in how iOS and Android handle native symbols differently. On iOS, SF Symbols are deeply integrated into the operating system. When the native bottom tab implementation requests an sfSymbol, the iOS system readily provides the vector graphic without any additional application-level configuration.
Android, however, does not natively bundle Material Symbols at the OS level in the exact same plug-and-play manner for external views. For the experimental native bottom tabs to render a materialSymbol, the underlying font and asset files must be explicitly injected into the Android build. Because the project was utilizing a managed workflow, the native Android directories were generated dynamically. Without explicit instructions in the configuration to bundle these fonts, the Android native view simply received an instruction to render a glyph it did not possess, resulting in a silent failure and an invisible icon.
What Alternative Solutions Did We Evaluate to Fix the Missing Icons?
When dealing with experimental libraries, diagnosing the root cause requires evaluating multiple layers of the application stack. We considered several approaches to resolve this visibility issue:
Should We Revert to JavaScript Based Tabs?
The most immediate fallback was to abandon the native tabs and revert to the stable JavaScript-based @react-navigation/bottom-tabs. While this would immediately solve the icon issue, it meant sacrificing the significant performance gains we set out to achieve. We discarded this option.
Could We Use Custom SVG or PNG Assets?
We explored bypassing the symbol system entirely and passing custom SVGs or PNGs as icons. However, the experimental native tabs API heavily favors the native symbol objects. Injecting custom assets would require building custom native bridges, complicating the codebase. Organizations that hire frontend developers for enterprise ui expect scalable solutions, and managing rasterized assets for every tab state is not scalable.
Could We Manually Link Fonts in the Native Folders?
We considered ejecting to a bare workflow and manually adding the Material Symbols font files to the Android assets/fonts directory. While technically feasible, this breaks the continuous integration benefits of the Expo managed workflow and prebuild system.
Can We Configure Expo Plugins to Handle the Injection?
Our final and most robust consideration was utilizing Expo’s config plugins to automatically inject the necessary native dependencies during the build phase. This aligned perfectly with modern React Native best practices.
How Did We Successfully Implement and Configure Material Symbols for Expo Native Tabs?
The solution required us to explicitly tell the Expo build system to include the symbol assets for Android. In Expo 56, this is handled by the expo-symbols config plugin, which seamlessly bridges the gap between the JavaScript request for a Material Symbol and the native Android font rendering engine.
First, we updated the app.json configuration file to include the required plugin:
{
"expo": {
"name": "PropTechMobile",
"slug": "proptech-mobile",
"version": "1.0.0",
"plugins": [
"expo-router",
"expo-symbols"
]
}
}
After updating the configuration, the critical step was ensuring the native Android project was properly regenerated. Simply reloading the JavaScript bundle is insufficient when dealing with native asset changes. We ran the following command to clean and rebuild the native directories:
npx expo prebuild --clean
Finally, we verified the implementation. By clearing the Metro bundler cache and rebuilding the Android application, the materialSymbol assets were successfully injected into the native build. The Android bottom tab bar immediately rendered the filled, weighted icons perfectly, matching the high-fidelity native experience of the iOS counterpart.
What Key Takeaways Should Engineering Teams Remember When Adopting Experimental Native Features?
Navigating bleeding-edge framework updates requires a mature engineering approach. Here are the key lessons our team extracted from this integration challenge:
- Understand Asymmetric Platform Behavior: Never assume that because an API is unified in JavaScript, the underlying OS handles the request identically. iOS and Android have fundamentally different asset management systems.
- Prebuild Hygiene is Critical: When dealing with missing native assets, UI, or fonts, always clear your prebuild directories. Stale native code is a common culprit in cross-platform framework issues.
- Read the Plugin Ecosystem Carefully: Experimental packages often decouple their dependencies. The native tabs relied on
expo-symbolsto function properly on Android, an implicit dependency that required explicit configuration. - Avoid Premature Ejection: Before abandoning a managed workflow to fix a native bug, thoroughly investigate the config plugin ecosystem. Most native bridging issues can now be solved within the
app.jsonfile. - Test Hardware Early: Emulators sometimes cache system fonts differently than physical devices. Always validate native UI changes on physical Android and iOS hardware to catch symbol rendering failures.
- Embrace Feature Flags: Because this was an unstable package, wrapping the tab navigation in conditional feature flags allowed us to test the native UI safely without blocking the production release cycle.
How Can Expert Engineering Teams Help You Navigate Complex Framework Migrations?
Migrating to next-generation architectures like native bottom tabs provides massive performance benefits, but it also introduces platform-specific complexities that require deep native understanding. Resolving this Material Symbol rendering issue demonstrated the importance of knowing how cross-platform frameworks compile down to the metal. If your organization is looking to hire mobile app developer to create a cross-platform app that truly feels native, having a partner who understands these lower-level intricacies is invaluable. To learn how our dedicated engineering teams can support your next modernization initiative, contact us.
Social Hashtags
#Expo56 #ExpoRouter #ReactNative #AndroidDevelopment #MobileDevelopment #MaterialSymbols #ExpoSDK #ReactNativeDev #JavaScript #TypeScript #CrossPlatform #SoftwareEngineering #FrontendDevelopment #NativeMobile #AppDevelopment
Frequently Asked Questions
Native bottom tabs offload the rendering and transition animations to the native UI threads of iOS and Android. This drastically reduces the load on the JavaScript thread, preventing frame drops and ensuring a significantly smoother user experience, especially in data-heavy applications.
These are platform-specific vector graphic libraries. sfSymbol refers to Apple's system-level iconography used across iOS and macOS. materialSymbol refers to Google's variable font iconography used in Android. Using platform-specific symbols ensures your application adheres to the native design guidelines of each operating system.
In a managed Expo workflow, the native iOS and Android project files are dynamically generated. If you add a new library that requires native fonts, permissions, or build scripts, running the prebuild command regenerates the native code to include these new dependencies correctly.
Experimental packages should be used with caution in production. They are subject to breaking changes between minor framework updates. If utilized, they should be isolated behind feature flags or abstractions so they can be easily swapped out or reverted if critical issues arise.
Expo config plugins are essentially scripts written in JavaScript or TypeScript that modify native files like AndroidManifest.xml, build.gradle, or Info.plist during the prebuild phase. They allow developers to configure native module settings without ever having to write Java, Kotlin, Swift, or Objective-C directly.
Success Stories That Inspire
See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.

California-based SMB Hired Dedicated Developers to Build a Photography SaaS Platform

Swedish Agency Built a Laravel-Based Staffing System by Hiring a Dedicated Remote Team

















